tty/serial: add generic serial earlycon
authorRob Herring <robh@kernel.org>
Fri, 18 Apr 2014 22:19:55 +0000 (17:19 -0500)
committerMark Brown <broonie@linaro.org>
Thu, 5 Jun 2014 11:05:23 +0000 (12:05 +0100)
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: Jiri Slaby <jslaby@suse.cz>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
(cherry picked from commit 9aac5887595b765b6f64b2af08b785e82e095b57)
Signed-off-by: Mark Brown <broonie@linaro.org>
drivers/tty/serial/Kconfig
drivers/tty/serial/Makefile
drivers/tty/serial/earlycon.c [new file with mode: 0644]
include/linux/serial_core.h

index 7e7006fd404e62000b19b728274c0abf8a51bf38..1ad9aadc00552108f9e2d9b79e65458df9838923 100644 (file)
@@ -7,6 +7,13 @@ if TTY
 menu "Serial drivers"
        depends on HAS_IOMEM && GENERIC_HARDIRQS
 
+config SERIAL_EARLYCON
+       bool
+       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"
index eedfec40e3dda242220cf1dc73b9ec1787c63cee..e51c680d6044995a2d920832d09eb863fe828461 100644 (file)
@@ -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 (file)
index 0000000..73bf1e2
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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>
+
+#ifdef CONFIG_FIX_EARLYCON_MEM
+#include <asm/fixmap.h>
+#endif
+
+#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_io(FIX_EARLYCON_MEM_BASE, paddr & PAGE_MASK);
+       base = (void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
+       base += paddr & ~PAGE_MASK;
+#else
+       base = ioremap(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;
+       }
+
+       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;
+       struct uart_port *port = &early_console_dev.port;
+
+       if (!buf || !match || !setup)
+               return 0;
+
+       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;
+
+       if (port->mapbase)
+               port->membase = earlycon_map(port->mapbase, 64);
+
+       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;
+}
index 87d4bbc773fc7d8d7223423c9d388accc32c527c..c2b355fd921abe3c96b431dc5f9c6413ecb308eb 100644 (file)
@@ -279,6 +279,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,