early_printk.c 9.89 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
Thomas Gleixner's avatar
Thomas Gleixner committed
2 3 4 5 6
#include <linux/console.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/screen_info.h>
7 8 9 10
#include <linux/usb/ch9.h>
#include <linux/pci_regs.h>
#include <linux/pci_ids.h>
#include <linux/errno.h>
Thomas Gleixner's avatar
Thomas Gleixner committed
11 12 13
#include <asm/io.h>
#include <asm/processor.h>
#include <asm/fcntl.h>
14
#include <asm/setup.h>
Thomas Gleixner's avatar
Thomas Gleixner committed
15
#include <xen/hvc-console.h>
16 17
#include <asm/pci-direct.h>
#include <asm/fixmap.h>
18
#include <asm/intel-mid.h>
19
#include <asm/pgtable.h>
20
#include <linux/usb/ehci_def.h>
21
#include <linux/usb/xhci-dbgp.h>
22 23
#include <linux/efi.h>
#include <asm/efi.h>
24
#include <asm/pci_x86.h>
Linus Torvalds's avatar
Linus Torvalds committed
25

Thomas Gleixner's avatar
Thomas Gleixner committed
26 27 28 29
/* Simple VGA output */
#define VGABASE		(__ISA_IO_base + 0xb8000)

static int max_ypos = 25, max_xpos = 80;
30
static int current_ypos = 25, current_xpos;
Thomas Gleixner's avatar
Thomas Gleixner committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

static void early_vga_write(struct console *con, const char *str, unsigned n)
{
	char c;
	int  i, k, j;

	while ((c = *str++) != '\0' && n-- > 0) {
		if (current_ypos >= max_ypos) {
			/* scroll 1 line up */
			for (k = 1, j = 0; k < max_ypos; k++, j++) {
				for (i = 0; i < max_xpos; i++) {
					writew(readw(VGABASE+2*(max_xpos*k+i)),
					       VGABASE + 2*(max_xpos*j + i));
				}
			}
			for (i = 0; i < max_xpos; i++)
				writew(0x720, VGABASE + 2*(max_xpos*j + i));
			current_ypos = max_ypos-1;
		}
50 51 52 53 54 55 56 57
#ifdef CONFIG_KGDB_KDB
		if (c == '\b') {
			if (current_xpos > 0)
				current_xpos--;
		} else if (c == '\r') {
			current_xpos = 0;
		} else
#endif
Thomas Gleixner's avatar
Thomas Gleixner committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
		if (c == '\n') {
			current_xpos = 0;
			current_ypos++;
		} else if (c != '\r')  {
			writew(((0x7 << 8) | (unsigned short) c),
			       VGABASE + 2*(max_xpos*current_ypos +
						current_xpos++));
			if (current_xpos >= max_xpos) {
				current_xpos = 0;
				current_ypos++;
			}
		}
	}
}

static struct console early_vga_console = {
	.name =		"earlyvga",
	.write =	early_vga_write,
	.flags =	CON_PRINTBUFFER,
	.index =	-1,
};

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

82
static unsigned long early_serial_base = 0x3f8;  /* ttyS0 */
Thomas Gleixner's avatar
Thomas Gleixner committed
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

#define XMTRDY          0x20

#define DLAB		0x80

#define TXR             0       /*  Transmit register (WRITE) */
#define RXR             0       /*  Receive register  (READ)  */
#define IER             1       /*  Interrupt Enable          */
#define IIR             2       /*  Interrupt ID              */
#define FCR             2       /*  FIFO control              */
#define LCR             3       /*  Line control              */
#define MCR             4       /*  Modem control             */
#define LSR             5       /*  Line Status               */
#define MSR             6       /*  Modem Status              */
#define DLL             0       /*  Divisor Latch Low         */
#define DLH             1       /*  Divisor latch High        */

100 101 102 103 104 105 106 107 108 109 110 111 112
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;

