linux驅(qū)動之串口驅(qū)動框架

一、前言

前面介紹了 Linux內(nèi)核 的 2 個(gè)驅(qū)動框架—— I2CSPI 膘婶,這 2 個(gè)框架相對簡單一些缺前,直來直去,沒有比較難以理解的點(diǎn)悬襟,層次分明衅码。而今天我們要講述的是我們熟悉的 串口驅(qū)動,該驅(qū)動加框也較之之前的驅(qū)動來說脊岳,復(fù)雜了許多逝段。串口 是我們常用的通訊手段,但其軟件框架在 Linux內(nèi)核 中非常復(fù)雜割捅。當(dāng)然奶躯,這里面也有歷史原因在。本文將簡單地介紹 UART驅(qū)動框架亿驾,希望能夠幫助各位讀者嘹黔。

二、UART驅(qū)動

2.1 tty簡介

串口(UART) 通常是以 tty驅(qū)動 的形式在用戶層表現(xiàn)出來莫瞬,那么我們首先需要知道 tty 是什么東西儡蔓。

ttyLinux系統(tǒng) 中出現(xiàn)得最多的一種設(shè)備類型,可以直接查看 /dev目錄 下疼邀,往往可以看到多個(gè) tty設(shè)備節(jié)點(diǎn)喂江。要了解 tty設(shè)備 就需要從歷史的角度說起。

在計(jì)算機(jī)系統(tǒng)中旁振,終端電子設(shè)備获询,用于向系統(tǒng)輸入數(shù)據(jù),或者接收系統(tǒng)發(fā)送的數(shù)據(jù)并顯示出來规求,所以該設(shè)備的作用是用于 人機(jī)交互筐付。而 電傳打字機(jī)(Teletype) 是一種久遠(yuǎn)的 遠(yuǎn)距離信息傳送設(shè)備卵惦,其通過 鍵盤 輸入數(shù)據(jù)阻肿,通過 印字機(jī)器 顯示接收到的數(shù)據(jù)。電傳打字機(jī) 就是一種典型的 終端沮尿,其英語簡稱即 tty丛塌。

按照筆者的理解较解,tty 即用于與 用戶 交互的設(shè)備,其向計(jì)算機(jī)系統(tǒng)輸入數(shù)據(jù)赴邻,也接收來自計(jì)算機(jī)系統(tǒng)的數(shù)據(jù)印衔。有興趣的讀者可以參考鏈接中的文章 Linux TTY framework(1)_基本概念

對于 Linux系統(tǒng) 來說姥敛,通過 串口 連接的設(shè)備一般都可以作為 終端設(shè)備奸焙。經(jīng)過日積月累,終端彤敛、tty与帆、串口等概念逐漸不再去分。一般來講墨榄,所有 串口設(shè)備 都是 tty設(shè)備玄糟,而 tty設(shè)備 除了 串口外還有其他的具體形式,比如 虛擬終端 袄秩、偽終端等阵翎。

其實(shí)這里還涉及到一個(gè)概念 控制臺(console),但本文目前不做講述之剧,后續(xù)有機(jī)會再寫相關(guān)文章郭卫。

2.2 串口tty框架

在講解相關(guān)細(xì)節(jié)之前,我們需要對 串口tty驅(qū)動 有個(gè)整體性的認(rèn)識背稼,這樣有非常助于后面理解代碼細(xì)節(jié)箱沦。在看細(xì)節(jié)之前,我們需要知道:

  • 整個(gè)驅(qū)動的整體架構(gòu)是如何組織的
  • 數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系是如何
  • 函數(shù)的傳遞是如何遞進(jìn)的

下面將展示一張圖雇庙,該圖有助于我們理解


串口tty驅(qū)動框架圖

我們可以看到:

  • 從左往右 看谓形,整個(gè)驅(qū)動可以分為 3個(gè)層次,分為為 字符設(shè)備層疆前、tty層串口硬件層寒跳。
    字符設(shè)備層tty驅(qū)動 作為 字符設(shè)備 直接與應(yīng)用層進(jìn)行交互,中間通過 tty層 進(jìn)行抽象竹椒,使得底層的具體形式可以被解耦童太。最終是底層硬件的 串口層,該層負(fù)責(zé)數(shù)據(jù)的設(shè)備之間的交互及一系列管理操作的具體實(shí)現(xiàn)胸完。
  • 從上往下 看书释,驅(qū)動主要由幾個(gè)數(shù)據(jù)結(jié)構(gòu)組成,分別為 UART_driver赊窥、UART_statetty_driver爆惧。UART_driver 是全局的 根數(shù)據(jù)結(jié)構(gòu),所有的結(jié)構(gòu)體都由其來進(jìn)行保存和控制锨能。tty_driver 則是對 tty層的具體實(shí)現(xiàn)扯再,最后 UART_stateUart_pot 則是底層驅(qū)動的具體實(shí)現(xiàn)芍耘。

2.3 串口tty驅(qū)動

本節(jié)將對代碼進(jìn)行講述,其中我們選擇 8250 這個(gè)串口IP的代碼作為示例來進(jìn)行講述熄阻。當(dāng)然只是為了學(xué)習(xí)驅(qū)動的實(shí)現(xiàn)斋竞,其他的串口驅(qū)動讀者可以按照自己的理解再去閱讀。

本節(jié)會分別按照 初始化讀/寫 來進(jìn)行講述秃殉。

2.3.1 串口硬件初始化

串口驅(qū)動的框架一大部分是在初始化階段進(jìn)行坝初,對 初始化 進(jìn)行梳理是必要的階段。下面先來看看代碼調(diào)用圖譜钾军。

首先是對 UART_driver結(jié)構(gòu)體 進(jìn)行創(chuàng)建和相關(guān)初始化脖卖,其負(fù)責(zé)高度的軟件抽象和相關(guān)軟件邏輯。其代碼調(diào)用圖譜如下:

->serial8250_init
  ->serial8250_isa_init_ports(初始化serial8250_ports巧颈,數(shù)據(jù)類型為uart_8250_port)
    ->serial8250_init_port(將serial8250_pops賦值給uart_port->ops)
    ->univ8250_rsa_support
  ->uart_register_driver(注冊uart_driver畦木,即全局變量serial8250_reg,將uart_ops設(shè)置給tty_driver)
    ->alloc_tty_driver(分配uart_driver->uart_state)
    ->tty_port_init(初始化uart_driver->uart_state->tty_port砸泛,tty_port的操作集為uart_port_ops)
    ->tty_register_driver(注冊tty_driver十籍,將tty_driver加入全局鏈表tty_drivers)
/* 8250_core */

/* 初始化8250串口框架 */
module_init(serial8250_init);
static int __init serial8250_init(void)
{
    int ret;
    ......
    /* 初始化各個(gè)串口的UART_port數(shù)據(jù)結(jié)構(gòu) */
    serial8250_isa_init_ports();
    ......

    /* 注冊UART驅(qū)動即UART_driver */
    serial8250_reg.nr = UART_NR;
    ret = uart_register_driver(&serial8250_reg);
    ......


    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
    ......
}

/* 初始化各個(gè)串口的UART_port數(shù)據(jù)結(jié)構(gòu) */
static void __init serial8250_isa_init_ports(void)
{
    struct uart_8250_port *up;
    static int first = 1;
    int i, irqflag = 0;
    ......

    /* 初始化每個(gè)UART_port的操作函數(shù)集 */
    for (i = 0; i < nr_uarts; i++) {
        struct uart_8250_port *up = &serial8250_ports[i];
        struct uart_port *port = &up->port;
        /* 設(shè)置當(dāng)前UART_port的編號 */
        port->line = i;
        /* 初始化UART_port的ops成員 */
        serial8250_init_port(up);
        {
          struct uart_port *port = &up->port;
          port->ops = &serial8250_pops;
          ......
        }
        /* 此時(shí)port->ops為serial8250_pops,所以base_ops指向了serial8250_pops */
        if (!base_ops)
            base_ops = port->ops;
        /* 將UART_port的ops成員指向univ8250_port_ops */
        port->ops = &univ8250_port_ops;

        ......
        /* 設(shè)置uart_8250_port的ops成員指向了univ8250_driver_ops唇礁,該成員在設(shè)置中斷時(shí)有用到 */
        up->ops = &univ8250_driver_ops;
    ......
    }

    /* 
      univ8250_port_ops的成員賦值為*base_ops指向的地址勾栗,即serial8250_pops  
      所以此時(shí)UART_port的ops成員指向serial8250_pops
    */
    univ8250_port_ops = *base_ops;
    /* 設(shè)置UART_port->ops的部分成員 */
    univ8250_rsa_support(&univ8250_port_ops);
    {
      ops->config_port  = univ8250_config_port;
      ops->request_port = univ8250_request_port;
      ops->release_port = univ8250_release_port;
    }
    ......
}

