Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 內核控制台驅動實現解析:以 Samsung S3C24xx 串口為例

編輯:Linux 教學

Linux 內核早期啟動階段,printk 輸出依賴控制台驅動將字符發送到串口。本文深度剖析 drivers/serial/samsung.c 中控制台註冊流程、底層發送函數、設置回調、早期初始化掛鉤機制,揭示內核如何在內存管理、調度器就緒前實現輸出。

一、 核心數據結構 struct console

static 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 驅動
};

二、 字符發送鏈路

1. 最底層發送:s3c24xx_serial_console_putchar

static 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);
}

2. 發送就緒判斷:s3c24xx_serial_console_txrdy

static 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;
    }
}

3. 字符串輸出入口:s3c24xx_serial_console_write

static 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_options

static 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));
}

四、 註冊流程與早期初始化

1. 控制台註冊入口:s3c24xx_serial_initconsole

int 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;
}

2. 宏自動生成初始化調用: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 段

3. 內核早期調用鏈

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 在驅動模塊加載前即可輸出。

五、 關鍵設計點總結

  1. 輪詢發送:控制台上下文不可睡眠、不可用中斷,必須輪詢硬件狀態寫入 THR/TX FIFO。
  2. 全局 cons_uart:setup 階段綁定具體端口,供 putchar 直接使用,避免傳遞 port 參數開銷。
  3. 參數回退機制:無命令行參數時讀取硬件寄存器現值,保證早期啟動輸出不亂碼。
  4. 緩存刷新:CON_PRINTBUFFER 標志使 register_console() 自動調用 console_unlock() 輸出 log_buf 積累的早期內核日誌。
  5. 多端口支持:index=-1 配合 console= 參數動態選擇,s3c24xx_serial_init_ports() 批量初始化所有 UART。
Copyright © Windows教程網 All Rights Reserved