Thomas Gleixner's avatar
Thomas Gleixner committed
113 114 115
static int early_serial_putc(unsigned char ch)
{
	unsigned timeout = 0xffff;
116

117
	while ((serial_in(early_serial_base, LSR) & XMTRDY) == 0 && --timeout)
Thomas Gleixner's avatar
Thomas Gleixner committed
118
		cpu_relax();
119
	serial_out(early_serial_base, TXR, ch);
Thomas Gleixner's avatar
Thomas Gleixner committed
120 121 122 123 124 125 126 127 128 129 130 131 132
	return timeout ? 0 : -1;
}

static void early_serial_write(struct console *con, const char *s, unsigned n)
{
	while (*s && n-- > 0) {
		if (*s == '\n')
			early_serial_putc('\r');
		early_serial_putc(*s);
		s++;
	}
}

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
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);
}

Thomas Gleixner's avatar
Thomas Gleixner committed
149 150 151 152 153
#define DEFAULT_BAUD 9600

static __init void early_serial_init(char *s)
{
	unsigned divisor;
154
	unsigned long baud = DEFAULT_BAUD;
Thomas Gleixner's avatar
Thomas Gleixner committed
155 156 157 158 159 160 161
	char *e;

	if (*s == ',')
		++s;

	if (*s) {
		unsigned port;
162
		if (!strncmp(s, "0x", 2)) {
Thomas Gleixner's avatar
Thomas Gleixner committed
163 164
			early_serial_base = simple_strtoul(s, &e, 16);
		} else {
165
			static const int __initconst bases[] = { 0x3f8, 0x2f8 };
Thomas Gleixner's avatar
Thomas Gleixner committed
166

167
			if (!strncmp(s, "ttyS", 4))
Thomas Gleixner's avatar
Thomas Gleixner committed
168 169 170 171 172 173 174 175 176 177 178
				s += 4;
			port = simple_strtoul(s, &e, 10);
			if (port > 1 || s == e)
				port = 0;
			early_serial_base = bases[port];
		}
		s += strcspn(s, ",");
		if (*s == ',')
			s++;
	}

179
	if (*s) {
180 181 182
		baud = simple_strtoull(s, &e, 0);

		if (baud == 0 || s == e)
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
			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
198 199
static void mem32_serial_out(unsigned long addr, int offset, int value)
{
200
	u32 __iomem *vaddr = (u32 __iomem *)addr;
201 202 203 204 205 206
	/* shift implied by pointer type */
	writel(value, vaddr + offset);
}

static unsigned int mem32_serial_in(unsigned long addr, int offset)
{
207
	u32 __iomem *vaddr = (u32 __iomem *)addr;
208 209 210 211
	/* shift implied by pointer type */
	return readl(vaddr + offset);
}

212 213 214 215
/*
 * early_pci_serial_init()
 *
 * This function is invoked when the early_printk param starts with "pciserial"
216 217 218
 * The rest of the param should be "[force],B:D.F,baud", where B, D & F describe
 * the location of a PCI device that must be a UART device. "force" is optional
 * and overrides the use of an UART device with a wrong PCI class code.
219 220 221 222 223 224
 */
static __init void early_pci_serial_init(char *s)
{
	unsigned divisor;
	unsigned long baud = DEFAULT_BAUD;
	u8 bus, slot, func;
225 226
	u32 classcode, bar0;
	u16 cmdreg;
227
	char *e;
228
	int force = 0;
229 230 231 232 233 234 235

	if (*s == ',')
		++s;

	if (*s == 0)
		return;

236 237 238 239 240 241 242 243 244
	/* Force the use of an UART device with wrong class code */
	if (!strncmp(s, "force,", 6)) {
		force = 1;
		s += 6;
	}

	/*
	 * Part the param to get the BDF values
	 */
245 246 247 248 249 250 251 252 253 254 255 256
	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;
Thomas Gleixner's avatar
Thomas Gleixner committed
257

258 259 260 261 262
	/* A baud might be following */
	if (*s == ',')
		s++;

	/*
263
	 * Find the device from the BDF
264 265 266 267 268 269 270 271 272 273
	 */
	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)) ||
274 275 276 277
	   (((classcode >> 8) & 0xff) != 0x02)) /* 16550 I/F at BAR0 */ {
		if (!force)
			return;
	}
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

	/*
	 * 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);
	}

	/*
301
	 * Initialize the hardware
302
	 */