/* 注冊UART_driver驅(qū)動 */
int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal;
    int i, retval;

    /* 為UART_driver的每個(gè)UART_state分配空間,每個(gè)串口硬件都有UART_state */
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    ......

    /* 為串口驅(qū)動UART_driver分配一個(gè)tty_driver */
    normal = alloc_tty_driver(drv->nr);
    {
      struct tty_driver *ret = tty_alloc_driver(lines, 0);
      ......
      return ret;
    }
    ......
    /* 設(shè)置UART_driver的tty_driver成員 */
    drv->tty_driver = normal;

   /* 初始化相關(guān)成員 */ 
    normal->driver_name = drv->driver_name;
    normal->name        = drv->dev_name;
    /* 這里的設(shè)備號會在后續(xù)的open過程中使用到 */
    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;
    /* 設(shè)置tty_driver的ops成員指向struct tty_operations uart_ops */
    tty_set_operations(normal, &uart_ops);
    {
      driver->ops = op;
    };
    /* 初始化每個(gè)UART_state的tty_port成員 */
    for (i = 0; i < drv->nr; i++) {
        struct uart_state *state = drv->state + i;
        struct tty_port *port = &state->port;

        /* 初始化每個(gè)tty_port */
        tty_port_init(port);
        {
          memset(port, 0, sizeof(*port));
          tty_buffer_init(port);
          /* 以下為tty_buffer_init函數(shù)體 */
          {
            struct tty_bufhead *buf = &port->buf;

            mutex_init(&buf->lock);
            tty_buffer_reset(&buf->sentinel, 0);
            buf->head = &buf->sentinel;
            buf->tail = &buf->sentinel;
            init_llist_head(&buf->free);
            atomic_set(&buf->mem_used, 0);
            atomic_set(&buf->priority, 0);
            INIT_WORK(&buf->work, flush_to_ldisc);//該work_queue盏筐,后面的讀取數(shù)據(jù)的時(shí)候要用到
            buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;
          }
          init_waitqueue_head(&port->open_wait);
          init_waitqueue_head(&port->delta_msr_wait);
          mutex_init(&port->mutex);
          mutex_init(&port->buf_mutex);
          spin_lock_init(&port->lock);
          port->close_delay = (50 * HZ) / 100;
          port->closing_wait = (3000 * HZ) / 100; 

          /* 設(shè)置tty_port的client_ops指向default_client_ops围俘,會在接收和發(fā)送數(shù)據(jù)時(shí)使用到 */
          port->client_ops = &default_client_ops;
          kref_init(&port->kref);
        }
        /* 設(shè)置tty_port的ops成員指向uart_port_ops */
        port->ops = &uart_port_ops;
    }

    retval = tty_register_driver(normal);
    ......
}

/* 注冊tty_driver驅(qū)動 */
int tty_register_driver(struct tty_driver *driver)
{
    int error;
    int i;
    dev_t dev;
    struct device *d;

    /* 分配設(shè)備好 */
    if (!driver->major) {
        error = alloc_chrdev_region(&dev, driver->minor_start,
                        driver->num, driver->name);
        if (!error) {
            driver->major = MAJOR(dev);
            driver->minor_start = MINOR(dev);
        }
    } else {
        dev = MKDEV(driver->major, driver->minor_start);
        error = register_chrdev_region(dev, driver->num, driver->name);
    }

    if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
        /* 添加tty的字符設(shè)備 */
        error = tty_cdev_add(driver, dev, 0, driver->num);
        {
            int err;
            driver->cdevs[index] = cdev_alloc();
            ......
            /* 設(shè)置字符設(shè)備的操作集為tty_fops,該操作集用于tty設(shè)備和應(yīng)用層交互 */
            driver->cdevs[index]->ops = &tty_fops;
            driver->cdevs[index]->owner = driver->owner;
            /* 添加字符設(shè)備 */
            err = cdev_add(driver->cdevs[index], dev, count);
            ......
            return err;
        }
        ......
    }

    /* 將當(dāng)前的tty_driver添加全局的tty_drivers鏈表 */
    mutex_lock(&tty_mutex);
    list_add(&driver->tty_drivers, &tty_drivers);
    mutex_unlock(&tty_mutex);

    /* 注冊每一個(gè)tty設(shè)備 */
    if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
        for (i = 0; i < driver->num; i++) {
            d = tty_register_device(driver, i, NULL);
            ......
            }
        }
    }
    ......
    return 0;
    ......
}

上面是針對 8250串口核心 做的一些基本的初始化琢融。按照筆者理解界牡,在實(shí)際應(yīng)用中用戶有可能需要加載其他串口驅(qū)動來修改串口的一些屬性,所以當(dāng)用戶執(zhí)行驅(qū)動裝載時(shí)有以下的初始化步驟漾抬,同樣也是針對 UART_portUART_state 的初始化和操作宿亡。其調(diào)用譜圖和代碼示例如下:

dw8250_probe
  ->serial8250_register_8250_port
    ->serial8250_find_match_or_unused(從全局serial8250_ports找出可以使用的元素,該元素可以理解為uart_port)
      ->uart_add_one_port(填充uart_port)
        ->tty_port_register_device_attr_serdev
          ->tty_port_link_device
          ->tty_register_device_attr
            ->tty_cdev_add(將tty_ops賦給tty_driver的cdec成員)
/* 初始化用戶的串口硬件模塊 */
static int dw8250_probe(struct platform_device *pdev)
{
    struct uart_8250_port uart = {};
    struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    int irq = platform_get_irq(pdev, 0);
    struct uart_port *p = &uart.port;
    struct device *dev = &pdev->dev;
    struct dw8250_data *data;
    int err;
    u32 val;

    ......

    /* 對p指向uart_port結(jié)構(gòu)體進(jìn)行初始化纳令,根據(jù)用戶自己的進(jìn)行設(shè)置 */
    spin_lock_init(&p->lock);
    p->mapbase  = regs->start;
    p->irq      = irq; //串口中斷號
    p->handle_irq   = dw8250_handle_irq; //串口中斷處理函數(shù)挽荠,會在串口中斷時(shí)執(zhí)行
    p->pm       = dw8250_do_pm;
    p->type     = PORT_8250;
    p->flags    = UPF_SHARE_IRQ | UPF_FIXED_PORT;
    p->dev      = dev;
    p->iotype   = UPIO_MEM;
    p->serial_in    = dw8250_serial_in; //串口輸入寄存器設(shè)置函數(shù)
    p->serial_out   = dw8250_serial_out; //串口輸出寄存器設(shè)置函數(shù)
    p->set_ldisc    = dw8250_set_ldisc; //串口線路規(guī)程設(shè)置函數(shù)
    p->set_termios  = dw8250_set_termios; //串口中斷參數(shù)規(guī)程設(shè)置函數(shù)

    /* 設(shè)置寄存器地址 */
    p->membase = devm_ioremap(dev, regs->start, resource_size(regs));
    ......

    /* 分配私有數(shù)據(jù) */
    data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
    ......

    data->dma.fn = dw8250_fallback_dma_filter;
    data->usr_reg = DW_UART_USR;
    p->private_data = data;
    ......
    
    /* 注冊用戶自己的串口驅(qū)動 */
    data->line = serial8250_register_8250_port(&uart);
    if (data->line < 0) {
        err = data->line;
        goto err_reset;
    }

    ......
}

