2004-04-01 22:04:34

by Bjorn Helgaas

[permalink] [raw]
Subject: [PATCH] early serial console support

This adds fairly generic early console support to the 8250 serial driver.

The current early_serial_setup() functionality is a bit of a problem for
ia64 because it assumes that you know where ttyS0 is before the driver
initializes. On ia64, we don't know that because all the devices are
enumerated via ACPI and PCI.

However, we do have a firmware interface to tell us where the serial
console device is. So this patch adds serial8250_early_console_setup()
so the architecture can specify the MMIO or I/O port address instead of
the ttyS0 device name.

After the serial driver initializes, we automatically try to locate
the corresponding ttyS device, and start up a console on that.

I'll post the corresponding ia64 changes that take advantage of these
as a response. Feedback/comments welcome.

Bjorn


===== drivers/serial/8250.c 1.48 vs edited =====
--- 1.48/drivers/serial/8250.c Mon Mar 15 15:16:26 2004
+++ edited/drivers/serial/8250.c Thu Apr 1 14:31:04 2004
@@ -1882,6 +1882,7 @@
}

#ifdef CONFIG_SERIAL_8250_CONSOLE
+static int serial8250_early_device(struct uart_port *);

#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)

@@ -1977,6 +1978,13 @@
return -ENODEV;