Thomas Gleixner's avatar
Thomas Gleixner committed
303
	if (*s) {
304 305 306 307 308 309 310 311
		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)
Thomas Gleixner's avatar
Thomas Gleixner committed
312 313 314
			baud = DEFAULT_BAUD;
	}

315
	/* Convert from baud to divisor value */
Thomas Gleixner's avatar
Thomas Gleixner committed
316
	divisor = 115200 / baud;
317 318 319

	/* Set up the HW */
	early_serial_hw_init(divisor);
Thomas Gleixner's avatar
Thomas Gleixner committed
320
}
321
#endif
Thomas Gleixner's avatar
Thomas Gleixner committed
322 323 324 325 326 327 328 329

static struct console early_serial_console = {
	.name =		"earlyser",
	.write =	early_serial_write,
	.flags =	CON_PRINTBUFFER,
	.index =	-1,
};

330
static void early_console_register(struct console *con, int keep_early)
331
{
332
	if (con->index != -1) {
333 334 335 336
		printk(KERN_CRIT "ERROR: earlyprintk= %s already used\n",
		       con->name);
		return;
	}
337 338 339 340 341 342 343
	early_console = con;
	if (keep_early)
		early_console->flags &= ~CON_BOOT;
	else
		early_console->flags |= CON_BOOT;
	register_console(early_console);
}
Thomas Gleixner's avatar
Thomas Gleixner committed
344 345 346

static int __init setup_early_printk(char *buf)
{
347
	int keep;
348

Thomas Gleixner's avatar
Thomas Gleixner committed
349 350 351
	if (!buf)
		return 0;

352
	if (early_console)
Thomas Gleixner's avatar
Thomas Gleixner committed
353 354
		return 0;

355 356 357 358
	keep = (strstr(buf, "keep") != NULL);

	while (*buf != '\0') {
		if (!strncmp(buf, "serial", 6)) {
359 360
			buf += 6;
			early_serial_init(buf);
361
			early_console_register(&early_serial_console, keep);
362 363
			if (!strncmp(buf, ",ttyS", 5))
				buf += 5;
364 365 366 367 368
		}
		if (!strncmp(buf, "ttyS", 4)) {
			early_serial_init(buf + 4);
			early_console_register(&early_serial_console, keep);
		}
369 370 371 372 373 374 375
#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
376 377 378 379 380 381 382
		if (!strncmp(buf, "vga", 3) &&
		    boot_params.screen_info.orig_video_isVGA == 1) {
			max_xpos = boot_params.screen_info.orig_video_cols;
			max_ypos = boot_params.screen_info.orig_video_lines;
			current_ypos = boot_params.screen_info.orig_y;
			early_console_register(&early_vga_console, keep);
		}
383
#ifdef CONFIG_EARLY_PRINTK_DBGP
384 385
		if (!strncmp(buf, "dbgp", 4) && !early_dbgp_init(buf + 4))
			early_console_register(&early_dbgp_console, keep);
386
#endif
Thomas Gleixner's avatar
Thomas Gleixner committed
387
#ifdef CONFIG_HVC_XEN
388 389
		if (!strncmp(buf, "xen", 3))
			early_console_register(&xenboot_console, keep);
390
#endif
391 392 393 394
#ifdef CONFIG_EARLY_PRINTK_EFI
		if (!strncmp(buf, "efi", 3))
			early_console_register(&early_efi_console, keep);
#endif
395 396 397 398
#ifdef CONFIG_EARLY_PRINTK_USB_XDBC
		if (!strncmp(buf, "xdbc", 4))
			early_xdbc_parse_parameter(buf + 4);
#endif
399

400
		buf++;
Thomas Gleixner's avatar
Thomas Gleixner committed
401 402 403
	}
	return 0;
}
404

Thomas Gleixner's avatar
Thomas Gleixner committed
405
early_param("earlyprintk", setup_early_printk);