/* 注冊用戶的串口驅(qū)動 */
int serial8250_register_8250_port(struct uart_8250_port *up)
{
    struct uart_8250_port *uart;
    int ret = -ENOSPC;
    ......

    mutex_lock(&serial_mutex);
    /* 從8250_core中找出一個(gè)沒有用且匹配的uart_8250_port */
    uart = serial8250_find_match_or_unused(&up->port);
    /* 對uart_8250_port 進(jìn)行初始化 */
    if (uart && uart->port.type != PORT_8250_CIR) {
        ......
        /* 初始化找到的uart_port數(shù)據(jù)結(jié)構(gòu)uart->port為uart_port數(shù)據(jù)結(jié)構(gòu) */
        uart->port.iobase       = up->port.iobase;
        uart->port.membase      = up->port.membase;
        uart->port.irq          = up->port.irq;
        uart->port.irqflags     = up->port.irqflags;
        uart->port.uartclk      = up->port.uartclk;
        uart->port.fifosize     = up->port.fifosize;
        uart->port.regshift     = up->port.regshift;
        uart->port.iotype       = up->port.iotype;
        uart->port.flags        = up->port.flags | UPF_BOOT_AUTOCONF;
        uart->bugs      = up->bugs;
        uart->port.mapbase      = up->port.mapbase;
        uart->port.mapsize      = up->port.mapsize;
        uart->port.private_data = up->port.private_data;
        uart->tx_loadsz     = up->tx_loadsz;
        uart->capabilities  = up->capabilities;
        uart->port.throttle = up->port.throttle;
        uart->port.unthrottle   = up->port.unthrottle;
        uart->port.rs485_config = up->port.rs485_config;
        uart->port.rs485    = up->port.rs485;
        uart->dma       = up->dma;

        if (uart->port.fifosize && !uart->tx_loadsz)
            uart->tx_loadsz = uart->port.fifosize;

        if (up->port.dev)
            uart->port.dev = up->port.dev;

        if (up->port.flags & UPF_FIXED_TYPE)
            uart->port.type = up->port.type;

        /* 設(shè)置uart_port的默認(rèn)屬性 */
        serial8250_set_defaults(uart);

        if (up->port.serial_in)
            uart->port.serial_in = up->port.serial_in;
        if (up->port.serial_out)
            uart->port.serial_out = up->port.serial_out;
        if (up->port.handle_irq)
            uart->port.handle_irq = up->port.handle_irq;

        if (up->port.set_termios)
            uart->port.set_termios = up->port.set_termios;
        if (up->port.set_ldisc)
            uart->port.set_ldisc = up->port.set_ldisc;
        if (up->port.get_mctrl)
            uart->port.get_mctrl = up->port.get_mctrl;
        if (up->port.set_mctrl)
            uart->port.set_mctrl = up->port.set_mctrl;
        if (up->port.startup)
            uart->port.startup = up->port.startup;
        if (up->port.shutdown)
            uart->port.shutdown = up->port.shutdown;
        if (up->port.pm)
            uart->port.pm = up->port.pm;
        if (up->port.handle_break)
            uart->port.handle_break = up->port.handle_break;
        if (up->dl_read)
            uart->dl_read = up->dl_read;
        if (up->dl_write)
            uart->dl_write = up->dl_write;

        if (uart->port.type != PORT_8250_CIR) {
            if (serial8250_isa_config != NULL)
                serial8250_isa_config(0, &uart->port,
                        &uart->capabilities);

            .....
            /* 將新的uart_port添加到uart_driver */
            ret = uart_add_one_port(&serial8250_reg, &uart->port);
            if (ret == 0)
                ret = uart->port.line;
        } else {
            dev_info(uart->port.dev,
                "skipping CIR port at 0x%lx / 0x%llx, IRQ %d\n",
                uart->port.iobase,
                (unsigned long long)uart->port.mapbase,
                uart->port.irq);

            ret = 0;
        }
    }
    mutex_unlock(&serial_mutex);

    return ret;
}

/* 把一個(gè)uart_port添加到uart_driver */
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
    struct uart_state *state;
    struct tty_port *port;
    int ret = 0;
    struct device *tty_dev;
    int num_groups;

    /* 根據(jù)串口編號獲取對應(yīng)的uart_state和uart_port */
    state = drv->state + uport->line;
    port = &state->port;
    ......

    /* 設(shè)置新的uart_port到uart_state中,并將對uart_port的state成員進(jìn)行賦值 */
    state->uart_port = uport;
    uport->state = state;

    ......

    /* 分配并設(shè)置屬性組 */
    uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
                    GFP_KERNEL);
    if (!uport->tty_groups) {
        ret = -ENOMEM;
        goto out;
    }
    uport->tty_groups[0] = &tty_dev_attr_group;
    if (uport->attr_group)
        uport->tty_groups[1] = uport->attr_group;

    /* 將新的uart_port及其屬性注冊到內(nèi)核中 */
    tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
            uport->line, uport->dev, port, uport->tty_groups);
    ......
}

struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
        struct tty_driver *driver, unsigned index,
        struct device *device, void *drvdata,
        const struct attribute_group **attr_grp)
{
    struct device *dev;

    /* 該函數(shù)設(shè)置tty_drvier的tty_port成員平绩,建立聯(lián)系 */
    tty_port_link_device(port, driver, index);
    {
        if (WARN_ON(index >= driver->num))
            return;
        driver->ports[index] = port;
    }
  
    /* 該函數(shù)會設(shè)置tty_port的一部分成員 */
    dev = serdev_tty_port_register(port, device, driver, index);
    {
        /* 設(shè)置tty_port的client_ops成員 */
        port->client_ops = &client_ops;
    }
    /* 注冊tty_port對應(yīng)的device */
    return tty_register_device_attr(driver, index, device, drvdata,
            attr_grp);
}
    ......


struct device *tty_register_device_attr(struct tty_driver *driver,
                   unsigned index, struct device *device,
                   void *drvdata,
                   const struct attribute_group **attr_grp)
{
    char name[64];
    dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
    struct ktermios *tp;
    struct device *dev;
    int retval;

    ......

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    ......

    
    /* 設(shè)置dev的相關(guān)屬性 */
    dev->devt = devt;
    dev->class = tty_class;
    dev->parent = device;
    dev->release = tty_device_create_release;
    dev_set_name(dev, "%s", name);
    dev->groups = attr_grp;
    dev_set_drvdata(dev, drvdata);
    ......
    /* 注冊dev */
    retval = device_register(dev);
    if (retval)
        goto err_put;

    if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
        ......
        /* 添加tty_driver的cdev圈匆,該函數(shù)在前面已經(jīng)出現(xiàn)過,這里的cdev是對應(yīng)新的tty_port */
        retval = tty_cdev_add(driver, devt, index, 1);
        if (retval)
            goto err_del;
    }
    ......
}

前面我們簡單的過了一遍 8250串口核心8250串口硬件 的初始化流程捏雌。在初始化以后跃赚,數(shù)據(jù)結(jié)構(gòu)就如 串口tty驅(qū)動框架圖 所示。當(dāng)然以上內(nèi)容刪減了許多細(xì)節(jié)腹忽,有興趣的讀者可以閱讀源碼加深理解来累。

2.3.2 串口使用流程

相比于 初始化砚作,理解 串口讀取 的難度會更加高一些窘奏,以為讀寫過程會更加復(fù)雜嘹锁,希望筆者的一點(diǎn)筆墨可以幫助大家稍微理解一下其過程。

這里需要先知道着裹,串口讀取 一般是由 2 個(gè)線程來完成:

  • 前臺線程:即用戶在應(yīng)用空間使用 read 等系統(tǒng)調(diào)用的線程领猾,該線程在進(jìn)入內(nèi)核空間會執(zhí)行相關(guān)的函數(shù)進(jìn)行等待。當(dāng)時(shí)機(jī)成熟時(shí)由 后臺線程 進(jìn)行喚醒并讀取相關(guān)數(shù)據(jù)到應(yīng)用恐案件骇扇。
  • 后臺線程:由 串口驅(qū)動 在初始化時(shí)會定義一個(gè)線程摔竿,由該線程在中斷有數(shù)據(jù)時(shí)進(jìn)行讀寫。并把讀取到的數(shù)據(jù)存放在某個(gè)緩存中少孝,最后會喚醒 前臺線程 去讀取這個(gè)緩存中的數(shù)據(jù)继低。

