一、前言 #
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}