新聞中心

        EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > Linux下的串口總線驅(qū)動(二)

        Linux下的串口總線驅(qū)動(二)

        作者: 時間:2016-11-22 來源:網(wǎng)絡(luò) 收藏
        四.TTY層內(nèi)核代碼

        TTY驅(qū)動程序有三種:控制臺、串口和pty。在此我們主要分析Mini2440串口驅(qū)動。

        本文引用地址:http://www.104case.com/article/201611/319917.htm

        我們現(xiàn)在跟蹤uart_register_driver和uart_add_one_port發(fā)現(xiàn),他們的原函數(shù)定義在TTY層驅(qū)動serial_core.o中。

        int uart_register_driver(struct uart_driver *drv)

        {

        struct tty_driver *normal = NULL;

        int i, retval;

        BUG_ON(drv->state);

        drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

        retval = -ENOMEM;

        if (!drv->state)

        goto out;

        normal = alloc_tty_driver(drv->nr); //分配TTY驅(qū)動

        if (!normal)

        goto out;

        drv->tty_driver = normal;

        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; //初始的termios

        normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;//控制模式設(shè)置

        normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600; //設(shè)置輸入/出速度

        normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;

        normal->driver_state = drv; //私有數(shù)據(jù)

        tty_set_operations(normal, &uart_ops); //設(shè)置TTY驅(qū)動操作

        for (i = 0; i < drv->nr; i++) { //初始化UART狀態(tài)

        struct uart_state *state = drv->state + i;

        struct tty_port *port = &state->port;

        tty_port_init(port);

        port->close_delay = 500;

        port->closing_wait = 30000;

        tasklet_init(&state->tlet, uart_tasklet_action,

        (unsigned long)state);

        }

        retval = tty_register_driver(normal); //注冊TTY驅(qū)動

        out:

        if (retval < 0) {

        put_tty_driver(normal);

        kfree(drv->state);

        }

        return retval;

        }

        在上面uart_register_driver這個函數(shù)里我們首先分配了TTY驅(qū)動,然后對其進(jìn)行填充,初始的termios,并設(shè)置TTY驅(qū)動操作,最后注冊TTY驅(qū)動。其中設(shè)置TTY驅(qū)動操作時用到uart_ops,我們看看這個uart_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,

        .set_ldisc = uart_set_ldisc,

        .stop = uart_stop,

        .start = uart_start,

        .hangup = uart_hangup,

        .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,

        .tiocmset = uart_tiocmset,

        #ifdef CONFIG_CONSOLE_POLL

        .poll_init = uart_poll_init,

        .poll_get_char = uart_poll_get_char,

        .poll_put_char = uart_poll_put_char,

        #endif

        };

        終端設(shè)備可以完成收發(fā)數(shù)據(jù)的功能,當(dāng)用戶在有數(shù)據(jù)發(fā)送給終端設(shè)備時候,通過”write()系統(tǒng)調(diào)用—tty核心—線路規(guī)程”的層層調(diào)用,最終調(diào)用tty_driver結(jié)構(gòu)體中的write()函數(shù)完成發(fā)送。因?yàn)閭鬏斔俣群蛅ty硬件緩沖區(qū)容量的原因,不是所有的寫程序要求的字符都可以在調(diào)用寫函數(shù)時候被發(fā)送出去,因此,寫函數(shù)應(yīng)當(dāng)返回能夠發(fā)給硬件的字節(jié)數(shù)以便用戶程序檢查是否所有的數(shù)據(jù)被真正寫入。如果在write()調(diào)用期間發(fā)生任何錯誤,一個負(fù)的錯誤碼應(yīng)當(dāng)被返回。在上面的uart_ops結(jié)構(gòu)體中,我們先看看寫函數(shù)uart_write的實(shí)現(xiàn)吧。

        static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)

        {

        struct uart_state *state = tty->driver_data; //獲取設(shè)備私有信息結(jié)構(gòu)體

        struct uart_port *port;

        struct circ_buf *circ;

        unsigned long flags;

        int c, ret = 0;

        if (!state) {

        WARN_ON(1);

        return -EL3HLT;

        }

        port = state->uart_port; //UART端口

        circ = &state->xmit; //數(shù)據(jù)緩沖區(qū)

        if (!circ->buf)

        return 0;

        spin_lock_irqsave(&port->lock, flags); //獲取UART端口操作的鎖

        while (1) {

        //返回可用緩存空間的大小

        c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);

        if (count < c)

        c = count;

        if (c <= 0) //緩存區(qū)太小則退出

        break;

        //將用戶空間buf中大小為c的內(nèi)容拷貝到緩存中

        memcpy(circ->buf + circ->head, buf, c);

        circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);

        buf += c; //緩存區(qū)指針后移

        count -= c; //當(dāng)一次發(fā)送的字節(jié)過多,需要分次發(fā)送

        ret += c; //已經(jīng)發(fā)送的字節(jié)數(shù)

        }

        spin_unlock_irqrestore(&port->lock, flags); //釋放UART端口操作的鎖

        uart_start(tty); //開始發(fā)送

        return ret;

        }

        根據(jù)上面對uart_write的分析,我們知道tty_driver的write()函數(shù)接收三個參數(shù)tty_struct,發(fā)送數(shù)據(jù)指針和要發(fā)送的字節(jié)數(shù)。uart_state作為這個驅(qū)動tty的私有數(shù)據(jù),其中circ_buf定義了緩沖區(qū),我們向這個緩沖區(qū)拷貝待發(fā)送的內(nèi)容后,執(zhí)行uart_start(tty)進(jìn)行發(fā)送數(shù)據(jù)。那我們繼續(xù)看跟蹤uart_start函數(shù)

        static void uart_start(struct tty_struct *tty)

        {

        struct uart_state *state = tty->driver_data;

        struct uart_port *port = state->uart_port;

        unsigned long flags;

        spin_lock_irqsave(&port->lock, flags); //獲取UART端口操作的鎖

        __uart_start(tty);

        spin_unlock_irqrestore(&port->lock, flags); //釋放UART端口操作的鎖

        }

        static void __uart_start(struct tty_struct *tty)

        {

        struct uart_state *state = tty->driver_data;

        struct uart_port *port = state->uart_port;

        if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&

        !tty->stopped && !tty->hw_stopped) //緩沖區(qū)有數(shù)據(jù)并開啟發(fā)送狀態(tài)

        port->ops->start_tx(port); //調(diào)用uart_ops下的start_tx,即s3c24xx_serial_start_tx

        }

        注意__uart_start函數(shù)中的port->ops->start_tx(port)便實(shí)現(xiàn)了tty層和uart層的相連,由tty層的write()調(diào)用uart層的write()。

        好了,上面講的是發(fā)送數(shù)據(jù),讀者可能注意到struct tty_operations uart_ops中沒有提到read()函數(shù)。因?yàn)榘l(fā)送是用戶主動的,而接收拾用戶調(diào)用read()從一片緩沖區(qū)讀取已經(jīng)放好的數(shù)據(jù),這個緩沖區(qū)由struct tty_flip_buffer結(jié)構(gòu)體實(shí)現(xiàn)。因?yàn)閠ty核提供了這樣的緩沖邏輯,所以每個tty驅(qū)動并非一定要實(shí)現(xiàn)它自身的緩沖邏輯。Tty驅(qū)動不需要關(guān)注struct tty_flip_buffe的細(xì)節(jié),從tty驅(qū)動接收到的來自硬件層的字符將被tty_insert_filp_char()函數(shù)插入filp緩沖區(qū)。如果傳輸?shù)淖止?jié)數(shù)count大于或等于TTY_FLIPBUF_SIEZE,這個flip緩沖區(qū)就需要被刷新到用戶,刷新是通過調(diào)用tty_flip_buffer_push()實(shí)現(xiàn)的。

        接著,我們繼續(xù)看struct tty_operations uart_ops中對termios的設(shè)置函數(shù)set_termios,即uart_set_termios。這個set_termios需要根據(jù)用戶對termios的設(shè)置完成實(shí)際的硬件設(shè)置。新的設(shè)置被保存在tty_struct中,舊的設(shè)置被保存在old參數(shù)中,若新舊參數(shù)相同,則什么都不需要做,對于被改的設(shè)置,需要完成硬件上的設(shè)置。好了,下面我們還是看看uart_set_termios的實(shí)現(xiàn)吧。

        static void uart_set_termios(struct tty_struct *tty,

        struct ktermios *old_termios)

        {

        struct uart_state *state = tty->driver_data; //獲取私有數(shù)據(jù)

        unsigned long flags;

        unsigned int cflag = tty->termios->c_cflag; //獲取當(dāng)前線路設(shè)置

        #define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

        //如果新舊線路設(shè)置的控制狀態(tài),輸入輸出速度等信息一樣,則退出

        if ((cflag ^ old_termios->c_cflag) == 0 &&

        tty->termios->c_ospeed == old_termios->c_ospeed &&

        tty->termios->c_ispeed == old_termios->c_ispeed &&

        RELEVANT_IFLAG(tty->termios->c_iflag ^ old_termios->c_iflag) == 0) {

        return;

        }

        uart_change_speed(state, old_termios); //用新的線路規(guī)程的速度更新舊的線路規(guī)程

        //處理波特率為B0情況

        if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD))

        uart_clear_mctrl(state->uart_port, TIOCM_RTS | TIOCM_DTR);

        /處理波特率為非B0情況

        if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {

        unsigned int mask = TIOCM_DTR;

        if (!(cflag & CRTSCTS) ||

        !test_bit(TTY_THROTTLED, &tty->flags))

        mask |= TIOCM_RTS;

        uart_set_mctrl(state->uart_port, mask); //設(shè)置modem控制

        }

        //處理無數(shù)據(jù)流控制情況

        if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) {

        spin_lock_irqsave(&state->uart_port->lock, flags);

        tty->hw_stopped = 0;

        __uart_start(tty);

        spin_unlock_irqrestore(&state->uart_port->lock, flags);

        }

        //處理有數(shù)據(jù)流控制情況

        if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) {

        spin_lock_irqsave(&state->uart_port->lock, flags);

        if (!(state->uart_port->ops->get_mctrl(state->uart_port) & TIOCM_CTS)) {

        tty->hw_stopped = 1;

        state->uart_port->ops->stop_tx(state->uart_port);

        }

        spin_unlock_irqrestore(&state->uart_port->lock, flags);

        }

        }

        好了我們已經(jīng)講解了write,set_termiox,下面我們講講tiocmget和tiocmset。Tiocmget()函數(shù)用于獲取tty設(shè)備的線路設(shè)置,對應(yīng)的tiocmset()用于設(shè)置tty設(shè)備的線路設(shè)置。

        static int uart_tiocmget(struct tty_struct *tty, struct file *file)

        {

        struct uart_state *state = tty->driver_data;

        struct tty_port *port = &state->port;

        struct uart_port *uport = state->uart_port;

        int result = -EIO;

        mutex_lock(&port->mutex); //獲取對tty_port操作的鎖

        if ((!file || !tty_hung_up_p(file)) &&

        !(tty->flags & (1 << TTY_IO_ERROR))) {

        result = uport->mctrl;

        spin_lock_irq(&uport->lock);

        result |= uport->ops->get_mctrl(uport); //調(diào)用UART層get_mctrl獲取modem控制

        spin_unlock_irq(&uport->lock);

        }

        mutex_unlock(&port->mutex); //釋放對tty_port操作的鎖

        return result;

        }

        static int uart_tiocmset(struct tty_struct *tty, struct file *file,

        unsigned int set, unsigned int clear)

        {

        struct uart_state *state = tty->driver_data;

        struct uart_port *uport = state->uart_port;

        struct tty_port *port = &state->port;

        int ret = -EIO;

        mutex_lock(&port->mutex); //獲取對tty_port操作的鎖

        if ((!file || !tty_hung_up_p(file)) &&

        !(tty->flags & (1 << TTY_IO_ERROR))) {

        uart_update_mctrl(uport, set, clear); //獲取modem控制

        ret = 0;

        }

        mutex_unlock(&port->mutex); //釋放對tty_port操作的鎖

        return ret;

        }

        上面uart_tiocmset中調(diào)用了uart_update_mctrl(uport, set, clear)函數(shù),它最終是通過調(diào)用port->ops->set_mctrl(port, port->mctrl)完成,而set_mctrl在UART層的uart_ops實(shí)現(xiàn)了。

        綜上,TTY層的ops中的uart_tiocmget和uart_tiocmset其實(shí)最終是調(diào)用UART層uart_ops中的get_mctrl和set_mctrl實(shí)現(xiàn)的。

        當(dāng)用戶在tty設(shè)備節(jié)點(diǎn)上進(jìn)行ioctl調(diào)用時,tty_operations中的ioctl()函數(shù)會被tty核心調(diào)用。我們接下來看看struct tty_operations uart_ops下的.ioctl也就是uart_ioctl。

        static int uart_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd,

        unsigned long arg)

        {

        struct uart_state *state = tty->driver_data;

        struct tty_port *port = &state->port;

        void __user *uarg = (void __user *)arg;

        int ret = -ENOIOCTLCMD;

        switch (cmd) { //這些ioctl不依賴硬件

        case TIOCGSERIAL: //獲得串口線信息

        ret = uart_get_info(state, uarg);

        break;

        case TIOCSSERIAL: //設(shè)置串口線信息

        ret = uart_set_info(state, uarg);

        break;

        case TIOCSERCONFIG: //自動配置

        ret = uart_do_autoconfig(state);

        break;

        case TIOCSERGWILD:

        case TIOCSERSWILD:

        ret = 0;

        break;

        }

        if (ret != -ENOIOCTLCMD)

        goto out;

        if (tty->flags & (1 << TTY_IO_ERROR)) {

        ret = -EIO;

        goto out;

        }

        switch (cmd) { //這些ioctl依賴硬件

        case TIOCMIWAIT: //等待MSR改變

        ret = uart_wait_modem_status(state, arg);

        break;

        case TIOCGICOUNT: //獲得中斷計數(shù)

        ret = uart_get_count(state, uarg);

        break;

        }

        if (ret != -ENOIOCTLCMD)

        goto out;

        mutex_lock(&port->mutex);

        if (tty_hung_up_p(filp)) {

        ret = -EIO;

        goto out_up;

        }

        switch (cmd) { //這些ioctl依賴硬件,并且需要保護(hù),房子tty被掛起

        case TIOCSERGETLSR: //獲得這個tty設(shè)備的線路狀態(tài)寄存器LSR的值

        ret = uart_get_lsr_info(state, uarg);

        break;

        default: {

        struct uart_port *uport = state->uart_port;

        if (uport->ops->ioctl)

        ret = uport->ops->ioctl(uport, cmd, arg);

        break;

        }

        }

        out_up:

        mutex_unlock(&port->mutex);

        out:

        return ret;

        }

        當(dāng)TTY核心想知道由TTY驅(qū)動程序提供的可用寫入緩沖區(qū)的大小時,就會調(diào)用write_room。在清空寫緩沖區(qū),或者調(diào)用write函數(shù)向緩沖區(qū)添加數(shù)據(jù)時,該值是變化的。接下來我們看看TTY層ops中write_room,也就是uart_write_room。跟蹤發(fā)現(xiàn)其實(shí)這個函數(shù)實(shí)現(xiàn)主要是把緩沖區(qū)頭尾相減得到剩余空間大小。

        除了write_room外,還有其他一些緩沖函數(shù),例如TTY層ops中chars_in_buffer,也就是uart_chars_in_buffer,當(dāng)tty核心在tty驅(qū)動程序的寫緩沖區(qū)中還有多少個需要傳輸?shù)淖址麜r調(diào)用該函數(shù)。

        除此之外TTY層ops中還有三個回調(diào)函數(shù),用來刷新驅(qū)動程序保存的任何數(shù)據(jù),并不一定要實(shí)現(xiàn),但是如果tty驅(qū)動程序能在發(fā)送給硬件前緩沖數(shù)據(jù),還是推薦實(shí)現(xiàn)它們的,它們分別是flush_buffer,wait_until_sent,flush_buffer。

        回顧一下,我們在TTY層的ops中,主要講了write,set_termiox, tiocmget,tiocmset,ioctl,五個函數(shù),還簡單介紹了write_room,chars_in_buffer,flush_buffer,wait_until_sent,flush_buffer五個函數(shù)。到目前為止,我們已經(jīng)分析好了uart_register_driver函數(shù),現(xiàn)在該分析uart_add_one_port函數(shù)了。

        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;

        BUG_ON(in_interrupt());

        if (uport->line >= drv->nr)

        return -EINVAL;

        state = drv->state + uport->line;

        port = &state->port;

        mutex_lock(&port_mutex);

        mutex_lock(&port->mutex);

        if (state->uart_port) {

        ret = -EINVAL;

        goto out;

        }

        state->uart_port = uport;

        state->pm_state = -1;

        uport->cons = drv->cons;

        uport->state = state;

        //如果這個端口是控制臺,那么這個鎖就已經(jīng)初始化了

        if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {

        spin_lock_init(&uport->lock);

        lockdep_set_class(&uport->lock, &port_lock_key);

        }

        uart_configure_port(drv, state, uport); //配置端口

        tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);//注冊端口

        if (likely(!IS_ERR(tty_dev))) {

        device_init_wakeup(tty_dev, 1);

        device_set_wakeup_enable(tty_dev, 0);

        } else

        printk(KERN_ERR "Cannot register tty device on line %dn",

        uport->line);

        //確保UPF_DEAD沒有被置位

        uport->flags &= ~UPF_DEAD;

        out:

        mutex_unlock(&port->mutex);

        mutex_unlock(&port_mutex);

        return ret;

        }

        對于uart_add_one_port,我們發(fā)現(xiàn)其中最核心的一句代碼就是tty_register_device,僅有tty_driver是不夠的,驅(qū)動必須依附于設(shè)備,tty_register_device函數(shù)用于注冊關(guān)聯(lián)于tty_driver的設(shè)備。

        總結(jié)下,TTY層的uart_register_driver和uart_register_port最終調(diào)用線路規(guī)程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的實(shí)現(xiàn)在線路規(guī)程中。

        對于TTY驅(qū)動主要涉及如下幾個重要結(jié)構(gòu)體,struct tty_struct包含了和打開的tty相關(guān)的所有狀態(tài)信息。其中一個重要的成員就是struct tty_bufhead buf,它是數(shù)據(jù)收集和處理機(jī)制的中樞,其定義如下

        struct tty_bufhead {

        struct delayed_work work;

        spinlock_t lock;

        struct tty_buffer *head;

        struct tty_buffer *tail;

        struct tty_buffer *free;

        int memory_used;

        };

        另一個重要結(jié)構(gòu)體是struct tty_driver,它規(guī)定了tty驅(qū)動程序和高層之間的編程接口。在我們這個TTY層,由uart_register_driver下的tty_register_driver注冊入內(nèi)核,其中這個結(jié)構(gòu)體中的成員部分是通過拷貝uart_driver中的參數(shù)得到。

        好了,對于TTY層驅(qū)動,一般而言,我們需要完成如下兩個任務(wù):

        其一,終端設(shè)備驅(qū)動模塊的加載函數(shù)和卸載函數(shù),完成注冊和注銷tty_driver,初始化和釋放終端設(shè)備對應(yīng)的tty_driver結(jié)構(gòu)體成員和硬件資源。

        其二,實(shí)現(xiàn)tty_operations結(jié)構(gòu)體中的一系列成員函數(shù),主要的是實(shí)現(xiàn)open()、close()、 write()、 tiocmget()、 tiocmset()。



        關(guān)鍵詞: Linux串口總線驅(qū)

        評論


        技術(shù)專區(qū)

        關(guān)閉
        主站蜘蛛池模板: 鄂托克旗| 铁岭县| 洱源县| 梅河口市| 长治县| 山阳县| 庐江县| 灵台县| 宝清县| 吉水县| 天等县| 祁连县| 应用必备| 铁力市| 即墨市| 利津县| 葫芦岛市| 舟山市| 呼伦贝尔市| 安仁县| 巩义市| 芮城县| 军事| 平远县| 高邮市| 股票| 娄底市| 南和县| 阆中市| 大城县| 犍为县| 房产| 新余市| 万山特区| 定兴县| 永顺县| 稻城县| 织金县| 周至县| 永德县| 通海县|