在講解線程的執(zhí)行之前需要知道 中斷的初始化及其處理函數(shù),因?yàn)?串口讀取中斷 密不可分稍走。下面會先講述 中斷初始化及其處理袁翁,然后再講述 讀取的前臺及后臺線程

2.3.2.1 打開串口

串口中斷 的設(shè)置是在串口打開時(shí)設(shè)置的婿脸,所以我們直接查看串口打開時(shí)的調(diào)用圖譜及代碼即可知道相關(guān)內(nèi)容粱胜,如下所示:

tty_open(struct file_operations tty_fops)
  /* 申請并初始化相關(guān)結(jié)構(gòu)體 */
  ->tty_open_by_driver
    ->tty_init_dev
      ->alloc_tty_struct
        ->tty_ldisc_init(設(shè)置默認(rèn)線路規(guī)程為N_TTY)
  /* 執(zhí)行open操作,比如設(shè)置寄存器或者中斷等 */
  ->uart_open
    ->tty_port_open
      ->uart_port_activate
        ->uart_startup
          ->uart_port_startup
            ->serial8250_startup
              ->serial8250_do_startup
                ->setup_irq(univ8250_setup_irq)
                  ->serial_link_irq_chain
                    ->request_irq(serial8250_interrupt)

可以看到狐树,打開的流程很長焙压,其調(diào)用圖譜也非常的深,我們逐步看一下代碼吧

/* 在前面的章節(jié)中可以看到抑钟,tty_open是tty設(shè)備使用字符設(shè)備cdev對外的接口涯曲,當(dāng)我們打開 */
static int tty_open(struct inode *inode, struct file *filp)
{
    struct tty_struct *tty;
    int noctty, retval;
    dev_t device = inode->i_rdev;
    unsigned saved_flags = filp->f_flags;

    ......
      
    /* 分配一個(gè)tty_file_private結(jié)構(gòu)體并賦值給文件的private_data成員 */
    retval = tty_alloc_file(filp);
             {
                struct tty_file_private *priv;

                priv = kmalloc(sizeof(*priv), GFP_KERNEL);
                ......
                file->private_data = priv;

                return 0;
              }
    ......
    /* 
      根據(jù)內(nèi)核注釋,tty_open_current_tty用于獲取當(dāng)前tty設(shè)備的鎖在塔,但從函數(shù)命名和返回值來看這應(yīng)該是另一種說法
      根據(jù)筆者理解掀抹,這里應(yīng)該獲取當(dāng)前tty設(shè)備的tty_struct結(jié)構(gòu)體
      如果獲取失敗則表明當(dāng)前的tty設(shè)備沒有打開,需要調(diào)用tty_open_by_driver重新打開
    */
    tty = tty_open_current_tty(device, filp);
    if (!tty)
        tty = tty_open_by_driver(device, inode, filp);

    ......
    
    /* 將當(dāng)前文件的私有數(shù)據(jù)即tty_file_private成員進(jìn)行填充心俗,再將其鏈入tty_stuct的tty_files鏈表 */
    tty_add_file(tty, filp);
    {
        struct tty_file_private *priv = file->private_data;

        priv->tty = tty;
        priv->file = file;

        spin_lock(&tty->files_lock);
        list_add(&priv->list, &tty->tty_files);
        spin_unlock(&tty->files_lock);
    }
    ......

    /* tty_struct結(jié)構(gòu)題的ops成員(sturct tty_operations)中的open函數(shù)傲武,即uart_open */
    if (tty->ops->open)
        retval = tty->ops->open(tty, filp);
    else
        retval = -ENODEV;
    filp->f_flags = saved_flags;

    ......
}

static struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,
                         struct file *filp)
{
    struct tty_struct *tty;
    struct tty_driver *driver = NULL;
    int index = -1;
    int retval;

    mutex_lock(&tty_mutex);
    /* 根據(jù)設(shè)備好找到對應(yīng)的tty_driver */
    driver = tty_lookup_driver(device, filp, &index);
    ......

    /* 檢查我們是否重復(fù)打開tty */
    tty = tty_driver_lookup_tty(driver, filp, index);
    ......
  
    /* 如果重復(fù)打開則執(zhí)行該分支 */
    if (tty) {
        if (tty_port_kopened(tty->port)) {
            tty_kref_put(tty);
            mutex_unlock(&tty_mutex);
            tty = ERR_PTR(-EBUSY);
            goto out;
        }
        mutex_unlock(&tty_mutex);
        retval = tty_lock_interruptible(tty);
        tty_kref_put(tty);  /* drop kref from tty_driver_lookup_tty() */
        if (retval) {
            if (retval == -EINTR)
                retval = -ERESTARTSYS;
            tty = ERR_PTR(retval);
            goto out;
        }
        retval = tty_reopen(tty);
        if (retval < 0) {
            tty_unlock(tty);
            tty = ERR_PTR(retval);
        }
    } else { 
        /* 沒有重復(fù)打開則初始化出一個(gè)tty_struct */
        tty = tty_init_dev(driver, index);
        mutex_unlock(&tty_mutex);
    }
    ......

    return tty;
}

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
{
    struct tty_struct *tty;
    int retval;

    ......

    /* 分配tty_struct結(jié)構(gòu)體并初始化,該函數(shù)比較重要 */
    ......

    tty_lock(tty);
    /* 該函數(shù)主要講tty_struct的地址鏈接到tty_driver的成員中 */
    retval = tty_driver_install_tty(driver, tty);
    if (retval < 0)
        goto err_free_tty;
  
    /* 復(fù)制tty_struct中的tty_port成員 */
    if (!tty->port)
        tty->port = driver->ports[idx];

    ......
    tty->port->itty = tty;

    /* 調(diào)用線程規(guī)程并打開 */
    retval = tty_ldisc_setup(tty, tty->link);
    /* 以下為tty_ldisc_setup函數(shù)體 */
    {
        int retval = tty_ldisc_open(tty, tty->ldisc);
        if (retval)
            return retval;

        if (o_tty) {
            retval = tty_ldisc_open(o_tty, o_tty->ldisc);
            if (retval) {
                tty_ldisc_close(tty, tty->ldisc);
                return retval;
            }
        }
        return 0;    
    }

    ......
    /* Return the tty locked so that it cannot vanish under the caller */
    return tty;
    ......
}

/* 分配并初始化tty_struct */
struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
{
    struct tty_struct *tty;

    tty = kzalloc(sizeof(*tty), GFP_KERNEL);
    if (!tty)
        return NULL;

    kref_init(&tty->kref);
    tty->magic = TTY_MAGIC;
    /* 初始化線路規(guī)程 */
    tty_ldisc_init(tty);
    /* 以下為tty_ldisc_init函數(shù)體 */
    {
          /* 線路規(guī)程初始化為N_TTY */
        struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);
        if (IS_ERR(ld))
            panic("n_tty: init_tty");
        tty->ldisc = ld;
    }
    tty->session = NULL;
    tty->pgrp = NULL;
    mutex_init(&tty->legacy_mutex);
    mutex_init(&tty->throttle_mutex);
    init_rwsem(&tty->termios_rwsem);
    mutex_init(&tty->winsize_mutex);
    init_ldsem(&tty->ldisc_sem);
    init_waitqueue_head(&tty->write_wait);
    init_waitqueue_head(&tty->read_wait);
    INIT_WORK(&tty->hangup_work, do_tty_hangup);
    mutex_init(&tty->atomic_write_lock);
    spin_lock_init(&tty->ctrl_lock);
    spin_lock_init(&tty->flow_lock);
    spin_lock_init(&tty->files_lock);
    INIT_LIST_HEAD(&tty->tty_files);
    INIT_WORK(&tty->SAK_work, do_SAK_work);

    tty->driver = driver;
    tty->ops = driver->ops;
    tty->index = idx;
    tty_line_name(driver, idx, tty->name);
    tty->dev = tty_get_device(tty);

    return tty;
}

