1. 程式人生 > >tty驅動框架分析

tty驅動框架分析

tty框架如下圖所示:

整個 uart 框架大概的樣子如上圖所示,大致可以分為四層,一層是下層我們的串列埠驅動層,它直接與硬體相接觸,我們需要填充一個 struct uart_ops 的結構體,再向上是tty核心層,在向上是線路規程,再向上是是直接和使用者空間對接的,它們每一層都有一個 Ops 結構,使用者空通過tty註冊的字元裝置節點來訪問,這麼說來如上圖所示涉及到了4個 ops 結構了,層層跳轉。其中我們想要新增一個驅動時,主要完成的工作就是底層驅動,其他三層核心已經實現。下面,就來分析分析它們的層次結構。以amba_pl011驅動為例。

使用者空間通過函式open、write等首先會呼叫到tty使用者空間的tty_open函式,然後tty_open函式會呼叫線路規程函式,然後線路規程的函式會呼叫tty核心層的函式,tty核心層的函式也可以直接呼叫tty核心層的函式(不經過線路規程),最終tty驅動層的函式在呼叫tty裝置驅動。即:

tty_open->n_tty_open->uart_open->pl011_startup或者是

tty_open->uart_open->pl011_startup

    在amba_pl011中,它是這樣來註冊串列埠驅動的,分配一個struct uart_driver 簡單填充,並呼叫uart_register_driver 註冊到核心中去。

static struct uart_driver amba_reg = {
    .owner = THIS_MODULE,
    .driver_name = "ttyAMA",
    .dev_name = "ttyAMA",
    .major = SERIAL_AMBA_MAJOR,
    .minor = SERIAL_AMBA_MINOR,
    .nr = UART_NR,
    .cons = AMBA_CONSOLE,
};
static int __init pl011_init(void)
{
    int ret;

    printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
    ret = uart_register_driver(&amba_reg);

    if (ret == 0) {
        ret = amba_driver_register(&pl011_driver);
        if (ret)
            uart_unregister_driver(&amba_reg);
    }

    return ret;

}

    uart_driver 中,我們只是填充了一些名字、裝置號等資訊,這些都是不涉及底層硬體訪問的,那是怎麼回事呢?來看一下完整的 uart_driver 結構或許就明白了。

struct uart_driver {
    struct module *owner; /* 擁有該uart_driver的模組,一般為THIS_MODULE */
    const char *driver_name; /* 串列埠驅動名,串列埠裝置檔名以驅動名為基礎 */
    const char *dev_name; /* 串列埠裝置名 */
    int  major; /* 主裝置號 */
    int  minor; /* 次裝置號 */
    int  nr; /* 該uart_driver支援的串列埠個數(最大) */
    struct console *cons; /* 其對應的console.若該uart_driver支援serial console,否則為NULL */
    /* 下面這倆,它們應該被初始化為NULL */
    struct uart_state *state;//下層,串列埠驅動層
    struct tty_driver *tty_driver; /* tty相關 */
};

    在我們上邊填充的結構體中,有兩個成員未被賦值,他們分別代表底層和上層,tty_driver 代表的是上層,他是tty_register_driver函式的引數,咱們註冊的是一個tty驅動,tty_driver是tty驅動的結構體,它會在register_uart_driver中的過程中賦值uart_state則代表下層,uart_state會在register_uart_driver 的過程中分配空間(他會根據支援的最大串列埠數量開闢相應的空間uart_driver->nr),它裡面真正硬體相關的結構是是uart_state->uart_port,這個uart_port是需要我們從其它地方呼叫uart_add_one_port來新增的。 在amba_pl011中是用probe函式新增uart_port

 

1、裝置驅動層(下層)

struct uart_state {
    struct tty_port port;
    int pm_state;
    struct circ_buf xmit;//緩衝區,傳送資料和接收資料的儲存空間
    struct tasklet_struct tlet;
    struct uart_port *uart_port; //對應於一個實際的串列埠裝置
};

    在註冊 driver 時,會根據 uart_driver->nr 來申請 nr 個 uart_state 空間,用來存放驅動所支援的串列埠(埠)的物理資訊。

struct uart_port {

    spinlock_t lock; /* port lock */
    unsigned long iobase; /* io埠基地址(物理) */
    unsigned char __iomem *membase; /* io記憶體基地址(虛擬) */
    unsigned int (*serial_in)(struct uart_port *, int);
    void (*serial_out)(struct uart_port *, int, int);
    unsigned int irq; /* 中斷號 */
    unsigned long irqflags; /* 中斷標誌  */
    unsigned int uartclk; /* 串列埠時鐘 */
    unsigned int fifosize; /* 串列埠緩衝區大小 */
    unsigned char x_char; /* xon/xoff char */
    unsigned char regshift; /* 暫存器位移 */
    unsigned char iotype; /* IO訪問方式 */
    unsigned char unused1;
    unsigned int read_status_mask; /* 關心 Rx error status */
    unsigned int ignore_status_mask; /* 忽略 Rx error status */
    struct uart_state *state; /* pointer to parent state */
    struct uart_icount icount; /* 串列埠資訊計數器 */
    struct console *cons; /* struct console, if any */
    
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
    unsigned long sysrq; /* sysrq timeout */
#endif