/*
+ * No need to dump the buffer again if the port is already in
+ * use as the early console device.
+ */
+ if (serial8250_early_device(port))
+ co->flags &= ~CON_PRINTBUFFER;
+
+ /*
* Temporary fix.
*/
spin_lock_init(&port->lock);
@@ -2013,6 +2021,166 @@
return 0;
}
late_initcall(serial8250_late_console_init);
+
+/*
+ * This is for use before the serial driver has initialized, in
+ * particular, before the UARTs have been discovered and named.
+ * Instead of specifying the console device as "ttyS0", platform
+ * code can call serial8250_early_console_setup() to specify it
+ * directly with an MMIO or I/O port address.
+ */
+static struct uart_8250_port serial8250_early_port __initdata;
+static char *serial8250_early_options __initdata;
+
+static void __init serial8250_early_putc(struct uart_8250_port *up, char c)
+{
+ while (!(UART_LSR_TEMT & serial_in(up, UART_LSR)))
+ ;
+
+ serial_out(up, UART_TX, c);
+}
+
+/*
+ * Can't use serial8250_console_write() because delay() might not work yet.
+ */
+static void __init serial8250_early_write(struct console *co, const char *s, unsigned int count)
+{
+ struct uart_8250_port *up = &serial8250_early_port;
+
+ while (*s && count-- > 0) {
+ serial8250_early_putc(up, *s);
+ if (*s == '\n')
+ serial8250_early_putc(up, '\r');
+ s++;
+ }
+}
+
+static struct console serial8250_early_console __initdata = {
+ .name = "serial",
+ .write = serial8250_early_write,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+static unsigned int __init serial8250_probe_baud(struct uart_8250_port *port)
+{
+ unsigned char lcr, dll, dlm;
+ unsigned int quot, baud;
+
+ lcr = serial_in(port, UART_LCR);
+ serial_out(port, UART_LCR, lcr | UART_LCR_DLAB);
+ dll = serial_in(port, UART_DLL);
+ dlm = serial_in(port, UART_DLM);
+ serial_out(port, UART_LCR, lcr);
+
+ quot = (dlm << 8) | dll;
+ baud = BASE_BAUD / quot;
+ return baud;
+}
+
+static void __init serial8250_early_init(struct uart_8250_port *port, unsigned int baud)
+{
+ unsigned char c;
+ unsigned int divisor;
+
+ serial_out(port, UART_LCR, 0x3); /* 8n1 */
+ serial_out(port, UART_IER, 0); /* no interrupt */
+ serial_out(port, UART_FCR, 0); /* no fifo */
+ serial_out(port, UART_MCR, 0x3); /* DTR + RTS */
+
+ divisor = 115200 / baud;
+ c = serial_in(port, UART_LCR);
+ serial_out(port, UART_LCR, c | UART_LCR_DLAB);
+ serial_out(port, UART_DLL, divisor & 0xff);
+ serial_out(port, UART_DLM, (divisor >> 8) & 0xff);
+ serial_out(port, UART_LCR, c & ~UART_LCR_DLAB);
+}
+
+void __init serial8250_early_console_setup(struct uart_port *port, char *options)
+{
+ unsigned int baud = 9600;
+ static char probed_options[16];
+
+ serial8250_early_port.port = *port;
+ if (options) {
+ serial8250_early_options = options;
+ baud = simple_strtoul(options, 0, 0);
+ } else {
+ baud = serial8250_probe_baud(&serial8250_early_port);
+ snprintf(probed_options, sizeof(probed_options), "%u", baud);
+ serial8250_early_options = probed_options;
+ }
+ serial8250_early_init(&serial8250_early_port, baud);
+ printk("Early serial console at %s 0x%lx (options %s)\n",
+ port->iotype == SERIAL_IO_MEM ? "MMIO" : "I/O port",
+ port->iotype == SERIAL_IO_MEM ? (unsigned long) port->mapbase :
+ (unsigned long) port->iobase,
+ serial8250_early_options);
+ register_console(&serial8250_early_console);
+}
+
+static int __init serial8250_early_device(struct uart_port *port)
+{
+ struct uart_port *early = &serial8250_early_port.port;
+
+ if (early->iotype == port->iotype &&
+ early->iobase == port->iobase &&
+ early->membase == port->membase)
+ return 1;
+ return 0;
+}
+
+static int __init serial8250_start_console(char *options)
+{
+ int line;
+ struct uart_port *port;
+
+ for (line = 0; line < UART_NR; line++) {
+ port = &serial8250_ports[line].port;
+ if (serial8250_early_device(port)) {
+ add_preferred_console("ttyS", line, options);
+ break;
+ }
+ }
+ if (line == UART_NR)
+ return -ENODEV;
+
+ printk("Starting serial console on ttyS%d at %s 0x%lx (options %s)\n",
+ line,
+ port->iotype == SERIAL_IO_MEM ? "MMIO" : "I/O port",
+ port->iotype == SERIAL_IO_MEM ? (unsigned long) port->mapbase :
+ (unsigned long) port->iobase,
+ options);
+ if (!(serial8250_console.flags & CON_ENABLED))
+ register_console(&serial8250_console);
+ return line;
+}
+
+static int __init serial8250_early_console_switch(void)
+{
+ struct uart_port *port = &serial8250_early_port.port;
+ int line;
+
+ if (!(serial8250_early_console.flags & CON_ENABLED))
+ return 0;
+
+ /* Try to start the normal driver on a matching line. */
+ line = serial8250_start_console(serial8250_early_options);
+ if (line < 0)
+ printk("No ttyS device at %s 0x%lx for console\n",
+ port->iotype == SERIAL_IO_MEM ? "MMIO" : "I/O port",
+ port->iotype == SERIAL_IO_MEM ?
+ (unsigned long) port->mapbase :
+ (unsigned long) port->iobase);
+
+ unregister_console(&serial8250_early_console);
+ if (line >= 0)
+ if (port->iotype == SERIAL_IO_MEM)
+ iounmap(port->membase);
+
+ return 0;
+}
+late_initcall(serial8250_early_console_switch);

#define SERIAL8250_CONSOLE &serial8250_console
#else
===== drivers/serial/8250_hcdp.c 1.3 vs edited =====
--- 1.3/drivers/serial/8250_hcdp.c Mon Mar 15 15:53:32 2004
+++ edited/drivers/serial/8250_hcdp.c Thu Apr 1 14:31:04 2004
@@ -1,8 +1,9 @@
/*
- * linux/drivers/char/hcdp_serial.c
+ * EFI HCDP support
*
- * Copyright (C) 2002 Hewlett-Packard Co.
+ * Copyright (C) 2002, 2004 Hewlett-Packard Co.
* Khalid Aziz <[email protected]>
+ * Bjorn Helgaas <[email protected]>
*
* Parse the EFI HCDP table to locate serial console and debug ports and
* initialize them.
@@ -10,19 +11,19 @@
* 2002/08/29 davidm Adjust it to new 2.5 serial driver infrastructure.
*/

-#include <linux/config.h>
+#include <linux/acpi.h>
+#include <linux/console.h>
#include <linux/kernel.h>
#include <linux/efi.h>
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
+#include <linux/string.h>
#include <linux/types.h>
-#include <linux/acpi.h>

#include <asm/io.h>
#include <asm/serial.h>
-#include <asm/acpi.h>

#include "8250_hcdp.h"

@@ -203,6 +204,50 @@
#ifdef SERIAL_DEBUG_HCDP
printk("Leaving setup_serial_hcdp()\n");
#endif
+}
+
+void __init
+setup_hcdp_console(hcdp_t *hcdp, char *cmdline)
+{
+ hcdp_dev_t *dev;
+ unsigned long iobase;
+ struct uart_port port;
+ static char options[16];
+ int i;
+
+ if (!hcdp)
+ return;
+
+ /*
+ * The presence of an HCDP entry does not imply that we should
+ * use a serial console.
+ */
+ if (!strstr(cmdline, "console=serial"))
+ return;
+
+ memset(&port, 0, sizeof(port));
+
+ for (i = 0; i < hcdp->num_entries; i++) {
+ dev = hcdp->hcdp_dev + i;
+ if (dev->type != HCDP_DEV_CONSOLE)
+ continue;
+
+ iobase = (u64) dev->base_addr.addrhi << 32 | dev->base_addr.addrlo;
+ if (dev->base_addr.space_id == ACPI_MEM_SPACE) {
+ port.mapbase = iobase;
+ port.membase = ioremap(iobase, 64);
+ port.iotype = SERIAL_IO_MEM;
+ } else if (dev->base_addr.space_id == ACPI_IO_SPACE) {
+ port.iobase = iobase;
+ port.iotype = SERIAL_IO_PORT;
+ } else
+ return;
+
+ snprintf(options, sizeof(options), "%lun%d", dev->baud,
+ dev->bits ? dev->bits : 8);
+ serial8250_early_console_setup(&port, options);
+ return;
+ }
}