static int uart_open(struct tty_struct *tty, struct file *filp)
{
    struct uart_driver *drv = tty->driver->driver_state;
    int retval, line = tty->index;
    struct uart_state *state = drv->state + line;

    /* 設(shè)置tty_struct的driver_data成員為uart_state */
    tty->driver_data = state;

    /* 打開tty_port */
    retval = tty_port_open(&state->port, tty, filp);
    if (retval > 0)
        retval = 0;

    return retval;
}

int tty_port_open(struct tty_port *port, struct tty_struct *tty,
                            struct file *filp)
{
    spin_lock_irq(&port->lock);
    ++port->count;
    spin_unlock_irq(&port->lock);
    tty_port_tty_set(port, tty);

    /* 只有在硬件沒有初始化好的情況才能打開設(shè)備 */
    mutex_lock(&port->mutex);

    /* 檢測tty_port是否初始化過 */
    if (!tty_port_initialized(port)) {
        clear_bit(TTY_IO_ERROR, &tty->flags);
        if (port->ops->activate) {
            /* 執(zhí)行tty_port的activate方法城榛,即uart_port_ops中的uart_port_activate方法 */
            int retval = port->ops->activate(port, tty);
            if (retval) {
                mutex_unlock(&port->mutex);
                return retval;
            }
        }
        tty_port_set_initialized(port, 1);
    }
    mutex_unlock(&port->mutex);
    return tty_port_block_til_ready(port, tty, filp);
}

/* 使能uart_port */
static int uart_port_activate(struct tty_port *port, struct tty_struct *tty)
{
    struct uart_state *state = container_of(port, struct uart_state, port);
    struct uart_port *uport;

    ......

    /* 啟動uart串口 */
    return uart_startup(tty, state, 0);
}

/* 啟動串口 */
static int uart_startup(struct tty_struct *tty, struct uart_state *state,
        int init_hw)
{
    struct tty_port *port = &state->port;
    int retval;

    /* 檢查串口是否有初始化 */
    if (tty_port_initialized(port))
        return 0;

    /* 執(zhí)行uart_port_startup */
    retval = uart_port_startup(tty, state, init_hw);
    ......

    return retval;
}

static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
        int init_hw)
{
    struct uart_port *uport = uart_port_check(state);
    unsigned long page;
    int retval = 0;

    ......

    /* 確保已經(jīng)開辟了傳輸緩存 */
    if (!state->xmit.buf) {
        /* 如果沒有則獲取并賦值 */
        page = get_zeroed_page(GFP_KERNEL);
        if (!page)
            return -ENOMEM;

        state->xmit.buf = (unsigned char *) page;
        uart_circ_clear(&state->xmit);
    }

    /* 執(zhí)行uart_port的startup函數(shù)揪利,即serial8250_pops的serial8250_startup */
    retval = uport->ops->startup(uport);
    /* 以下為serial8250_startup 函數(shù)體 */
    {
        if (port->startup)
            return port->startup(port);
        return serial8250_do_startup(port);
    }
    if (retval == 0) {  
        ......
        /* 初始化串口的硬件設(shè)置 */
        uart_change_speed(tty, state, NULL);

        /* 設(shè)置串口的RTS和DTR信號 */
        if (init_hw && C_BAUD(tty))
            uart_port_dtr_rts(uport, 1);
    }

    ......

    return retval;
}

int serial8250_do_startup(struct uart_port *port)
{
    struct uart_8250_port *up = up_to_u8250p(port);
    unsigned long flags;
    unsigned char lsr, iir;
    int retval;

    /* 省略的部分語句為根據(jù)端口類型設(shè)置硬件寄存器,不在本文的講述范圍內(nèi)狠持,有興趣的讀者可以閱讀源碼 */
    ......

    /*
     * For the Altera 16550 variants, set TX threshold trigger level.
     */
    if (((port->type == PORT_ALTR_16550_F32) ||
         (port->type == PORT_ALTR_16550_F64) ||
         (port->type == PORT_ALTR_16550_F128)) && (port->fifosize > 1)) {
        /* Bounds checking of TX threshold (valid 0 to fifosize-2) */
        if ((up->tx_loadsz < 2) || (up->tx_loadsz > port->fifosize)) {
            pr_err("ttyS%d TX FIFO Threshold errors, skipping\n",
                   serial_index(port));
        } else {
            serial_port_out(port, UART_ALTR_AFR,
                    UART_ALTR_EN_TXFIFO_LW);
            serial_port_out(port, UART_ALTR_TX_LOW,
                    port->fifosize - up->tx_loadsz);
            port->handle_irq = serial8250_tx_threshold_handle_irq;
        }
    }

    if (port->irq && !(up->port.flags & UPF_NO_THRE_TEST)) {
        unsigned char iir1;
        /*  
          當(dāng)發(fā)送器是空閑并且中斷已經(jīng)被清除時(shí)疟位,需要測試UART不要讓它再次聲明發(fā)送中斷。
          實(shí)際上當(dāng)發(fā)送器是空閑且允許中斷時(shí)喘垂,16500 應(yīng)該總是重新聲明該中斷甜刻。
          為了讓改變寄存器生效绍撞,需要加入必要的延時(shí)。
         */

        /* 關(guān)閉中斷 */
        spin_lock_irqsave(&port->lock, flags);
        if (up->port.irqflags & IRQF_SHARED)
            disable_irq_nosync(port->irq);

         /* 設(shè)置寄存器 */
        wait_for_xmitr(up, UART_LSR_THRE);
        serial_port_out_sync(port, UART_IER, UART_IER_THRI);
        /* 延遲并等待寄存器生效 */
        udelay(1);

        /* 設(shè)置寄存器 */
        iir1 = serial_port_in(port, UART_IIR);
        serial_port_out(port, UART_IER, 0);
        serial_port_out_sync(port, UART_IER, UART_IER_THRI);
        /* 延遲并等待寄存器生效 */
        udelay(1);
        iir = serial_port_in(port, UART_IIR);
        serial_port_out(port, UART_IER, 0);

        /* 使能中斷 */
        if (port->irqflags & IRQF_SHARED)
            enable_irq(port->irq);
        spin_unlock_irqrestore(&port->lock, flags);

        /*
         * If the interrupt is not reasserted, or we otherwise
         * don't trust the iir, setup a timer to kick the UART
         * on a regular basis.
         */
        if ((!(iir1 & UART_IIR_NO_INT) && (iir & UART_IIR_NO_INT)) ||
            up->port.flags & UPF_BUG_THRE) {
            up->bugs |= UART_BUG_THRE;
        }
    }

    /* 調(diào)用uart_8250_port結(jié)構(gòu)體中的setup_irq方法得院,即univ8250_driver_ops中的univ8250_setup_irq */
    retval = up->ops->setup_irq(up);
    if (retval)
        goto out;

    /* 初始化相關(guān)的硬件寄存器 */
    ......
}

/* 設(shè)置中斷 */
static int univ8250_setup_irq(struct uart_8250_port *up)
{
    struct uart_port *port = &up->port;
    int retval = 0;

    ......
    
    if (!port->irq) {
    } else
        /* 如果設(shè)置了中斷號傻铣,則執(zhí)行serial_link_irq_chain */
        retval = serial_link_irq_chain(up);

    return retval;
}

static int serial_link_irq_chain(struct uart_8250_port *up)
{
    struct hlist_head *h;
    struct hlist_node *n;
    struct irq_info *i;
    int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;

    ......

    if (i->head) {
        ......
    } else {
        ......
        /* 申請中斷 */
        ret = request_irq(up->port.irq, serial8250_interrupt,
                  irq_flags, up->port.name, i);
        if (ret < 0)
            serial_do_unlink(i, up);
    }

    return ret;
}

總結(jié)而言,open流程 可以分為 2個(gè)部分

  1. 申請并初始化結(jié)構(gòu)體祥绞,比如 tty_struct
  2. 設(shè)置相關(guān)寄存器和初始化中斷

2.3.2.1 打開串口

