早期日志输出

一、前言 #

linux启动过程中,还没有初始化好文件系统等,日志输出不到文件。使用printk则需要整个框架初始化好,包括锁和buffer等。这时需要一个能在最早期进行输出的方式,每个架构的cpu都实现了此方式,直接输出到芯片,通过芯片输出到外部。芯片只要上电就可以进行输出,输出方式分为串口和vga。

二、串口输出 #

串口输出就是直接将字符输出到0x3f8即可在串口上收到字符串数据,同时可以通过串口输入拿到数据。

 1// arch/x86/kernel/early_printk.c
 2/* Serial functions loosely based on a similar package from Klaus P. Gerlicher */
 3
 4static unsigned long early_serial_base = 0x3f8;  /* ttyS0 */
 5
 6#define XMTRDY          0x20
 7
 8#define DLAB		0x80
 9
10#define TXR             0       /*  Transmit register (WRITE) */
11#define RXR             0       /*  Receive register  (READ)  */
12#define IER             1       /*  Interrupt Enable          */
13#define IIR             2       /*  Interrupt ID              */
14#define FCR             2       /*  FIFO control              */
15#define LCR             3       /*  Line control              */
16#define MCR             4       /*  Modem control             */
17#define LSR             5       /*  Line Status               */
18#define MSR             6       /*  Modem Status              */
19#define DLL             0       /*  Divisor Latch Low         */
20#define DLH             1       /*  Divisor latch High        */
21
22static unsigned int io_serial_in(unsigned long addr, int offset)
23{
24	return inb(addr + offset);
25}
26
27static void io_serial_out(unsigned long addr, int offset, int value)
28{
29	outb(value, addr + offset);
30}
31
32static unsigned int (*serial_in)(unsigned long addr, int offset) = io_serial_in;
33static void (*serial_out)(unsigned long addr, int offset, int value) = io_serial_out;
34
35static int early_serial_putc(unsigned char ch)
36{
37	unsigned timeout = 0xffff;
38
39	while ((serial_in(early_serial_base, LSR) & XMTRDY) == 0 && --timeout)
40		cpu_relax();
41	serial_out(early_serial_base, TXR, ch);
42	return timeout ? 0 : -1;
43}
44
45static void early_serial_write(struct console *con, const char *s, unsigned n)
46{
47	while (*s && n-- > 0) {
48		if (*s == '\n')
49			early_serial_putc('\r');
50		early_serial_putc(*s);
51		s++;
52	}
53}

串口输出就是使用early_serial_write即可,对应的内核里面使用是注册到一个early_console里面,通过下面代码进行注册

 1// arch/x86/kernel/early_printk.c
 2static struct console early_serial_console = {
 3	.name =		"earlyser",
 4	.write =	early_serial_write,
 5	.flags =	CON_PRINTBUFFER,
 6	.index =	-1,
 7};
 8
 9static void early_console_register(struct console *con, int keep_early)
10{
11	if (con->index != -1) {
12		printk(KERN_CRIT "ERROR: earlyprintk= %s already used\n",
13		       con->name);
14		return;
15	}
16	early_console = con;
17	if (keep_early)
18		early_console->flags &= ~CON_BOOT;
19	else
20		early_console->flags |= CON_BOOT;
21	register_console(early_console);
22}
23
24static int __init setup_early_printk(char *buf)
25{
26	int keep;
27
28	if (!buf)
29		return 0;
30
31	if (early_console)
32		return 0;
33
34	keep = (strstr(buf, "keep") != NULL);
35
36	while (*buf != '\0') {
37		if (!strncmp(buf, "serial", 6)) {
38			buf += 6;
39			early_serial_init(buf);
40			early_console_register(&early_serial_console, keep);
41			if (!strncmp(buf, ",ttyS", 5))
42				buf += 5;
43		}
44		if (!strncmp(buf, "ttyS", 4)) {
45			early_serial_init(buf + 4);
46			early_console_register(&early_serial_console, keep);
47		}
48#ifdef CONFIG_PCI
49		if (!strncmp(buf, "pciserial", 9)) {
50			early_pci_serial_init(buf + 9);
51			early_console_register(&early_serial_console, keep);
52			buf += 9; /* Keep from match the above "serial" */
53		}
54#endif
55		if (!strncmp(buf, "vga", 3) &&
56		    boot_params.screen_info.orig_video_isVGA == 1) {
57			max_xpos = boot_params.screen_info.orig_video_cols;
58			max_ypos = boot_params.screen_info.orig_video_lines;
59			current_ypos = boot_params.screen_info.orig_y;
60			early_console_register(&early_vga_console, keep);
61		}
62#ifdef CONFIG_EARLY_PRINTK_DBGP
63		if (!strncmp(buf, "dbgp", 4) && !early_dbgp_init(buf + 4))
64			early_console_register(&early_dbgp_console, keep);
65#endif
66#ifdef CONFIG_HVC_XEN
67		if (!strncmp(buf, "xen", 3))
68			early_console_register(&xenboot_console, keep);
69#endif
70#ifdef CONFIG_EARLY_PRINTK_USB_XDBC
71		if (!strncmp(buf, "xdbc", 4))
72			early_xdbc_parse_parameter(buf + 4, keep);
73#endif
74
75		buf++;
76	}
77	return 0;
78}
79
80early_param("earlyprintk", setup_early_printk);

设置波特率和硬件初始化代码

 1// arch/x86/kernel/early_printk.c
 2static __init void early_serial_hw_init(unsigned divisor)
 3{
 4	unsigned char c;
 5
 6	serial_out(early_serial_base, LCR, 0x3);	/* 8n1 */
 7	serial_out(early_serial_base, IER, 0);	/* no interrupt */
 8	serial_out(early_serial_base, FCR, 0);	/* no fifo */
 9	serial_out(early_serial_base, MCR, 0x3);	/* DTR + RTS */
10
11	c = serial_in(early_serial_base, LCR);
12	serial_out(early_serial_base, LCR, c | DLAB);
13	serial_out(early_serial_base, DLL, divisor & 0xff);
14	serial_out(early_serial_base, DLH, (divisor >> 8) & 0xff);
15	serial_out(early_serial_base, LCR, c & ~DLAB);
16}
17
18#define DEFAULT_BAUD 9600
19
20static __init void early_serial_init(char *s)
21{
22	unsigned divisor;
23	unsigned long baud = DEFAULT_BAUD;
24	char *e;
25
26	if (*s == ',')
27		++s;
28
29	if (*s) {
30		unsigned port;
31		if (!strncmp(s, "0x", 2)) {
32			early_serial_base = simple_strtoul(s, &e, 16);
33		} else {
34			static const int __initconst bases[] = { 0x3f8, 0x2f8 };
35
36			if (!strncmp(s, "ttyS", 4))
37				s += 4;
38			port = simple_strtoul(s, &e, 10);
39			if (port > 1 || s == e)
40				port = 0;
41			early_serial_base = bases[port];
42		}
43		s += strcspn(s, ",");
44		if (*s == ',')
45			s++;
46	}
47
48	if (*s) {
49		baud = simple_strtoull(s, &e, 0);
50
51		if (baud == 0 || s == e)
52			baud = DEFAULT_BAUD;
53	}
54
55	/* Convert from baud to divisor value */
56	divisor = 115200 / baud;
57
58	/* These will always be IO based ports */
59	serial_in = io_serial_in;
60	serial_out = io_serial_out;
61
62	/* Set up the HW */
63	early_serial_hw_init(divisor);
64}