diff mbox

[4/8] tty/serial: add generic serial earlycon

Message ID 1395436128-11244-5-git-send-email-robherring2@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Rob Herring March 21, 2014, 9:08 p.m. UTC
From: Rob Herring <robh@kernel.org>

This introduces generic earlycon infrastructure for serial devices
based on the 8250 earlycon. This allows for supporting earlycon option
with other serial devices. The earlycon output is enabled at the time
early_params are processed.

Only architectures that have fixmap support or have functional ioremap
when early_params are processed are supported. This is the same
restriction that the 8250 driver had.

Signed-off-by: Rob Herring <robh@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
---
 drivers/tty/serial/Kconfig    |   7 ++
 drivers/tty/serial/Makefile   |   2 +
 drivers/tty/serial/earlycon.c | 148 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/serial_core.h   |  16 +++++
 4 files changed, 173 insertions(+)
 create mode 100644 drivers/tty/serial/earlycon.c

Comments

Catalin Marinas March 24, 2014, 11:22 a.m. UTC | #1
On Fri, Mar 21, 2014 at 09:08:44PM +0000, Rob Herring wrote:
> diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
> new file mode 100644
> index 0000000..241757a
> --- /dev/null
> +++ b/drivers/tty/serial/earlycon.c
[...]
> +static void __iomem * __init earlycon_map(unsigned long paddr, size_t size)
> +{
> +	void __iomem *base;
> +#ifdef CONFIG_FIX_EARLYCON_MEM
> +	set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
> +	base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
> +	base += paddr & ~PAGE_MASK;
> +#else
> +	base = ioremap_nocache(paddr, size);
> +#endif

Just curious why not set_fixmap_io (and plain ioremap)?

> +static int __init parse_options(struct earlycon_device *device,
> +				char *options)
> +{
[...]
> +	if (port->mapbase)
> +		port->membase = earlycon_map(port->mapbase, 64);

I would move the earlycon_map() call in setup_earlycon() (personal
preference, parse_options() implies just parsing the options rather than
having additional side-effects).
Arnd Bergmann March 24, 2014, 11:29 a.m. UTC | #2
On Monday 24 March 2014 11:22:03 Catalin Marinas wrote:
> On Fri, Mar 21, 2014 at 09:08:44PM +0000, Rob Herring wrote:
> > diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
> > new file mode 100644
> > index 0000000..241757a
> > --- /dev/null
> > +++ b/drivers/tty/serial/earlycon.c
> [...]
> > +static void __iomem * __init earlycon_map(unsigned long paddr, size_t size)
> > +{
> > +	void __iomem *base;
> > +#ifdef CONFIG_FIX_EARLYCON_MEM
> > +	set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
> > +	base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
> > +	base += paddr & ~PAGE_MASK;
> > +#else
> > +	base = ioremap_nocache(paddr, size);
> > +#endif
> 
> Just curious why not set_fixmap_io (and plain ioremap)?

Good point. Note that ioremap_nocache() is the same as ioremap()
on *all* architectures.

	Arnd
Rob Herring March 24, 2014, 1:36 p.m. UTC | #3
On Mon, Mar 24, 2014 at 6:29 AM, Arnd Bergmann <arnd@arndb.de> wrote:
> On Monday 24 March 2014 11:22:03 Catalin Marinas wrote:
>> On Fri, Mar 21, 2014 at 09:08:44PM +0000, Rob Herring wrote:
>> > diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
>> > new file mode 100644
>> > index 0000000..241757a
>> > --- /dev/null
>> > +++ b/drivers/tty/serial/earlycon.c
>> [...]
>> > +static void __iomem * __init earlycon_map(unsigned long paddr, size_t size)
>> > +{
>> > +   void __iomem *base;
>> > +#ifdef CONFIG_FIX_EARLYCON_MEM
>> > +   set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
>> > +   base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
>> > +   base += paddr & ~PAGE_MASK;
>> > +#else
>> > +   base = ioremap_nocache(paddr, size);
>> > +#endif
>>
>> Just curious why not set_fixmap_io (and plain ioremap)?
>
> Good point. Note that ioremap_nocache() is the same as ioremap()
> on *all* architectures.

I investigated this before adding this to arm64. set_fixmap_io and
set_fixmap_nocache are not the same settings on x86. Whether the
mapping type really matters on x86 or not, I don't know. So I added
the nocache variant to arm64 to avoid a change to x86.

Rob
Arnd Bergmann March 24, 2014, 3:42 p.m. UTC | #4
On Monday 24 March 2014 08:36:46 Rob Herring wrote:
> On Mon, Mar 24, 2014 at 6:29 AM, Arnd Bergmann <arnd@arndb.de> wrote:
> > On Monday 24 March 2014 11:22:03 Catalin Marinas wrote:
> >> On Fri, Mar 21, 2014 at 09:08:44PM +0000, Rob Herring wrote:
> >> > diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
> >> > new file mode 100644
> >> > index 0000000..241757a
> >> > --- /dev/null
> >> > +++ b/drivers/tty/serial/earlycon.c
> >> [...]
> >> > +static void __iomem * __init earlycon_map(unsigned long paddr, size_t size)
> >> > +{
> >> > +   void __iomem *base;
> >> > +#ifdef CONFIG_FIX_EARLYCON_MEM
> >> > +   set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
> >> > +   base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
> >> > +   base += paddr & ~PAGE_MASK;
> >> > +#else
> >> > +   base = ioremap_nocache(paddr, size);
> >> > +#endif
> >>
> >> Just curious why not set_fixmap_io (and plain ioremap)?
> >
> > Good point. Note that ioremap_nocache() is the same as ioremap()
> > on *all* architectures.
> 
> I investigated this before adding this to arm64. set_fixmap_io and
> set_fixmap_nocache are not the same settings on x86. Whether the
> mapping type really matters on x86 or not, I don't know. So I added
> the nocache variant to arm64 to avoid a change to x86.

My best guess is that it's an x86 bug. ioremap() always uses an uncached
mapping on x86, so it's strange to see early_ioremap() and set_fixmap_io()
use a cached mapping. It probably doesn't matter as long as the mtrr is
set up to treat all MMIO registers as non-cacheable, but I think there
should not be a difference.

	Arnd
Rob Herring April 17, 2014, 6:27 p.m. UTC | #5
On Mon, Mar 24, 2014 at 10:42 AM, Arnd Bergmann <arnd@arndb.de> wrote:
> On Monday 24 March 2014 08:36:46 Rob Herring wrote:
>> On Mon, Mar 24, 2014 at 6:29 AM, Arnd Bergmann <arnd@arndb.de> wrote:
>> > On Monday 24 March 2014 11:22:03 Catalin Marinas wrote:
>> >> On Fri, Mar 21, 2014 at 09:08:44PM +0000, Rob Herring wrote:
>> >> > diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
>> >> > new file mode 100644
>> >> > index 0000000..241757a
>> >> > --- /dev/null
>> >> > +++ b/drivers/tty/serial/earlycon.c
>> >> [...]
>> >> > +static void __iomem * __init earlycon_map(unsigned long paddr, size_t size)
>> >> > +{
>> >> > +   void __iomem *base;
>> >> > +#ifdef CONFIG_FIX_EARLYCON_MEM
>> >> > +   set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
>> >> > +   base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
>> >> > +   base += paddr & ~PAGE_MASK;
>> >> > +#else
>> >> > +   base = ioremap_nocache(paddr, size);
>> >> > +#endif
>> >>
>> >> Just curious why not set_fixmap_io (and plain ioremap)?
>> >
>> > Good point. Note that ioremap_nocache() is the same as ioremap()
>> > on *all* architectures.

Did you mean "not the same"? Why are there 2 flavors if they are
always the same.


>> I investigated this before adding this to arm64. set_fixmap_io and
>> set_fixmap_nocache are not the same settings on x86. Whether the
>> mapping type really matters on x86 or not, I don't know. So I added
>> the nocache variant to arm64 to avoid a change to x86.
>
> My best guess is that it's an x86 bug. ioremap() always uses an uncached
> mapping on x86, so it's strange to see early_ioremap() and set_fixmap_io()
> use a cached mapping. It probably doesn't matter as long as the mtrr is
> set up to treat all MMIO registers as non-cacheable, but I think there
> should not be a difference.

At some point it was believed to be needed on the 8250 driver. Perhaps
Alan can comment since the commit message tells us nothing:

commit 6f441fe99814f64315b8c11890744230b990c460
Author: Alan Cox <alan@lxorguk.ukuu.org.uk>
Date:   Thu May 1 04:34:59 2008 -0700

    8250: switch 8250 drivers to use _nocache ioremaps

    Signed-off-by: Alan Cox <alan@redhat.com>
    Cc: Russell King <rmk@arm.linux.org.uk>
    Cc: Ingo Molnar <mingo@elte.hu>
    Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
    Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Rob
Alan Cox April 17, 2014, 8:16 p.m. UTC | #6
> At some point it was believed to be needed on the 8250 driver. Perhaps
> Alan can comment since the commit message tells us nothing:

Don't remember sorry - I guess at the time someone had ioremap on their
plaform acting as cached. It was a long time ago 8)
Arnd Bergmann April 19, 2014, 11:32 a.m. UTC | #7
On Thursday 17 April 2014, Rob Herring wrote:
> On Mon, Mar 24, 2014 at 10:42 AM, Arnd Bergmann <arnd@arndb.de> wrote:
> > On Monday 24 March 2014 08:36:46 Rob Herring wrote:
> >> On Mon, Mar 24, 2014 at 6:29 AM, Arnd Bergmann <arnd@arndb.de> wrote:
> >> > On Monday 24 March 2014 11:22:03 Catalin Marinas wrote:
> >> >> On Fri, Mar 21, 2014 at 09:08:44PM +0000, Rob Herring wrote:
> >> >> > diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
> >> >> > new file mode 100644
> >> >> > index 0000000..241757a
> >> >> > --- /dev/null
> >> >> > +++ b/drivers/tty/serial/earlycon.c
> >> >> [...]
> >> >> > +static void __iomem * __init earlycon_map(unsigned long paddr, size_t size)
> >> >> > +{
> >> >> > +   void __iomem *base;
> >> >> > +#ifdef CONFIG_FIX_EARLYCON_MEM
> >> >> > +   set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
> >> >> > +   base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
> >> >> > +   base += paddr & ~PAGE_MASK;
> >> >> > +#else
> >> >> > +   base = ioremap_nocache(paddr, size);
> >> >> > +#endif
> >> >>
> >> >> Just curious why not set_fixmap_io (and plain ioremap)?
> >> >
> >> > Good point. Note that ioremap_nocache() is the same as ioremap()
> >> > on *all* architectures.
> 
> Did you mean "not the same"?

No

> Why are there 2 flavors if they are
> always the same.

I assume it's just a mistake someone made a long time ago and nobody
ever changed it because they didn't want to break obscure code.

x86 seems to be a bit odd here, because is has both page-table
and MTRR method of setting up whether an access can be cached or not.
This may have led to the introduction of the _nocache variant, when
the regular ioremap was already uncached on all other architectures
but cached on x86.

	Arnd
diff mbox

Patch

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 2e6d8dd..1685189 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -7,6 +7,13 @@  if TTY
 menu "Serial drivers"
 	depends on HAS_IOMEM
 
+config SERIAL_EARLYCON
+	bool "Early console for serial ports"
+	help
+	  Support for early consoles with the earlycon parameter. This enables
+	  the console before standard serial driver is probed. The console is
+	  enabled when early_param is processed.
+
 source "drivers/tty/serial/8250/Kconfig"
 
 comment "Non-8250 serial port support"
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 3680854..8af1415 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -5,6 +5,8 @@ 
 obj-$(CONFIG_SERIAL_CORE) += serial_core.o
 obj-$(CONFIG_SERIAL_21285) += 21285.o
 
+obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o
+
 # These Sparc drivers have to appear before others such as 8250
 # which share ttySx minor node space.  Otherwise console device
 # names change and other unplesantries.
diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
new file mode 100644
index 0000000..241757a
--- /dev/null
+++ b/drivers/tty/serial/earlycon.c
@@ -0,0 +1,148 @@ 
+/*
+ * Copyright (C) 2014 Linaro Ltd.
+ * Author: Rob Herring <robh@kernel.org>
+ *
+ * Based on 8250 earlycon:
+ * (c) Copyright 2004 Hewlett-Packard Development Company, L.P.
+ *	Bjorn Helgaas <bjorn.helgaas@hp.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/console.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/serial_core.h>
+
+#include <asm/fixmap.h>
+#include <asm/serial.h>
+
+static struct console early_con = {
+	.name =		"earlycon",
+	.flags =	CON_PRINTBUFFER | CON_BOOT,
+	.index =	-1,
+};
+
+static struct earlycon_device early_console_dev = {
+	.con = &early_con,
+};
+
+static void __iomem * __init earlycon_map(unsigned long paddr, size_t size)
+{
+	void __iomem *base;
+#ifdef CONFIG_FIX_EARLYCON_MEM
+	set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
+	base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
+	base += paddr & ~PAGE_MASK;
+#else
+	base = ioremap_nocache(paddr, size);
+#endif
+	if (!base)
+		pr_err("%s: Couldn't map 0x%llx\n", __func__,
+		       (unsigned long long)paddr);
+
+	return base;
+}
+
+static int __init parse_options(struct earlycon_device *device,
+				char *options)
+{
+	struct uart_port *port = &device->port;
+	int mmio, mmio32, length, ret;
+	unsigned long addr;
+
+	if (!options)
+		return -ENODEV;
+
+	mmio = !strncmp(options, "mmio,", 5);
+	mmio32 = !strncmp(options, "mmio32,", 7);
+	if (mmio || mmio32) {
+		port->iotype = (mmio ? UPIO_MEM : UPIO_MEM32);
+		options += mmio ? 5 : 7;
+		ret = kstrtoul(options, 0, &addr);
+		if (ret)
+			return ret;
+		port->mapbase = addr;
+		if (mmio32)
+			port->regshift = 2;
+	} else if (!strncmp(options, "io,", 3)) {
+		port->iotype = UPIO_PORT;
+		options += 3;
+		ret = kstrtoul(options, 0, &addr);
+		if (ret)
+			return ret;
+		port->iobase = addr;
+		mmio = 0;
+	} else if (!strncmp(options, "0x", 2)) {
+		port->iotype = UPIO_MEM;
+		ret = kstrtoul(options, 0, &addr);
+		if (ret)
+			return ret;
+		port->mapbase = addr;
+	} else {
+		return -EINVAL;
+	}
+
+	if (port->mapbase)
+		port->membase = earlycon_map(port->mapbase, 64);
+
+	port->uartclk = BASE_BAUD * 16;
+
+	options = strchr(options, ',');
+	if (options) {
+		options++;
+		ret = kstrtouint(options, 0, &device->baud);
+		if (ret)
+			return ret;
+		length = min(strcspn(options, " ") + 1,
+			     (size_t)(sizeof(device->options)));
+		strlcpy(device->options, options, length);
+	}
+
+	if (mmio || mmio32)
+		pr_info("Early serial console at MMIO%s 0x%llx (options '%s')\n",
+			mmio32 ? "32" : "",
+			(unsigned long long)port->mapbase,
+			device->options);
+	else
+		pr_info("Early serial console at I/O port 0x%lx (options '%s')\n",
+			port->iobase,
+			device->options);
+
+	return 0;
+}
+
+int __init setup_earlycon(char *buf, const char *match,
+			  int (*setup)(struct earlycon_device *, const char *))
+{
+	int err;
+	size_t len;
+
+	if (!buf || !match || !setup)
+		return -EINVAL;
+
+	len = strlen(match);
+	if (strncmp(buf, match, len))
+		return 0;
+	if (buf[len] && (buf[len] != ','))
+		return 0;
+
+	buf += len + 1;
+
+	err = parse_options(&early_console_dev, buf);
+	/* On parsing error, pass the options buf to the setup function */
+	if (!err)
+		buf = NULL;
+
+	early_console_dev.con->data = &early_console_dev;
+	err = setup(&early_console_dev, buf);
+	if (err < 0)
+		return err;
+	if (!early_console_dev.con->write)
+		return -ENODEV;
+
+	register_console(early_console_dev.con);
+	return 0;
+}
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index f729be9..7a15b5b 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -285,6 +285,22 @@  static inline int uart_poll_timeout(struct uart_port *port)
 /*
  * Console helpers.
  */
+struct earlycon_device {
+	struct console *con;
+	struct uart_port port;
+	char options[16];		/* e.g., 115200n8 */
+	unsigned int baud;
+};
+int setup_earlycon(char *buf, const char *match,
+		   int (*setup)(struct earlycon_device *, const char *));
+
+#define EARLYCON_DECLARE(name, func) \
+static int __init name ## _setup_earlycon(char *buf) \
+{ \
+	return setup_earlycon(buf, __stringify(name), func); \
+} \
+early_param("earlycon", name ## _setup_earlycon);
+
 struct uart_port *uart_get_console(struct uart_port *ports, int nr,
 				   struct console *c);
 void uart_parse_options(char *options, int *baud, int *parity, int *bits,