上面講述了串口的 open流程非洲,完成了中斷設(shè)置和寄存器設(shè)置。此時(shí)我們的應(yīng)用線程開始讀取串口數(shù)據(jù)蜕径。那么讀取流程需要分開 2 個(gè)部分來講述:

  1. 中斷后臺進(jìn)程:負(fù)責(zé)從硬件讀取數(shù)據(jù)存放到 buffer 中两踏,并通過機(jī)制通知 用戶前臺進(jìn)程 獲取數(shù)據(jù)。
  2. 用戶前臺進(jìn)程:負(fù)責(zé)等待接收 buffer 中的數(shù)據(jù)

下面先講述 后臺中斷讀取兜喻,其調(diào)用圖譜如下:

/* 中斷設(shè)置梦染,該部分以前在前文中講過,不再贅述 */
serial8250_init
  ->serial8250_isa_init_ports(up->ops = &univ8250_driver_ops;)

/* 中斷處理函數(shù) */
serial8250_interrupt
  ->handle_irq(dw8250_handle_irq朴皆,是在dw8250_probe設(shè)置的)
    ->serial8250_handle_irq
      ->serial8250_rx_chars
        |->serial8250_read_char//從硬件中讀取數(shù)據(jù)到tty_buffer中
        |  ->uart_insert_char
        |    ->tty_insert_flip_char
        |      ->__tty_insert_flip_char
        |        ->__tty_buffer_request_room
        |          ->tty_buffer_alloc
        |->tty_flip_buffer_push//將tty_buffer中數(shù)據(jù)投放到線路規(guī)程層(ldsc)的緩沖區(qū)中
        |  ->tty_schedule_flip
        |    ->flush_to_ldisc
        |      ->receive_buf
        |        ->tty_port_default_receive_buf(tty_port)
        |          ->tty_ldisc_receive_buf
        |            ->n_tty_receive_buf

下面直接看中斷代碼:

static irqreturn_t serial8250_interrupt(int irq, void *dev_id)
{
    struct irq_info *i = dev_id;
    struct list_head *l, *end = NULL;
    int pass_counter = 0, handled = 0;

    ......
    l = i->head;
    /* 輪眉每個(gè)串口的中斷函數(shù) */
    do {
        struct uart_8250_port *up;
        struct uart_port *port;

        up = list_entry(l, struct uart_8250_port, list);
        port = &up->port;

        /* 執(zhí)行中斷函數(shù)帕识,也就是dw8250_handle_irq,該函數(shù)在dw8250_probe設(shè)置 */
        if (port->handle_irq(port)) {
            handled = 1;
            end = NULL;
        } else if (end == NULL)
            end = l;

        l = l->next;

        ......
    } while (l != end);

    ......

    return IRQ_RETVAL(handled);
}

static int dw8250_handle_irq(struct uart_port *p)
{
    struct uart_8250_port *up = up_to_u8250p(p);
    struct dw8250_data *d = p->private_data;
    unsigned int iir = p->serial_in(p, UART_IIR);
    unsigned int status;
    unsigned long flags;

    ......

    if (serial8250_handle_irq(p, iir))
        return 1;

    ......

    return 0;
}

int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
{
    unsigned char status;
    unsigned long flags;
    struct uart_8250_port *up = up_to_u8250p(port);

    ......

    if (status & (UART_LSR_DR | UART_LSR_BI)) {
        if (!up->dma || handle_rx_dma(up, iir))
            /* 從串口中讀取字節(jié) */
            status = serial8250_rx_chars(up, status);
    }
    ......
    return 1;
}

unsigned char serial8250_rx_chars(struct uart_8250_port *up, unsigned char lsr)
{
    struct uart_port *port = &up->port;
    int max_count = 256;

    do {
        /* 循環(huán)讀取字節(jié)到tty_buffer中车荔,該buffer位于uart_port數(shù)據(jù)結(jié)構(gòu)中 */
        serial8250_read_char(up, lsr);
        if (--max_count == 0)
            break;
        lsr = serial_in(up, UART_LSR);
    } while (lsr & (UART_LSR_DR | UART_LSR_BI));

    tty_flip_buffer_push(&port->state->port);
    return lsr;
}

static void serial8250_read_char(struct uart_8250_port *up, unsigned char lsr)
{
    struct uart_port *port = &up->port;
    unsigned char ch;
    char flag = TTY_NORMAL;

    if (likely(lsr & UART_LSR_DR))
        /* 讀取串口接收到的字節(jié) */
        ch = serial_in(up, UART_RX);
    ......
    /* 將數(shù)據(jù)放入tty_buffer中 */
    uart_insert_char(port, lsr, UART_LSR_OE, ch, flag);
}

void uart_insert_char(struct uart_port *port, unsigned int status,
         unsigned int overrun, unsigned int ch, unsigned int flag)
{
    struct tty_port *tport = &port->state->port;

    if ((status & port->ignore_status_mask & ~overrun) == 0)
        /* 將字節(jié)ch保存到tty_buffer中 */
        if (tty_insert_flip_char(tport, ch, flag) == 0)
            ++port->icount.buf_overrun;

    if (status & ~port->ignore_status_mask & overrun)
        /* 將字節(jié)ch保存到tty_buffer中 */
        if (tty_insert_flip_char(tport, 0, TTY_OVERRUN) == 0)
            ++port->icount.buf_overrun;
}

static inline int tty_insert_flip_char(struct tty_port *port,
                    unsigned char ch, char flag)
{
    /* 獲取tty_buffer的尾部 */
    struct tty_buffer *tb = port->buf.tail;
    int change;

    ......
    if (!change && tb->used < tb->size) {
        if (~tb->flags & TTYB_NORMAL)
            *flag_buf_ptr(tb, tb->used) = flag;
        /* 如果tty_buffer中還有空間渡冻,則將字節(jié)ch插入tty_buffer中尾部中并返回 */
        *char_buf_ptr(tb, tb->used++) = ch;
        return 1;
    }
    /* 如果沒有空間則執(zhí)行__tty_insert_flip_char */
    return __tty_insert_flip_char(port, ch, flag);
}

int __tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag)
{
    struct tty_buffer *tb;
    int flags = (flag == TTY_NORMAL) ? TTYB_NORMAL : 0;

    /* 開辟tty_buffer的空間 */
    if (!__tty_buffer_request_room(port, 1, flags))
        return 0;

    /* 將字節(jié)ch插入剛剛開辟的tty_buffer中 */
    tb = port->buf.tail;
    if (~tb->flags & TTYB_NORMAL)
        *flag_buf_ptr(tb, tb->used) = flag;
    *char_buf_ptr(tb, tb->used++) = ch;

    return 1;
}

static int __tty_buffer_request_room(struct tty_port *port, size_t size,
                     int flags)
{
    struct tty_bufhead *buf = &port->buf;
    struct tty_buffer *b, *n;
    int left, change;

    /* 獲取剩余空間 */
    b = buf->tail;
    if (b->flags & TTYB_NORMAL)
        left = 2 * b->size - b->used;
    else
        left = b->size - b->used;

    change = (b->flags & TTYB_NORMAL) && (~flags & TTYB_NORMAL);
    if (change || left < size) {
        /* 開辟tty_buffer的空間 */
        n = tty_buffer_alloc(port, size);
        if (n != NULL) {
            n->flags = flags;
            /* 設(shè)置緩沖區(qū)到尾部 */
            buf->tail = n;
            smp_store_release(&b->commit, b->used);
            smp_store_release(&b->next, n);
        } else if (change)
            size = 0;
        else
            size = left;
    }
    return size;
}

static struct tty_buffer *tty_buffer_alloc(struct tty_port *port, size_t size)
{
    struct llist_node *free;
    struct tty_buffer *p;

    /* 對齊緩沖區(qū)大小 */
    size = __ALIGN_MASK(size, TTYB_ALIGN_MASK);

    if (size <= MIN_TTYB_SIZE) {
        free = llist_del_first(&port->buf.free);
        if (free) {
            p = llist_entry(free, struct tty_buffer, free);
            goto found;
        }
    }

