Skip to content

Commit

Permalink
Specify PCI based UART for earlyprintk
Browse files Browse the repository at this point in the history
Add support for specifying PCI based UARTs for earlyprintk
using a syntax like "earlyprintk=pciserial,00:18.1,115200",
where 00:18.1 is the BDF of a UART device.

[Slightly tidied from Stuart's original patch]
Signed-off-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Stuart R. Anderson authored and Greg Kroah-Hartman committed Feb 2, 2015
1 parent d2b6f44 commit ea9e9d8
Showing 1 changed file with 166 additions and 16 deletions.
182 changes: 166 additions & 16 deletions arch/x86/kernel/early_printk.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <linux/usb/ehci_def.h>
#include <linux/efi.h>
#include <asm/efi.h>
#include <asm/pci_x86.h>

/* Simple VGA output */
#define VGABASE (__ISA_IO_base + 0xb8000)
Expand Down Expand Up @@ -76,7 +77,7 @@ static struct console early_vga_console = {

/* Serial functions loosely based on a similar package from Klaus P. Gerlicher */

static int early_serial_base = 0x3f8; /* ttyS0 */
static unsigned long early_serial_base = 0x3f8; /* ttyS0 */

#define XMTRDY 0x20

Expand All @@ -94,13 +95,40 @@ static int early_serial_base = 0x3f8; /* ttyS0 */
#define DLL 0 /* Divisor Latch Low */
#define DLH 1 /* Divisor latch High */

static void mem32_serial_out(unsigned long addr, int offset, int value)
{
uint32_t *vaddr = (uint32_t *)addr;
/* shift implied by pointer type */
writel(value, vaddr + offset);
}

static unsigned int mem32_serial_in(unsigned long addr, int offset)
{
uint32_t *vaddr = (uint32_t *)addr;
/* shift implied by pointer type */
return readl(vaddr + offset);
}

static unsigned int io_serial_in(unsigned long addr, int offset)
{
return inb(addr + offset);
}

static void io_serial_out(unsigned long addr, int offset, int value)
{
outb(value, addr + offset);
}

static unsigned int (*serial_in)(unsigned long addr, int offset) = io_serial_in;
static void (*serial_out)(unsigned long addr, int offset, int value) = io_serial_out;

static int early_serial_putc(unsigned char ch)
{
unsigned timeout = 0xffff;

while ((inb(early_serial_base + LSR) & XMTRDY) == 0 && --timeout)
while ((serial_in(early_serial_base, LSR) & XMTRDY) == 0 && --timeout)
cpu_relax();
outb(ch, early_serial_base + TXR);
serial_out(early_serial_base, TXR, ch);
return timeout ? 0 : -1;
}

Expand All @@ -114,13 +142,28 @@ static void early_serial_write(struct console *con, const char *s, unsigned n)
}
}

static __init void early_serial_hw_init(unsigned divisor)
{
unsigned char c;

serial_out(early_serial_base, LCR, 0x3); /* 8n1 */
serial_out(early_serial_base, IER, 0); /* no interrupt */
serial_out(early_serial_base, FCR, 0); /* no fifo */
serial_out(early_serial_base, MCR, 0x3); /* DTR + RTS */

c = serial_in(early_serial_base, LCR);
serial_out(early_serial_base, LCR, c | DLAB);
serial_out(early_serial_base, DLL, divisor & 0xff);
serial_out(early_serial_base, DLH, (divisor >> 8) & 0xff);
serial_out(early_serial_base, LCR, c & ~DLAB);
}

#define DEFAULT_BAUD 9600

static __init void early_serial_init(char *s)
{
unsigned char c;
unsigned divisor;
unsigned baud = DEFAULT_BAUD;
unsigned long baud = DEFAULT_BAUD;
char *e;

if (*s == ',')
Expand All @@ -145,24 +188,124 @@ static __init void early_serial_init(char *s)
s++;
}

outb(0x3, early_serial_base + LCR); /* 8n1 */
outb(0, early_serial_base + IER); /* no interrupt */
outb(0, early_serial_base + FCR); /* no fifo */
outb(0x3, early_serial_base + MCR); /* DTR + RTS */
if (*s) {
if (kstrtoul(s, 0, &baud) < 0 || baud == 0)
baud = DEFAULT_BAUD;
}

