Linux 內核早期啟動階段,printk 輸出依賴控制台驅動將字符發送到串口。本文深度剖析 drivers/serial/samsung.c 中控制台註冊流程、底層發送函數、設置回調、早期初始化掛鉤機制,揭示內核如何在內存管理、調度器就緒前實現輸出。
struct consolestatic struct console s3c24xx_serial_console = {
.name = S3C24XX_SERIAL_NAME, // "ttySAC"
.device = uart_console_device, // /dev/console 打開時調用
.flags = CON_PRINTBUFFER, // 啟動時刷新 log_buf 緩存
.index = -1, // 由內核命令行 console= 指定
.write = s3c24xx_serial_console_write, // 輸出回調
.setup = s3c24xx_serial_console_setup, // 參數解析回調
.data = &s3c24xx_uart_drv, // 關聯 uart 驅動
};
s3c24xx_serial_console_putcharstatic void s3c24xx_serial_console_putchar(struct uart_port *port, int ch) {
unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON);
// 等待發送 FIFO 就緒(輪詢,無中斷)
while (!s3c24xx_serial_console_txrdy(port, ufcon))
barrier(); // 編譯器屏障,防止優化掉循環
// 寫入發送保持寄存器
wr_regb(cons_uart, S3C2410_UTXH, ch);
}
s3c24xx_serial_console_txrdystatic int s3c24xx_serial_console_txrdy(struct uart_port *port, unsigned int ufcon) {
struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
if (ufcon & S3C2410_UFCON_FIFOMODE) {
// FIFO 模式:檢查 TX FIFO 滿標志
unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
return (ufstat & info->tx_fifofull) ? 0 : 1;
} else {
// 非 FIFO:檢查發送緩衝區空標志
unsigned long utrstat = rd_regl(port, S3C2410_UTRSTAT);
return (utrstat & S3C2410_UTRSTAT_TXE) ? 1 : 0;
}
}
s3c24xx_serial_console_writestatic void s3c24xx_serial_console_write(struct console *co, const char *s, unsigned int count) {
// 調用通用 uart 輸出輔助,逐字符調用 putchar
uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
}
s3c24xx_serial_console_setup解析內核命令行 console=ttySAC0,115200n8 或讀取硬件寄存器推導參數:
static int __init s3c24xx_serial_console_setup(struct console *co, char *options) {
struct uart_port *port;
int baud = 9600, bits = 8, parity = 'n', flow = 'n';
// 1. 確定端口號
if (co->index == -1 || co->index >= CONFIG_SERIAL_SAMSUNG_UARTS)
co->index = 0;
port = &s3c24xx_serial_ports[co->index].port;
if (port->mapbase == 0) { // 端口未初始化,回退到 0
co->index = 0;
port = &s3c24xx_serial_ports[0].port;
}
cons_uart = port; // 全局指針供 putchar 使用
// 2. 解析參數
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
else
s3c24xx_serial_get_options(port, &baud, &parity, &bits); // 從硬件寄存器讀取
// 3. 應用設置
return uart_set_options(port, co, baud, parity, bits, flow);
}
s3c24xx_serial_get_optionsstatic void __init s3c24xx_serial_get_options(struct uart_port *port, int *baud, int *parity, int *bits) {
unsigned int ulcon = rd_regl(port, S3C2410_ULCON);
unsigned int ucon = rd_regl(port, S3C2410_UCON);
unsigned int ubrdiv = rd_regl(port, S3C2410_UBRDIV);
// 數據位
switch (ulcon & S3C2410_LCON_CSMASK) {
case S3C2410_LCON_CS5: *bits = 5; break;
case S3C2410_LCON_CS6: *bits = 6; break;
case S3C2410_LCON_CS7: *bits = 7; break;
default: *bits = 8;
}
// 校驗
switch (ulcon & S3C2410_LCON_PMASK) {
case S3C2410_LCON_PEVEN: *parity = 'e'; break;
case S3C2410_LCON_PODD: *parity = 'o'; break;
default: *parity = 'n';
}
// 波特率計算
struct clk *clk = clk_get(port->dev, clksrc.name);
unsigned long rate = clk_get_rate(clk) / clksrc.divisor;
*baud = rate / (16 * (ubrdiv + 1));
}
s3c24xx_serial_initconsoleint s3c24xx_serial_initconsole(struct platform_driver *drv, struct s3c24xx_uart_info **info) {
struct platform_device *dev = s3c24xx_uart_devs[0];
if (!dev || strcmp(dev->name, drv->driver.name) != 0)
return 0;
s3c24xx_serial_console.data = &s3c24xx_uart_drv;
s3c24xx_serial_init_ports(info); // 初始化所有 uart 端口
register_console(&s3c24xx_serial_console); // 關鍵:掛載到全局 console 鏈表
return 0;
}
s3c24xx_console_init(samsung.h)#define s3c24xx_console_init(__drv, __inf) \
static int __init s3c_serial_console_init(void) { \
struct s3c24xx_uart_info *uinfo[CONFIG_SERIAL_SAMSUNG_UARTS]; \
for (int i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++) uinfo[i] = __inf; \
return s3c24xx_serial_initconsole(__drv, uinfo); \
} \
console_initcall(s3c_serial_console_init) // 放入 .con_initcall.init 段
start_kernel()
→ console_init() // tty_io.c
→ initcall_t *call = __con_initcall_start;
→ while (call < __con_initcall_end) (*call++)();
→ s3c_serial_console_init()
→ s3c24xx_serial_initconsole()
→ register_console()
console_initcall 級別早於 device_initcall,確保 printk 在驅動模塊加載前即可輸出。
cons_uart:setup 階段綁定具體端口,供 putchar 直接使用,避免傳遞 port 參數開銷。CON_PRINTBUFFER 標志使 register_console() 自動調用 console_unlock() 輸出 log_buf 積累的早期內核日誌。index=-1 配合 console= 參數動態選擇,s3c24xx_serial_init_ports() 批量初始化所有 UART。