    upf_t flags;
    unsigned int mctrl; /* 當前的Moden 設定 */
    unsigned int timeout; /* character-based timeout */
    unsigned int type; /* 埠型別 */
    const struct uart_ops *ops; /* 串列埠埠操作函式 */
    unsigned int custom_divisor;
    unsigned int line; /* 埠索引 */
    resource_size_t mapbase; /* io記憶體物理基地址 */
    struct device *dev; /* 父裝置 */
    unsigned char hub6; /* this should be in the 8250 driver */
    unsigned char suspended;
    unsigned char unused[2];
    void *private_data; /* generic platform data pointer */
};

    這個結構體,是需要我們自己來填充的,有幾個串列埠,那麼就需要填充個uart_port ,並且通過 uart_add_one_port 新增到 uart_driver->uart_state->uart_port 中去。當然uart_driver 有多個 uart_state ,每個 uart_state 有一個 uart_port 。在 uart_port 裡還有一個非常重要的成員 struct uart_ops *ops ,這個是具體的暫存器操作函式,是需要我們自己來實現的,也就和裸機驅動差不多。

static struct uart_ops amba_pl011_pops = {
    .tx_empty = pl01x_tx_empty,               //串列埠的tx_fifo是否為空
    .set_mctrl = pl011_set_mctrl,              //設定串列埠的modem控制,xyz
    .get_mctrl = pl01x_get_mctrl,              //獲取modem設定
    .stop_tx = pl011_stop_tx,                //停止傳輸
    .start_tx = pl011_start_tx,               //開始傳輸
    .stop_rx = pl011_stop_rx,                //停止接收
    .enable_ms = pl011_enable_ms,              //使能modem的狀態訊號
    .break_ctl = pl011_break_ctl,              //設定break訊號
    .startup = pl011_startup,                //使能串列埠,使用者呼叫open使最終會呼叫此函式
    .shutdown = pl011_shutdown,               // 關閉串列埠,應用程式關閉串列埠裝置檔案時,該函式會被呼叫
    .flush_buffer = pl011_dma_flush_buffer,
    .set_termios = pl011_set_termios,        //設定串列埠屬性,包括波特率等
    .type = pl011_type,                   //判斷串列埠型別是否為amba
    .release_port = pl010_release_port,       //釋放埠使用的記憶體
    .request_port = pl010_request_port,       //請求埠使用的記憶體
    .config_port = pl010_config_port,        //設定埠型別並申請埠使用的記憶體
    .verify_port = pl010_verify_port,        //檢驗串列埠屬性,包括匯流排型別和波特率

#ifdef CONFIG_CONSOLE_POLL
    .poll_get_char = pl010_get_poll_char,       //獲取console的輸入
    .poll_put_char = pl010_put_poll_char,       //將資料顯示到console中
#endif

};

2、tty核心層(上層)

底層驅動層和tty層之間的聯絡需要從register_uart_driver中分析,tty_driver是在uart_driver註冊過程中構建的

int uart_register_driver(struct uart_driver *drv)

{
    struct tty_driver *normal = NULL;
    int i, retval;

    /* 根據driver支援的最大裝置數,申請n個 uart_state 空間,每一個 uart_state 
    都有一個uart_port,支援多少個串列埠,就開闢多少塊空間 */
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

    /* tty層:分配一個 tty_driver ,並將drv->tty_driver 指向它 */
    normal  = alloc_tty_driver(drv->nr);
    drv->tty_driver = normal;

    /* 對 tty_driver 進行設定 */
    normal->owner = drv->owner;
    normal->driver_name = drv->driver_name;
    normal->name = drv->dev_name;
    normal->major = drv->major;
    normal->minor_start = drv->minor;
    normal->type = TTY_DRIVER_TYPE_SERIAL;
    normal->subtype = SERIAL_TYPE_NORMAL;
    normal->init_termios = tty_std_termios;
    normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
    normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    normal->driver_state    = drv;
    tty_set_operations(normal, &uart_ops);//設定tty驅動層的處理函式

    /*
     * Initialise the UART state(s).
     */
    for (i = 0; i < drv->nr; i++) {
        struct uart_state *state = drv->state + i;
        struct tty_port *port = &state->port; /* driver->state->tty_port */
        tty_port_init(port);
        port->close_delay     = 500; /* .5 seconds */
        port->closing_wait    = 30000; /* 30 seconds */

        /* 初始化 tasklet */
        tasklet_init(&state->tlet, uart_tasklet_action, (unsigned long)state);
    }

    /* tty層:註冊 driver->tty_driver */
    retval = tty_register_driver(normal);
}