#ifdef CONFIG_IA64_EARLY_PRINTK_UART
===== include/linux/serial.h 1.11 vs edited =====
--- 1.11/include/linux/serial.h Thu Feb 26 04:26:01 2004
+++ edited/include/linux/serial.h Thu Apr 1 14:31:05 2004
@@ -181,6 +181,7 @@
/* Allow architectures to override entries in serial8250_ports[] at run time: */
struct uart_port; /* forward declaration */
extern int early_serial_setup(struct uart_port *port);
+extern void serial8250_early_console_setup(struct uart_port *port, char *options);

#endif /* __KERNEL__ */
#endif /* _LINUX_SERIAL_H */


2004-04-01 22:25:11

by Bjorn Helgaas

[permalink] [raw]
Subject: Re: [PATCH] early serial console support

This updates ia64 to use the early serial console in the previous
patch. It depends on that patch, so please don't apply it unless/
until that is applied.

The benefits to ia64 are:
- /dev/ttyS<N> naming is now independent of any EFI console
configuration or "console=" arguments.
- Serial console can work a little earlier because it no longer
depends on ACPI for interrupt registration.
- It probably works early enough to obsolete the EARLY_PRINTK
stuff.

"console=serial" means use the first device described in the HCDP (or
COM1 at I/O port 0x3f8 if no HCDP) as the early and normal console.
The baud rate is obtained from the HCDP or probed from the UART if
not specified.

"console=ttyS<N>" means use ttyS<N> as the console. There will be no
early console. The baud rate must be specified unless it is 9600.

Gotchas:
- Old kernels don't understand "console=serial", so elilo.conf
changes are needed if you want an early console.
- Old kernels named ttyS devices in different orders, depending on
which one was selected as the EFI console device, so you may
need to add or change a getty entry in /etc/inittab.

For example, a machine with a built-in serial port plus an MP might
have these ports:

old, EFI old, EFI new, EFI
MMIO console console console
address on builtin on MP anywhere
---------- --------- -------- --------
builtin 0xff5e0000 ttyS0 ttyS1 ttyS0
MP UPS 0xf8031000 ttyS1 ttyS2 ttyS1
MP Console 0xf8030000 ttyS2 ttyS0 ttyS2
MP 2 0xf8030010 ttyS3 ttyS3 ttyS3
MP 3 0xf8030038 ttyS4 ttyS4 ttyS4

If you're using the MP console port (the port labelled "console" on
the 3-headed cable), it used to be /dev/ttyS0, but is now /dev/ttyS2.

Troubleshooting:
- No kernel output after "Uncompressing Linux... done":
-> You're using an MP port as the console and specified
"console=ttyS0". This port is now named something else.
Use "console=serial" instead.
-> Multiple UARTs selected as EFI console devices, and you're
looking at the wrong one. Make sure only one UART is
selected (use the Boot Manager "Boot option maintenance"
menu).

- Long pause (60+ seconds) between "Uncompressing Linux... done"
and start of kernel output:
-> No early console, probably because you used "console=ttyS0".
Replace it with "console=serial".

- Kernel and init script output is fine, but no "login:" prompt:
-> Missing getty entry in /etc/inittab. Add the appropriate
entry based on the kernel "Starting serial console on
ttyS<N>" message.

- "login:" prompt, but can't login as root:
-> Add entry to /etc/securetty for console tty.


