新聞中心

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

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

作者: 時間:2016-11-22 來源:網(wǎng)絡 收藏
五.線路規(guī)程內核代碼

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

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

我們在TTY層uart_register_driver函數(shù)里初始化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 //輸出速率

};

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

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

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

ldisc=N_TTY;

ioctl(fd,TIOCSETD,&ldisc);

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

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

首先我們看tty_init函數(shù),在tty_init函數(shù)中執(zhí)行了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相聯(lián)系的。

我們就來看看這個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) {

//檢查我們是否重復打開一個已經(jīng)存在的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; //設置私有數(shù)據(jù)

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函數(shù)

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函數(shù)中,我們主要涉及為需要打開的終端建立tty_struct結構體而執(zhí)行的一條代碼tty_init_dev(driver, index, 0),同時看到了怎么調用tty_operations下的open函數(shù)。在此我們好好看看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);

}

我們繼續(xù)跟蹤tty_init_dev中的initialize_tty_struct(tty, driver, idx)函數(shù)實現(xiàn)吧

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

}

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

void tty_ldisc_init(struct tty_struct *tty)

{

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

if (IS_ERR(ld))

panic("n_tty: init_tty");

tty_ldisc_assign(tty, ld);

}

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

繼續(xù)看tty_init_dev,我們發(fā)現(xiàn)retval = tty_ldisc_setup(tty, tty->link);繼續(xù)跟蹤

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函數(shù)中的tty_ldisc_open函數(shù)

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這里已經(jīng)通過相應tty_ldisc結構所提供的函數(shù)指針調用了與鏈路規(guī)則有關的open操作。

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

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

最后再次總結如下幾點:

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

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

其三,另外內核中的一個重要指針termios,這個數(shù)據(jù)結構在某種程度上可以看作是對tty_ldisc結構的補充,它規(guī)定了對接口上輸入和輸出的每個字符所作的處理以及傳輸?shù)乃俣龋床ㄌ芈省?/p>



關鍵詞: Linux串口總線驅

評論


技術專區(qū)

關閉