註冊過程主要做的工作如下所示

    1、根據driver支援的最大裝置數,申請n個uart_state空間,每一個uart_state都對應一個 uart_port 。

    2、分配一個tty_driver空間並指向drv->tty_driver。

    3、對tty_driver進行設定,其中包括預設波特率、校驗方式等,還有一個重要的 Ops ,uart_ops ,它是tty核心與我們串列埠驅動通訊的介面。

    4、初始化每一個 uart_state 的 tasklet 。

    5、註冊 tty_driver 。

 

註冊uart_driver實際上是註冊一個tty_driver的過程最終生成的裝置節點也是tty的,與使用者空間打交道的工作也是由使用者空間層的函式實現的,好在這一部分核心已經幫我們實現好的,我們只需要知道他們需要什麼機構,套用一下他們的框架就可以了。

如下所示為tty核心層的函式:這一層的函式通過如下方式呼叫裝置驅動層

uart_state->uart_port->ops
static const struct tty_operations uart_ops = {
    .open = uart_open,
    .close = uart_close,
    .write = uart_write,
    .put_char = uart_put_char, // 單位元組寫函式
    .flush_chars = uart_flush_chars, // 重新整理資料到硬體函式
    .write_room = uart_write_room, // 指示多少緩衝空閒的函式
    .chars_in_buffer= uart_chars_in_buffer, // 只是多少緩衝滿的函式
    .flush_buffer = uart_flush_buffer, // 重新整理資料到硬體
    .ioctl = uart_ioctl,
    .throttle = uart_throttle,
    .unthrottle = uart_unthrottle,
    .send_xchar = uart_send_xchar,
    .set_termios = uart_set_termios, // 當termios設定被改變時又tty核心呼叫
    .set_ldisc = uart_set_ldisc, // 設定線路規程函式
    .stop = uart_stop,
    .start = uart_start,
    .hangup = uart_hangup, // 掛起函式,當驅動掛起tty裝置時呼叫
    .break_ctl = uart_break_ctl, // 線路中斷控制函式
    .wait_until_sent= uart_wait_until_sent,

#ifdef CONFIG_PROC_FS
    .proc_fops = &uart_proc_fops,
#endif

    .tiocmget = uart_tiocmget, // 獲得當前tty的線路規程的設定
    .tiocmset = uart_tiocmset, // 設定當前tty線路規程的設定

#ifdef CONFIG_CONSOLE_POLL
    .poll_init = uart_poll_init,
    .poll_get_char = uart_poll_get_char,
    .poll_put_char = uart_poll_put_char,
#endif

};

 3、使用者空間函式分析

int tty_register_driver(struct tty_driver *driver)
{

    int error;
    int i;
    dev_t dev;
    void **p = NULL;

    if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
        p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
    }

    /* 申請裝置號 */
    if (!driver->major) {//裝置號未知的情況
        error = alloc_chrdev_region(&dev, driver->minor_start,
        driver->num, driver->name);
    } else {//裝置號已知的情況
        dev = MKDEV(driver->major, driver->minor_start);
        error = register_chrdev_region(dev, driver->num, driver->name);
    }

    if (p) { /* 為線路規程和termios分配空間 */
        driver->ttys = (struct tty_struct **)p;
        driver->termios = (struct ktermios **)(p + driver->num);
    } else {
        driver->ttys = NULL;
        driver->termios = NULL;
    }

    /* 建立字元裝置,使用 tty_fops */
    cdev_init(&driver->cdev, &tty_fops);
    driver->cdev.owner = driver->owner;
    error = cdev_add(&driver->cdev, dev, driver->num);
    mutex_lock(&tty_mutex);

    /* 將該 driver->tty_drivers 新增到全域性連結串列 tty_drivers */
    list_add(&driver->tty_drivers, &tty_drivers);
    mutex_unlock(&tty_mutex);
    if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
        for (i = 0; i < driver->num; i++)
            tty_register_device(driver, i, NULL);
    }

    /* proc 檔案系統註冊driver */
    proc_tty_register_driver(driver);
    driver->flags |= TTY_DRIVER_INSTALLED;

    return 0;
}

tty_driver 註冊過程幹了哪些事:

    1、為線路規程和termios分配空間,並使 tty_driver 相應的成員指向它們。

    2、註冊字元裝置,名字是 uart_driver->name 我們這裡是“ttyAMA”,檔案操作函式集是 tty_fops。

    3、將該 uart_driver->tty_drivers 新增到全域性連結串列 tty_drivers 。

    4、向proc檔案系統新增driver,提供除錯節點