===== arch/ia64/kernel/setup.c 1.70 vs edited =====
--- 1.70/arch/ia64/kernel/setup.c Wed Mar 17 05:46:59 2004
+++ edited/arch/ia64/kernel/setup.c Thu Apr 1 12:39:14 2004
@@ -263,20 +263,34 @@

#ifdef CONFIG_SERIAL_8250_CONSOLE
static void __init
-setup_serial_legacy (void)
+setup_serial_legacy (char *cmdline)
{
+ static char buf[32];
+ char *options, *space;
struct uart_port port;
- unsigned int i, iobase[] = {0x3f8, 0x2f8};

- printk(KERN_INFO "Registering legacy COM ports for serial console\n");
+ if (!strstr(cmdline, "console=serial"))
+ return;
+
+ /*
+ * We have no idea where the console UART is, but the
+ * user explicitly requested it, so assume it's COM1.
+ */
memset(&port, 0, sizeof(port));
port.iotype = SERIAL_IO_PORT;
- port.uartclk = BASE_BAUD * 16;
- for (i = 0; i < ARRAY_SIZE(iobase); i++) {
- port.line = i;
- port.iobase = iobase[i];
- early_serial_setup(&port);
+ port.iobase = 0x3f8;
+
+ options = strstr(cmdline, "console=serial,");
+ if (options) {
+ options += 15; // strlen("console=serial,")
+ strlcpy(buf, options, sizeof(buf));
+ space = strchr(buf, ' ');
+ if (space)
+ *space = 0;
+ options = buf;
}
+
+ serial8250_early_console_setup(&port, options);
}
#endif

@@ -297,6 +311,17 @@
machvec_init(acpi_get_sysname());
#endif

+#ifdef CONFIG_SERIAL_8250_CONSOLE
+#ifdef CONFIG_SERIAL_8250_HCDP
+ if (efi.hcdp) {
+ extern void setup_hcdp_console(void *, char *);
+ setup_hcdp_console(efi.hcdp, *cmdline_p);
+ }
+#endif
+ if (!efi.hcdp)
+ setup_serial_legacy(*cmdline_p);
+#endif
+
#ifdef CONFIG_ACPI_BOOT
/* Initialize the ACPI boot-time table parser */
acpi_table_init();
@@ -322,26 +347,6 @@

#ifdef CONFIG_ACPI_BOOT
acpi_boot_init();
-#endif
-#ifdef CONFIG_SERIAL_8250_CONSOLE
-#ifdef CONFIG_SERIAL_8250_HCDP
- if (efi.hcdp) {
- void setup_serial_hcdp(void *);
- setup_serial_hcdp(efi.hcdp);
- }
-#endif
- /*
- * Without HCDP, we won't discover any serial ports until the serial driver looks
- * in the ACPI namespace. If ACPI claims there are some legacy devices, register
- * the legacy COM ports so serial console works earlier. This is slightly dangerous
- * because we don't *really* know whether there's anything there, but we hope that
- * all new boxes will implement HCDP.
- */
- {
- extern unsigned char acpi_legacy_devices;
- if (!efi.hcdp && acpi_legacy_devices)
- setup_serial_legacy();
- }
#endif

#ifdef CONFIG_VT

2004-04-02 17:18:38

by Bjorn Helgaas

[permalink] [raw]
Subject: Re: [PATCH] early serial console support (updated ia64 part)

Here's an update to the ia64 part of my early serial console patch.
The only change is to supply BASE_BAUD * 16 as the uartclk for
the COM1 port.


Changelog text:

This updates ia64 to use the early serial console support.

The benefits to ia64 are:
- /dev/ttyS<N> naming is now independent of any EFI console
configuration or "console=" arguments.
- Serial console can work a little earlier because it no longer
depends on ACPI for interrupt registration.
- It probably works early enough to obsolete the EARLY_PRINTK
stuff.

"console=serial" means use the first device described in the HCDP (or
COM1 at I/O port 0x3f8 if no HCDP) as the early and normal console.
The baud rate is obtained from the HCDP or probed from the UART if
not specified.

"console=ttyS<N>" means use ttyS<N> as the console. There will be no
early console. The baud rate must be specified unless it is 9600.

Gotchas:
- Old kernels don't understand "console=serial", so elilo.conf
changes are needed if you want an early console.
- Old kernels named ttyS devices in different orders, depending on
which one was selected as the EFI console device, so you may
need to add or change a getty entry in /etc/inittab.

For example, a machine with a built-in serial port plus an MP might
have these ports:

old, EFI old, EFI new, EFI
MMIO console console console
address on builtin on MP anywhere
---------- --------- -------- --------
builtin 0xff5e0000 ttyS0 ttyS1 ttyS0
MP UPS 0xf8031000 ttyS1 ttyS2 ttyS1
MP Console 0xf8030000 ttyS2 ttyS0 ttyS2
MP 2 0xf8030010 ttyS3 ttyS3 ttyS3
MP 3 0xf8030038 ttyS4 ttyS4 ttyS4

If you're using the MP console port (the port labelled "console" on
the 3-headed cable), it used to be /dev/ttyS0, but is now /dev/ttyS2.