    ......
    /* 分配緩沖區(qū) */
    p = kmalloc(sizeof(struct tty_buffer) + 2 * size, GFP_ATOMIC);
    if (p == NULL)
        return NULL;

found:
    tty_buffer_reset(p, size);
    atomic_add(size, &port->buf.mem_used);
    return p;
}

到了這里,我們基本上就完成了 數(shù)據(jù)到tty_buffer 的填充忧便,接下來我們就需要使用 tty_flip_buffer_push 喚醒 前臺線程族吻,下面為代碼實(shí)例:

void tty_flip_buffer_push(struct tty_port *port)
{
    tty_schedule_flip(port);
}
void tty_schedule_flip(struct tty_port *port)
{
    struct tty_bufhead *buf = &port->buf;

    /* paired w/ acquire in flush_to_ldisc(); ensures
     * flush_to_ldisc() sees buffer data.
     */
    smp_store_release(&buf->tail->commit, buf->tail->used);
    /* 喚醒workqueue執(zhí)行處理,即調(diào)用flush_to_ldisc珠增,該函數(shù)在tty_buffer_init時(shí)初始化 */
    queue_work(system_unbound_wq, &buf->work);
}

static void flush_to_ldisc(struct work_struct *work)
{
    struct tty_port *port = container_of(work, struct tty_port, buf.work);
    struct tty_bufhead *buf = &port->buf;

    mutex_lock(&buf->lock);

    while (1) {
        struct tty_buffer *head = buf->head;
        struct tty_buffer *next;
        int count;

        .....
        /* 判斷是否有數(shù)據(jù)可讀 */
        next = smp_load_acquire(&head->next);
        count = smp_load_acquire(&head->commit) - head->read;
        if (!count) {
            if (next == NULL)
                break;
            buf->head = next;
            tty_buffer_free(port, head);
            continue;
        }
        /* 接收數(shù)據(jù) */
        count = receive_buf(port, head, count);
        if (!count)
            break;
        head->read += count;
    }

    mutex_unlock(&buf->lock);

}

static int receive_buf(struct tty_port *port, struct tty_buffer *head, int count)
{
    /* 獲取tty_buffer的緩沖區(qū)地址 */
    unsigned char *p = char_buf_ptr(head, head->read);
    char          *f = NULL;

    if (~head->flags & TTYB_NORMAL)
        f = flag_buf_ptr(head, head->read);

    /* 
        這里執(zhí)行的是tty_port的client_ops操作集
        其初始化是在函數(shù)tty_port_init中進(jìn)行初始化超歌,該回調(diào)的函數(shù)名為tty_port_default_receive_buf
    */
    return port->client_ops->receive_buf(port, p, f, count);
}

static int tty_port_default_receive_buf(struct tty_port *port,
                    const unsigned char *p,
                    const unsigned char *f, size_t count)
{
    int ret;
    struct tty_struct *tty;
    struct tty_ldisc *disc;

    ......
    /* 使用線路規(guī)程的接收方法對數(shù)據(jù)進(jìn)行接收 */
    ret = tty_ldisc_receive_buf(disc, p, (char *)f, count);

    tty_ldisc_deref(disc);

    return ret;
}

int tty_ldisc_receive_buf(struct tty_ldisc *ld, const unsigned char *p,
              char *f, int count)
{
    /* 
      使用線路規(guī)程的接收方法,前面講說初始化使用的是ntty作為默認(rèn)的線路規(guī)程
      所以這里的回調(diào)receive_buf2為n_tty_receive_buf2函數(shù)蒂教,執(zhí)行該函數(shù)接收數(shù)據(jù)
      請助理巍举,這里p是存放有接收數(shù)據(jù)的緩沖區(qū)
    */
    if (ld->ops->receive_buf2)
        count = ld->ops->receive_buf2(ld->tty, p, f, count);
    else {
        count = min_t(int, count, ld->tty->receive_room);
        if (count && ld->ops->receive_buf)
            ld->ops->receive_buf(ld->tty, p, f, count);
    }
    return count;
}

static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,
                  char *fp, int count)
{
    return n_tty_receive_buf_common(tty, cp, fp, count, 1);
}

static int n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
             char *fp, int count, int flow)
{
    struct n_tty_data *ldata = tty->disc_data;
    int room, n, rcvd = 0, overflow;

    ......

    while (1) {
        ......
        /* 在這里對數(shù)據(jù)進(jìn)行接收 */
        /* ignore parity errors if handling overflow */
        if (!overflow || !fp || *fp != TTY_PARITY)
            __receive_buf(tty, cp, fp, n);

        cp += n;
        if (fp)
            fp += n;
        count -= n;
        rcvd += n;
    }

    tty->receive_room = room;

    /* Unthrottle if handling overflow on pty */
    if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
        if (overflow) {
            tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
            tty_unthrottle_safe(tty);
            __tty_set_flow_change(tty, 0);
        }
    } else
        n_tty_check_throttle(tty);

    up_read(&tty->termios_rwsem);

    return rcvd;
}

static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,
              char *fp, int count)
{
    struct n_tty_data *ldata = tty->disc_data;
    bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty));

    /* 
      這里判斷分支比較多,需要根據(jù)不同的設(shè)置執(zhí)行不同的接收函數(shù)凝垛。
      不同的接收函數(shù)對數(shù)據(jù)的處理不同懊悯,但在最終都會調(diào)用put_tty_queue來投放數(shù)據(jù)
      下面以n_tty_receive_char_fast函數(shù)為例子進(jìn)行說明
    */
    if (ldata->real_raw)
        n_tty_receive_buf_real_raw(tty, cp, fp, count);
    else if (ldata->raw || (L_EXTPROC(tty) && !preops))
        n_tty_receive_buf_raw(tty, cp, fp, count);
    else if (tty->closing && !L_EXTPROC(tty))
        n_tty_receive_buf_closing(tty, cp, fp, count);
    else {
        if (ldata->lnext) {
            char flag = TTY_NORMAL;

            if (fp)
                flag = *fp++;
            n_tty_receive_char_lnext(tty, *cp++, flag);
            count--;
        }

        if (!preops && !I_PARMRK(tty))
            n_tty_receive_buf_fast(tty, cp, fp, count);
        else
            n_tty_receive_buf_standard(tty, cp, fp, count);

        flush_echoes(tty);
        /* 
          flush_chars回到調(diào)用的是uart_ops操作集的uart_flush_chars函數(shù)。
          該函數(shù)使能串口發(fā)送應(yīng)用程的數(shù)據(jù) 
        */
        if (tty->ops->flush_chars)
            tty->ops->flush_chars(tty);
    }

    if (ldata->icanon && !L_EXTPROC(tty))
        return;

    /* publish read_head to consumer */
    smp_store_release(&ldata->commit_head, ldata->read_head);

    /* 喚醒前臺進(jìn)程來接收數(shù)據(jù) */
    if (read_cnt(ldata)) {
        kill_fasync(&tty->fasync, SIGIO, POLL_IN);
        wake_up_interruptible_poll(&tty->read_wait, POLLIN);
    }
}

static void
n_tty_receive_buf_fast(struct tty_struct *tty, const unsigned char *cp,
               char *fp, int count)
{
    struct n_tty_data *ldata = tty->disc_data;
    char flag = TTY_NORMAL;

    /* 循環(huán)接收字節(jié) */
    while (count--) {
        if (fp)
            flag = *fp++;
        if (likely(flag == TTY_NORMAL)) {
            /* 從緩沖區(qū)中獲取字節(jié) */
            unsigned char c = *cp++;

            if (!test_bit(c, ldata->char_map))
                /* 將接收到的字節(jié)投放到線路規(guī)程的數(shù)據(jù)緩沖區(qū)中 */
                n_tty_receive_char_fast(tty, c);
            ......
        } 
    }
}

static inline void n_tty_receive_char_fast(struct tty_struct *tty, unsigned char c)
{
    struct n_tty_data *ldata = tty->disc_data;

    ......
    /* 投送字節(jié)到線路規(guī)程的數(shù)據(jù)中 */
    put_tty_queue(c, ldata);
}

