新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > Linux下的串口總線驅動(三)

        Linux下的串口總線驅動(三)

        作者: 時間:2016-11-22 來源:網絡 收藏
        五.線路規程內核代碼

        底層的物理驅動程序和tty驅動程序負責從硬件上收發數據,而線路規程則負責處理這些數據,并在用戶空間和內核空間知覺傳遞數據。打開串行端口時系統默認的線路規程是N_TTY,它實現終端I/O處理。線路規程也實現通過串行傳輸協議實現的網絡接口,PPP(N_PPP),SLIP(串行線路網際協議)(N_SLIP),紅外數據(N_IRDA),藍牙主機控制接口(N_HCI)。

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

        我們在TTY層uart_register_driver函數里初始化termios的時候用到tty_std_termios,這個是線路的原始設置,具體定義如下

        struct ktermios tty_std_termios = {

        .c_iflag = ICRNL | IXON, //輸入標志

        .c_oflag = OPOST | ONLCR, //輸出標志

        .c_cflag = B38400 | CS8 | CREAD | HUPCL, //控制標志

        .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |

        ECHOCTL | ECHOKE | IEXTEN, //本地標志

        .c_cc = INIT_C_CC, //字符控制

        .c_ispeed = 38400, //輸入速率

        .c_ospeed = 38400 //輸出速率

        };

        如果需要對線路原始設置的部分加以修改,則可以添加其他操作。主要分為內核空間修改線路規程和用戶空間修改線路規程兩個途徑。內核空間修改線路規程很簡單,只需要對需要修改項進行重新賦值就行了,對于用戶空間修改線路規程我們來講解下。

        假如用戶空間程序打開和觸摸控制器相連的串行端口時,N_TCH將被綁定到底層的串行驅動程序,但假如你想編寫程序清空觸摸控制器接收的所有原始數據而不處理它,那你就需要修改線路規程為N_TTY并清空所有接收的數據的程序。用戶空間修改線程代碼如下

        fd=open(“/dev/ttys0”,O_RDONLY|O_NOCTTY);

        ldisc=N_TTY;

        ioctl(fd,TIOCSETD,&ldisc);

        好了,前面我們從應用角度分析了線路規程的設置,現在我們從理論角度,深度剖析下線路規程是怎么實現的吧。

        在TTY層我們講過TTY層的uart_register_driver和uart_register_port最終調用線路規程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的實現在線路規程中tty_io.c中實現的,我們可以打開tty_io.c這個文件。

        首先我們看tty_init函數,在tty_init函數中執行了cdev_init(&tty_cdev, &tty_fops)一行代碼,說明向內核中添加了一個cdev設備,我們跟蹤tty_fops。

        static const struct file_operations tty_fops = {

        .llseek = no_llseek,

        .read = tty_read,

        .write = tty_write,

        .poll = tty_poll,

        .unlocked_ioctl = tty_ioctl,

        .compat_ioctl = tty_compat_ioctl,

        .open = tty_open,

        .release = tty_release,

        .fasync = tty_fasync,

        };

        這個結構體我們很熟悉,在字符設備中,我們就是使用的這個結構體吧。那說明我們用戶進行open,read,write,ioctl等對串口操作時,第一步調用就是這里的open,read,write,ioctl。那么我們就看看怎么由這里的open,read,write,ioctl跟TTY層,UART層的open,read,write,ioctl相聯系的。

        我們就來看看這個open吧

        static int __tty_open(struct inode *inode, struct file *filp)

        {

        struct tty_struct *tty = NULL;

        int noctty, retval;

        struct tty_driver *driver;

        int index;

        dev_t device = inode->i_rdev; //獲取目標設備的設備號

        unsigned saved_flags = filp->f_flags;

        nonseekable_open(inode, filp);

        retry_open:

        noctty = filp->f_flags & O_NOCTTY;

        index = -1;

        retval = 0;

        mutex_lock(&tty_mutex);

        if (device == MKDEV(TTYAUX_MAJOR, 0)) { //當前進程的控制終端,/dev/tty

        tty = get_current_tty();

        if (!tty) { //該進程還沒有控制終端

        mutex_unlock(&tty_mutex);

        return -ENXIO;

        }

        driver = tty_driver_kref_get(tty->driver); //如果打開的確實是控制終端的處理

        index = tty->index;

        filp->f_flags |= O_NONBLOCK;

        tty_kref_put(tty);

        goto got_driver;

        }

        #ifdef CONFIG_VT

        if (device == MKDEV(TTY_MAJOR, 0)) { //當前虛擬控制臺,/dev/tty0

        extern struct tty_driver *console_driver;

        driver = tty_driver_kref_get(console_driver);

        index = fg_console; // fg_console表示當前的前臺控制臺

        noctty = 1; //因為虛擬控制臺原來就打開,故置位

        goto got_driver;

        }

        #endif

        if (device == MKDEV(TTYAUX_MAJOR, 1)) { //用于外接的控制臺,/dev/console

        struct tty_driver *console_driver = console_device(&index);

        if (console_driver) {

        driver = tty_driver_kref_get(console_driver);

        if (driver) {

        filp->f_flags |= O_NONBLOCK;

        noctty = 1;

        goto got_driver;

        }

        }

        mutex_unlock(&tty_mutex);

        return -ENODEV;

        }

        driver = get_tty_driver(device, &index);

        if (!driver) {

        mutex_unlock(&tty_mutex);

        return -ENODEV;

        }

        got_driver:

        if (!tty) {

        //檢查我們是否重復打開一個已經存在的tty

        tty = tty_driver_lookup_tty(driver, inode, index);

        if (IS_ERR(tty)) {

        mutex_unlock(&tty_mutex);

        return PTR_ERR(tty);

        }

        }

        if (tty) {

        retval = tty_reopen(tty); //重新打開

        if (retval)

        tty = ERR_PTR(retval);

        } else

        tty = tty_init_dev(driver, index, 0); //初始化,為需要打開的終端建立tty_struct結構體

        mutex_unlock(&tty_mutex);

        tty_driver_kref_put(driver);

        if (IS_ERR(tty))

        return PTR_ERR(tty);

        filp->private_data = tty; //設置私有數據

        file_move(filp, &tty->tty_files);

        check_tty_count(tty, "tty_open");

        if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&

        tty->driver->subtype == PTY_TYPE_MASTER)

        noctty = 1;

        #ifdef TTY_DEBUG_HANGUP

        printk(KERN_DEBUG "opening %s...", tty->name);

        #endif

        if (!retval) {

        if (tty->ops->open)

        retval = tty->ops->open(tty, filp); //調用tty_operations下的open函數

        else

        retval = -ENODEV;

        }

        filp->f_flags = saved_flags;

        if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&

        !capable(CAP_SYS_ADMIN))

        retval = -EBUSY;

        if (retval) {

        #ifdef TTY_DEBUG_HANGUP

        printk(KERN_DEBUG "error %d in opening %s...", retval,

        tty->name);

        #endif

        tty_release_dev(filp);

        if (retval != -ERESTARTSYS)

        return retval;

        if (signal_pending(current))

        return retval;

        schedule();

        //需要復位f_op,以防掛起

        if (filp->f_op == &hung_up_tty_fops)

        filp->f_op = &tty_fops;

        goto retry_open;

        }

        mutex_lock(&tty_mutex);

        spin_lock_irq(¤t->sighand->siglock);

        if (!noctty &&

        current->signal->leader &&

        !current->signal->tty &&

        tty->session == NULL)

        __proc_set_tty(current, tty);

        spin_unlock_irq(¤t->sighand->siglock);

        mutex_unlock(&tty_mutex);

        return 0;

        }

        在上面這個open函數中,我們主要涉及為需要打開的終端建立tty_struct結構體而執行的一條代碼tty_init_dev(driver, index, 0),同時看到了怎么調用tty_operations下的open函數。在此我們好好看看tty_init_dev(driver, index, 0)的內幕吧。

        struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,

        int first_ok)

        {

        struct tty_struct *tty;

        int retval;

        //檢查是否pty被多次打開

        if (driver->subtype == PTY_TYPE_MASTER &&

        (driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok)

        return ERR_PTR(-EIO);

        if (!try_module_get(driver->owner))

        return ERR_PTR(-ENODEV);

        tty = alloc_tty_struct(); //分配tty_struct結構體

        if (!tty)

        goto fail_no_mem;

        initialize_tty_struct(tty, driver, idx); //初始化tty_struct結構體

        retval = tty_driver_install_tty(driver, tty);

        if (retval < 0) {

        free_tty_struct(tty);

        module_put(driver->owner);

        return ERR_PTR(retval);

        }

        retval = tty_ldisc_setup(tty, tty->link); //調用ldisc下open

        if (retval)

        goto release_mem_out;

        return tty;

        fail_no_mem:

        module_put(driver->owner);

        return ERR_PTR(-ENOMEM);

        release_mem_out:

        if (printk_ratelimit())

        printk(KERN_INFO "tty_init_dev: ldisc open failed, "

        "clearing slot %dn", idx);

        release_tty(tty, idx);

        return ERR_PTR(retval);

        }

        我們繼續跟蹤tty_init_dev中的initialize_tty_struct(tty, driver, idx)函數實現吧

        void initialize_tty_struct(struct tty_struct *tty,

        struct tty_driver *driver, int idx)

        {

        memset(tty, 0, sizeof(struct tty_struct));

        kref_init(&tty->kref);

        tty->magic = TTY_MAGIC;

        tty_ldisc_init(tty); // tty_ldisc的初始化,

        tty->session = NULL;

        tty->pgrp = NULL;

        tty->overrun_time = jiffies;

        tty->buf.head = tty->buf.tail = NULL;

        tty_buffer_init(tty);

        mutex_init(&tty->termios_mutex);

        mutex_init(&tty->ldisc_mutex);

        init_waitqueue_head(&tty->write_wait);

        init_waitqueue_head(&tty->read_wait);

        INIT_WORK(&tty->hangup_work, do_tty_hangup);

        mutex_init(&tty->atomic_read_lock);

        mutex_init(&tty->atomic_write_lock);

        mutex_init(&tty->output_lock);

        mutex_init(&tty->echo_lock);

        spin_lock_init(&tty->read_lock);

        spin_lock_init(&tty->ctrl_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);

        }

        我們繼續跟蹤initialize_tty_struct函數中的tty_ldisc_init(tty)函數

        void tty_ldisc_init(struct tty_struct *tty)

        {

        struct tty_ldisc *ld = tty_ldisc_get(N_TTY); //設置線路規程N_TTY

        if (IS_ERR(ld))

        panic("n_tty: init_tty");

        tty_ldisc_assign(tty, ld);

        }

        在tty_ldisc_init里,我們終于找到了N_TTY,這是默認的線路規程。

        繼續看tty_init_dev,我們發現retval = tty_ldisc_setup(tty, tty->link);繼續跟蹤

        int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)

        {

        struct tty_ldisc *ld = tty->ldisc;

        int retval;

        retval = tty_ldisc_open(tty, ld);

        if (retval)

        return retval;

        if (o_tty) {

        retval = tty_ldisc_open(o_tty, o_tty->ldisc);

        if (retval) {

        tty_ldisc_close(tty, ld);

        return retval;

        }

        tty_ldisc_enable(o_tty);

        }

        tty_ldisc_enable(tty);

        return 0;

        }

        然后我們跟蹤tty_ldisc_setup函數中的tty_ldisc_open函數

        static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)

        {

        WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));

        if (ld->ops->open)

        return ld->ops->open(tty); //打開ldisc下的open,進行鏈路的初始化

        return 0;

        }

        在tty_ldisc_open這里已經通過相應tty_ldisc結構所提供的函數指針調用了與鏈路規則有關的open操作。

        前面tty_open函數中也有個open調用,這是為什么呢?因為具體的終端類型也可能有需要在打開文件時加以調用的函數。對于用作控制臺的虛擬終端,其tty_driver數據結構為console_driver,其open函數則為con_open()。

        綜上,我們可以把tty_io.c看作是tty核心,然后tty核心里調用ldisc中的open,ldisc里的open調用tty層的open,tty層的open調用uart層的open,最終實現打開操作。

        最后再次總結如下幾點:

        其一,內核中有一個鏈表tty_drivers,系統在初始化時,或者安裝某種終端設備的驅動模塊時,通過函數tty_register_driver()將各種終端設備的tty_driver結構登記到這個鏈表中。每當新打開一個終端設備時,就要根據其設備號通過函數get_tty_driver()在這個鏈表中找到的tty_driver結構,并把它復制到具體的tty_struct結構體中。

        其二,當新創建一個tty_struct結構時,就把相應的tty_ldisc結構復制到tty_struct結構體中的這個成員中。

        其三,另外內核中的一個重要指針termios,這個數據結構在某種程度上可以看作是對tty_ldisc結構的補充,它規定了對接口上輸入和輸出的每個字符所作的處理以及傳輸的速度,即波特率。



        關鍵詞: Linux串口總線驅

        評論


        技術專區

        關閉
        主站蜘蛛池模板: 太和县| 建水县| 崇文区| 山阴县| 镇赉县| 沅陵县| 安康市| 富蕴县| 绥棱县| 宜春市| 诸城市| 乐平市| 商河县| 井研县| 平果县| 太湖县| 津市市| 嘉定区| 逊克县| 台南市| 什邡市| 吉安市| 澳门| 建始县| 绵竹市| 南乐县| 滦南县| 淳化县| 远安县| 缙云县| 开阳县| 沧州市| 宜春市| 玉树县| 左权县| 休宁县| 方正县| 池州市| 乐安县| 枞阳县| 宁陵县|