Troubleshooting:
- No kernel output after "Uncompressing Linux... done":
-> You're using an MP port as the console and specified
"console=ttyS0". This port is now named something else.
Use "console=serial" instead.
-> Multiple UARTs selected as EFI console devices, and you're
looking at the wrong one. Make sure only one UART is
selected (use the Boot Manager "Boot option maintenance"
menu).

- Long pause (60+ seconds) between "Uncompressing Linux... done"
and start of kernel output:
-> No early console, probably because you used "console=ttyS0".
Replace it with "console=serial".

- Kernel and init script output is fine, but no "login:" prompt:
-> Missing getty entry in /etc/inittab. Add the appropriate
entry based on the kernel "Starting serial console on
ttyS<N>" message.

- "login:" prompt, but can't login as root:
-> Add entry to /etc/securetty for console tty.


===== arch/ia64/kernel/setup.c 1.70 vs edited =====
--- 1.70/arch/ia64/kernel/setup.c Wed Mar 17 05:46:59 2004
+++ edited/arch/ia64/kernel/setup.c Fri Apr 2 09:42:47 2004
@@ -263,20 +263,35 @@

#ifdef CONFIG_SERIAL_8250_CONSOLE
static void __init
-setup_serial_legacy (void)
+setup_serial_legacy (char *cmdline)
{
+ static char buf[32];
+ char *options, *space;
struct uart_port port;
- unsigned int i, iobase[] = {0x3f8, 0x2f8};

- printk(KERN_INFO "Registering legacy COM ports for serial console\n");
+ if (!strstr(cmdline, "console=serial"))
+ return;
+
+ /*
+ * We have no idea where the console UART is, but the
+ * user explicitly requested it, so assume it's COM1.
+ */
memset(&port, 0, sizeof(port));
port.iotype = SERIAL_IO_PORT;
+ port.iobase = 0x3f8;
port.uartclk = BASE_BAUD * 16;
- for (i = 0; i < ARRAY_SIZE(iobase); i++) {
- port.line = i;
- port.iobase = iobase[i];
- early_serial_setup(&port);
+
+ options = strstr(cmdline, "console=serial,");
+ if (options) {
+ options += 15; // strlen("console=serial,")
+ strlcpy(buf, options, sizeof(buf));
+ space = strchr(buf, ' ');
+ if (space)
+ *space = 0;
+ options = buf;
}
+
+ serial8250_early_console_setup(&port, options);
}
#endif

@@ -297,6 +312,17 @@
machvec_init(acpi_get_sysname());
#endif

+#ifdef CONFIG_SERIAL_8250_CONSOLE
+#ifdef CONFIG_SERIAL_8250_HCDP
+ if (efi.hcdp) {
+ extern void setup_hcdp_console(void *, char *);
+ setup_hcdp_console(efi.hcdp, *cmdline_p);
+ }
+#endif
+ if (!efi.hcdp)
+ setup_serial_legacy(*cmdline_p);
+#endif
+
#ifdef CONFIG_ACPI_BOOT
/* Initialize the ACPI boot-time table parser */
acpi_table_init();
@@ -322,26 +348,6 @@

#ifdef CONFIG_ACPI_BOOT
acpi_boot_init();
-#endif
-#ifdef CONFIG_SERIAL_8250_CONSOLE
-#ifdef CONFIG_SERIAL_8250_HCDP
- if (efi.hcdp) {
- void setup_serial_hcdp(void *);
- setup_serial_hcdp(efi.hcdp);
- }
-#endif
- /*
- * Without HCDP, we won't discover any serial ports until the serial driver looks
- * in the ACPI namespace. If ACPI claims there are some legacy devices, register
- * the legacy COM ports so serial console works earlier. This is slightly dangerous
- * because we don't *really* know whether there's anything there, but we hope that
- * all new boxes will implement HCDP.
- */
- {
- extern unsigned char acpi_legacy_devices;
- if (!efi.hcdp && acpi_legacy_devices)
- setup_serial_legacy();
- }
#endif

#ifdef CONFIG_VT