static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata)
{
      /* 將字節(jié)賦值給線路規(guī)程中的read_buf中 */
    *read_buf_addr(ldata, ldata->read_head) = c;
    /* 以下為read_buf_addr函數(shù)體 */
    {
      return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
    }
    /* 記錄緩沖區(qū)當(dāng)前的頭部下標(biāo) */
    ldata->read_head++;
}

到了這里梦皮,數(shù)據(jù)的 中斷后臺接收流程 就已經(jīng)完成了炭分。這里我們還需要注意 前臺接收進(jìn)程 的流程,求該流程比較簡單剑肯,相比于 后臺接收流程 來了說調(diào)用圖譜十分簡潔捧毛。其代碼實(shí)例和調(diào)用圖譜如下:

tty_read
  ->n_tty_read
    ->canon_copy_from_read_buf
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
            loff_t *ppos)
{
    int i;
    struct inode *inode = file_inode(file);
    struct tty_struct *tty = file_tty(file);
    struct tty_ldisc *ld;

    ......
    /* 調(diào)用線路規(guī)程的read方法,同理這里使用的是ntty作用默認(rèn)線路規(guī)程,所以其函數(shù)名為n_tty_read */
    if (ld->ops->read)
        i = ld->ops->read(tty, file, buf, count);
    ......

    return i;
}

static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
             unsigned char __user *buf, size_t nr)
{
    struct n_tty_data *ldata = tty->disc_data;
    unsigned char __user *b = buf;
    /* 聲明一個(gè)等待隊(duì)列實(shí)例 */
    DEFINE_WAIT_FUNC(wait, woken_wake_function);
    int c;
    int minimum, time;
    ssize_t retval = 0;
    long timeout;
    int packet;
    size_t tail;

    ......

  
    packet = tty->packet;
    tail = ldata->read_tail;

    /* 加入等待隊(duì)列 */
    add_wait_queue(&tty->read_wait, &wait);
    while (nr) {
        /* First test for status change. */
        if (packet && tty->link->ctrl_status) {
            unsigned char cs;
            if (b != buf)
                break;
            spin_lock_irq(&tty->link->ctrl_lock);
            cs = tty->link->ctrl_status;
            tty->link->ctrl_status = 0;
            spin_unlock_irq(&tty->link->ctrl_lock);
            if (put_user(cs, b)) {
                retval = -EFAULT;
                break;
            }
            b++;
            nr--;
            break;
        }

        if (!input_available_p(tty, 0)) {
            up_read(&tty->termios_rwsem);
            tty_buffer_flush_work(tty->port);
            down_read(&tty->termios_rwsem);
            if (!input_available_p(tty, 0)) {
                ......
                /* 如果當(dāng)前由于某種原因不可讀呀忧,則進(jìn)程讓出當(dāng)前CPU师痕,等待喚醒 */
                timeout = wait_woken(&wait, TASK_INTERRUPTIBLE,
                        timeout);

                down_read(&tty->termios_rwsem);
                continue;
            }
        }
    

        if (ldata->icanon && !L_EXTPROC(tty)) {
          ......
        } else {
            int uncopied;

            if (packet && b == buf) {
                if (put_user(TIOCPKT_DATA, b)) {
                    retval = -EFAULT;
                    break;
                }
                b++;
                nr--;
            }

            /* 根據(jù)不同情況將數(shù)據(jù)投放到應(yīng)用層 */
            uncopied = copy_from_read_buf(tty, &b, &nr);
            ......
        }

        n_tty_check_unthrottle(tty);

        if (b - buf >= minimum)
            break;
        if (time)
            timeout = time;
    }
    if (tail != ldata->read_tail)
        n_tty_kick_worker(tty);
    up_read(&tty->termios_rwsem);

    /* 移除出等待隊(duì)列 */
    remove_wait_queue(&tty->read_wait, &wait);
    mutex_unlock(&ldata->atomic_read_lock);

    if (b - buf)
        retval = b - buf;

    return retval;
}

static int copy_from_read_buf(struct tty_struct *tty,
                      unsigned char __user **b,
                      size_t *nr)

{
    struct n_tty_data *ldata = tty->disc_data;
    int retval;
    size_t n;
    bool is_eof;
    size_t head = smp_load_acquire(&ldata->commit_head);
    size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1);

    retval = 0;
    n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail);
    n = min(*nr, n);
    if (n) {
        /* 獲取當(dāng)前數(shù)據(jù)包的所在地址 */
        const unsigned char *from = read_buf_addr(ldata, tail);
        /* 以下為read_buf_addr函數(shù)體 */
        {
          /* 獲取線路規(guī)程中的讀取buffer地址 */
          return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
        }
        /* 將地址拷貝到應(yīng)用空間,其中b為應(yīng)用空間buffer地址而账,from為串口接收數(shù)據(jù)包所在地址 */
        retval = copy_to_user(*b, from, n);
        n -= retval;
        ......
        *b += n;
        *nr -= n;
    }
    return retval;
}

到了這里胰坟,串口讀流程 基本完成了。其 寫流程 框架類似福扬,但數(shù)據(jù)流相反腕铸,這里不做講述惜犀,有興趣的讀者可以自行閱讀源碼理解铛碑。

三、結(jié)語

本文更新得有些許慢虽界,主要是平時(shí)看代碼寫文章的時(shí)間少了汽烦,再加上 tty框架 復(fù)雜且代碼多,所以閱讀起來理解不是很容易莉御。按照筆者理解撇吞,包括筆者在內(nèi)大部分人一輩子都應(yīng)該不會自己去寫一個(gè) 串口或者tty驅(qū)動,但學(xué)習(xí) 串口和tty驅(qū)動 有助于理解和使用 線路規(guī)程礁叔,想 串口藍(lán)牙 等使用方法相信不少人會遇到過牍颈,學(xué)習(xí)串口驅(qū)動框架有助于我們工作生活中開發(fā)和調(diào)試。
本文與該系列的其他文章類似琅关,主要在于梳理驅(qū)動框架來幫助讀者理解煮岁,而非講述使用方法。所以對于很多不影響流程的細(xì)節(jié)都省略掉涣易,筆者建議讀者有條件的可以閱讀一遍代碼可以加深理解画机。如果本文有誤或者有講得不好的地方,請各位讀者指出并海涵新症。

四步氏、參考鏈接

http://www.wowotech.net/tty_framework/435.html
http://www.wowotech.net/sort/tty_framework
https://blog.csdn.net/lizuobin2/article/details/51773305/
https://blog.csdn.net/sharecode/article/details/9197567
https://blog.csdn.net/sharecode/article/details/9196591

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市徒爹,隨后出現(xiàn)的幾起案子荚醒,更是在濱河造成了極大的恐慌,老刑警劉巖隆嗅,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件界阁,死亡現(xiàn)場離奇詭異,居然都是意外死亡榛瓮,警方通過查閱死者的電腦和手機(jī)铺董,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人精续,你說我怎么就攤上這事坝锰。” “怎么了重付?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵顷级,是天一觀的道長。 經(jīng)常有香客問我确垫,道長弓颈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任删掀,我火速辦了婚禮翔冀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘披泪。我一直安慰自己纤子,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布款票。 她就那樣靜靜地躺著控硼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艾少。 梳的紋絲不亂的頭發(fā)上卡乾,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音缚够,去河邊找鬼幔妨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛潮瓶,可吹牛的內(nèi)容都是我干的陶冷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼毯辅,長吁一口氣:“原來是場噩夢啊……” “哼埂伦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起思恐,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤沾谜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后胀莹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體基跑,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年描焰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媳否。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栅螟。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖篱竭,靈堂內(nèi)的尸體忽然破棺而出力图,到底是詐尸還是另有隱情,我是刑警寧澤掺逼,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布吃媒,位于F島的核電站,受9級特大地震影響吕喘,放射性物質(zhì)發(fā)生泄漏赘那。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一氯质、第九天 我趴在偏房一處隱蔽的房頂上張望募舟。 院中可真熱鬧,春花似錦病梢、人聲如沸胃珍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吩蔑,卻和暖如春钮热,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烛芬。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工隧期, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赘娄。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓仆潮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遣臼。 傳聞我的和親對象是個(gè)殘疾皇子性置,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345