/* Convert from baud to divisor value */
divisor = 115200 / baud;

/* These will always be IO based ports */
serial_in = io_serial_in;
serial_out = io_serial_out;

/* Set up the HW */
early_serial_hw_init(divisor);
}

#ifdef CONFIG_PCI
/*
* early_pci_serial_init()
*
* This function is invoked when the early_printk param starts with "pciserial"
* The rest of the param should be ",B:D.F,baud" where B, D & F describe the
* location of a PCI device that must be a UART device.
*/
static __init void early_pci_serial_init(char *s)
{
unsigned divisor;
unsigned long baud = DEFAULT_BAUD;
u8 bus, slot, func;
uint32_t classcode, bar0;
uint16_t cmdreg;
char *e;


/*
* First, part the param to get the BDF values
*/
if (*s == ',')
++s;

if (*s == 0)
return;

bus = (u8)simple_strtoul(s, &e, 16);
s = e;
if (*s != ':')
return;
++s;
slot = (u8)simple_strtoul(s, &e, 16);
s = e;
if (*s != '.')
return;
++s;
func = (u8)simple_strtoul(s, &e, 16);
s = e;

/* A baud might be following */
if (*s == ',')
s++;

/*
* Second, find the device from the BDF
*/
cmdreg = read_pci_config(bus, slot, func, PCI_COMMAND);
classcode = read_pci_config(bus, slot, func, PCI_CLASS_REVISION);
bar0 = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_0);

/*
* Verify it is a UART type device
*/
if (((classcode >> 16 != PCI_CLASS_COMMUNICATION_MODEM) &&
(classcode >> 16 != PCI_CLASS_COMMUNICATION_SERIAL)) ||
(((classcode >> 8) & 0xff) != 0x02)) /* 16550 I/F at BAR0 */
return;

/*
* Determine if it is IO or memory mapped
*/
if (bar0 & 0x01) {
/* it is IO mapped */
serial_in = io_serial_in;
serial_out = io_serial_out;
early_serial_base = bar0&0xfffffffc;
write_pci_config(bus, slot, func, PCI_COMMAND,
cmdreg|PCI_COMMAND_IO);
} else {
/* It is memory mapped - assume 32-bit alignment */
serial_in = mem32_serial_in;
serial_out = mem32_serial_out;
/* WARNING! assuming the address is always in the first 4G */
early_serial_base =
(unsigned long)early_ioremap(bar0 & 0xfffffff0, 0x10);
write_pci_config(bus, slot, func, PCI_COMMAND,
cmdreg|PCI_COMMAND_MEMORY);
}

/*
* Lastly, initalize the hardware
*/
if (*s) {
baud = simple_strtoul(s, &e, 0);
if (baud == 0 || s == e)
if (strcmp(s, "nocfg") == 0)
/* Sometimes, we want to leave the UART alone
* and assume the BIOS has set it up correctly.
* "nocfg" tells us this is the case, and we
* should do no more setup.
*/
return;
if (kstrtoul(s, 0, &baud) < 0 || baud == 0)
baud = DEFAULT_BAUD;
}

/* Convert from baud to divisor value */
divisor = 115200 / baud;
c = inb(early_serial_base + LCR);
outb(c | DLAB, early_serial_base + LCR);
outb(divisor & 0xff, early_serial_base + DLL);
outb((divisor >> 8) & 0xff, early_serial_base + DLH);
outb(c & ~DLAB, early_serial_base + LCR);

/* Set up the HW */
early_serial_hw_init(divisor);
}
#endif

static struct console early_serial_console = {
.name = "earlyser",
Expand Down Expand Up @@ -210,6 +353,13 @@ static int __init setup_early_printk(char *buf)
early_serial_init(buf + 4);
early_console_register(&early_serial_console, keep);
}
#ifdef CONFIG_PCI
if (!strncmp(buf, "pciserial", 9)) {
early_pci_serial_init(buf + 9);
early_console_register(&early_serial_console, keep);
buf += 9; /* Keep from match the above "serial" */
}
#endif
if (!strncmp(buf, "vga", 3) &&
boot_params.screen_info.orig_video_isVGA == 1) {
max_xpos = boot_params.screen_info.orig_video_cols;
Expand Down

0 comments on commit ea9e9d8

Please sign in to comment.