打通linux的tty驅動的數據鏈路

一、首先把tty驅動在linux中的分層結構理清楚:


自上而下分爲TTY核心層、TTY線路規程、TTY驅動。


二、TTY核心層與線路規程層分析

用戶空間的程序直接對tty核心層進行讀寫等相關操作,在tty_io.c中:

int__init tty_init(void)

cdev_init(&tty_cdev,&tty_fops);

if(cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||

register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty")< 0)

panic("Couldn'tregister /dev/tty driver\n");

device_create(tty_class,NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");

…...


以上的一段初始化代碼可以獲取以下信息:

註冊了一個字符驅動,用戶空間操作對應到tty_fops結構體裏的函數:

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

};

對於字符設備驅動,我們知道,讀寫操作一一對應於fops


tty_open:

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

int index;

dev_tdevice = inode->i_rdev;

structtty_driver *driver;

……

driver= get_tty_driver(device, &index);

……

tty= tty_init_dev(driver, index, 0);

……

retval= tty_add_file(tty, filp);

……

if(tty->ops->open)

retval= tty->ops->open(tty, filp);


get_tty_driver是根據設備號device,通過查找tty_drivers全局鏈表來查找tty_driver

tty_init_dev是初始化一個tty結構體:

tty->driver= driver;

tty->ops= driver->ops;

並建立線路規程:

ldops= tty_ldiscs[N_TTY];

ld->ops= ldops;

tty->ldisc= ld;


其實tty_ldiscs[N_TTY]console_init中確定,該函數在內核啓動的時候調用。

tty_register_ldisc(N_TTY,&tty_ldisc_N_TTY);

則:tty_ldiscs[N_TTY]&tty_ldisc_N_TTY


struct tty_ldisc_ops tty_ldisc_N_TTY = {

.magic = TTY_LDISC_MAGIC,

.name = "n_tty",

.open = n_tty_open,

.close = n_tty_close,

.flush_buffer = n_tty_flush_buffer,

.chars_in_buffer= n_tty_chars_in_buffer,

.read = n_tty_read,

.write = n_tty_write,

.ioctl = n_tty_ioctl,

.set_termios = n_tty_set_termios,

.poll = n_tty_poll,

.receive_buf = n_tty_receive_buf,

.write_wakeup = n_tty_write_wakeup

};

tty_add_file主要是將tty保存到file的私有變量private_data中。

tty->ops->open的調用,實則上就是應用driver->ops->open。這樣,我們就從tty核心層到tty驅動層了。


tty_write:

static ssize_t tty_write(struct file *file, const char __user *buf,

size_t count, loff_t *ppos)

{

…...

ld= tty_ldisc_ref_wait(tty);

if(!ld->ops->write)

ret= -EIO;

else

ret= do_tty_write(ld->ops->write, tty, file, buf, count);

…...

}

從以上這個函數裏,可以看到tty_write調用路線規程的write函數,所以,我們來看ldisc中的write函數是怎樣的。經過一些操作後,最終調用:

tty->ops->flush_chars(tty);

tty->ops->write(tty,b, nr);

顯然,這兩個函數,都調用了tty_driver操作函數,因爲在之前的tty_open函數中有了tty->ops=driver->ops這樣的操作。那麼這個tty_driver是怎樣的呢,在TTY系統中,tty_driver是需要在驅動層註冊的。註冊的時候就初始化了ops,也就是說,接下來的事情要看tty_driver的了。


tty_read:

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,

loff_t *ppos)

{

…...

ld= tty_ldisc_ref_wait(tty);

if(ld->ops->read)

i= (ld->ops->read)(tty, file, buf, count);

else

i= -EIO;

……

}

tty_write的一樣,在tty_read裏,也調用了線路規程的對應read函數。不同的是,這個read沒有調用tty_driveropsread,而是這樣:

uncopied= copy_from_read_buf(tty, &b, &nr);

uncopied+= copy_from_read_buf(tty, &b, &nr);

從函數名來看copy_from_read_buf,就是從read_buf這個緩衝區拷貝數據。實際上是在tty->read_buf的末尾tty->read_tail中讀取數據。那麼read_buf中的數據是怎麼來的呢?猜想,那肯定是tty_driver乾的事了。


tty_ioctl

long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

……

switch(cmd) {

case… ... :

…...

}

就是根據cmd的值進行相關操作,有對線路規程操作的,有直接通過tty_driver操作的。


三、TTY驅動層分析

接下來看,TTY驅動層是怎樣的:

TTY驅動層是根據不同的硬件操作來完成相應的操作,這裏我們以串口爲例。

串口作爲一個標準的設備,把共性的分離出來,就成了uart層,特性成了serial層。

主要是serial層作爲一個驅動模塊加載。以8250.c爲例:

static int __init serial8250_init(void)

…...

serial8250_reg.nr= UART_NR;

ret= uart_register_driver(&serial8250_reg);

…...

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

…...


#define UART_NR CONFIG_SERIAL_8250_NR_UARTS

CONFIG_SERIAL_8250_NR_UARTS是在配置內核的時候定義的,表示支持串口的個數。

static struct uart_driver serial8250_reg = {

.owner =THIS_MODULE,

.driver_name ="serial",

.dev_name ="ttyS",

.major =TTY_MAJOR,

.minor =64,

.cons =SERIAL8250_CONSOLE,

};

在驅動層裏有幾個重要的數據結構:

structuart_driver

structuart_state

structuart_port

structtty_driver

structtty_port

實際上,理清了這幾個結構體的關係,也就理清了TTY驅動層。


uart_register_driver:

這個函數主要是向TTY核心層註冊一個TTY驅動:

retval= tty_register_driver(normal);

其中normaltty_driver

另外,還會對tty_driveruart_driver之間進行某些賦值和指針連接。我們最關心的是,給tty_driver初始化了操作函數uart_ops,這樣,在tty核心層就可以通過uart_ops來對UART層進行操作。

serial8250_register_ports

最重要的兩個函數:serial8250_isa_init_portsuart_add_one_port

serial8250_isa_init_ports主要的工作是初始化uart_8250_port:開啓定時器和初始化uart_port

uart_add_one_port顧名思議,就是爲uart_driver增加一個端口,在uart_driver裏的state指向NRslot,然後,這個函數的主要工作就是爲slot增加一個port。這樣,uart_driver就可以通過portops操作函數集進行最底層的操作。

現在來分析下連接部分,也就是tty_driver如何工作,如何連接tty核心層(或者ldisc層)和串口層uart_port。關於操作部分主要是uart_ops

uart_open

staticint uart_open(struct tty_struct *tty, struct file *filp)

{

…...

retval= uart_startup(tty, state, 0);

……

}

staticint uart_startup(struct tty_struct *tty, struct uart_state *state,int init_hw)

{

……

retval= uport->ops->startup(uport);

…...

調用了uart_port的操作函數opsstartup,在這個函數裏作了一些串口初始化的工作,其中有申請接收數據中斷或建立超時輪詢處理。

startup裏面申請了接收數據中斷,那麼這個中斷服務程序就跟讀操作密切相關了,從tty核心層的讀操作可知,接收到的數據一定是傳送到read_buf中的。現在來看是中斷服務程序。

調用receive_chars來接收數據,在receive_chars中,出現了兩個傳輸數據的函數:

tty_insert_flip_chartty_flip_buffer_push

static inline int tty_insert_flip_char(struct tty_struct *tty,

unsigned char ch, char flag)

{

struct tty_buffer *tb = tty->buf.tail;

if(tb && tb->used < tb->size) {

tb->flag_buf_ptr[tb->used]= flag;

tb->char_buf_ptr[tb->used++]= ch;

return1;

}

return tty_insert_flip_string_flags(tty, &ch, &flag, 1);

}

噹噹前的tty_buffer空間不夠時調用tty_insert_flip_string_flags,在這個函數裏會去查找下一個tty_buffer,並將數據放到下一個tty_bufferchar_buf_ptr裏。

那麼char_buf_ptr的數據怎樣與線路規程中的read_buf關聯的呢,我們看,在初始化tty_buffer的時候,也就是在tty_buffer_init函數中:

void tty_buffer_init(struct tty_struct *tty)

{

spin_lock_init(&tty->buf.lock);

tty->buf.head= NULL;

tty->buf.tail= NULL;

tty->buf.free= NULL;

tty->buf.memory_used= 0;

INIT_DELAYED_WORK(&tty->buf.work,flush_to_ldisc);

}

在函數的最後,初始化了一個工作隊列。

而這個隊列在什麼時候調度呢,在驅動層裏receive_chars的最後調用了tty_flip_buffer_push這個函數。

void tty_flip_buffer_push(struct tty_struct *tty)

{

unsigned long flags;
spin_lock_irqsave(&tty->buf.lock, flags);
if (tty->buf.tail != NULL)
tty->buf.tail->commit = tty->buf.tail->used;
spin_unlock_irqrestore(&tty->buf.lock, flags);


if (tty->low_latency)
flush_to_ldisc(&tty->buf.work.work);
else
schedule_delayed_work(&tty->buf.work, 1);

}

那麼,在push數據到tty_buffer的時候有兩種方式,一種是flush_to_ldisc,另一種就是調度tty緩衝區的工作隊列。

flush_to_ldisc是隊列調用的函數:

static void flush_to_ldisc(struct work_struct *work)

{

……

while((head = tty->buf.head) != NULL) {

…...

count= head->commit – head->read;

…...

char_buf= head->char_buf_ptr + head->read;

flag_buf= head->flag_buf_ptr + head->read;

head->read+= count;

disc->ops->receive_buf(tty,char_buf,

flag_buf,count);

…...

}

……

}

這個函數主要的功能是,從tty_buffer中找到數據緩衝區char_buf_ptr,並將這個緩衝區指針傳遞給線路規程的操作函數receive_buf。再來看receive_buf

static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char*cp,

char *fp, int count)

{

……

if(tty->real_raw) {

…...

memcpy(tty->read_buf+ tty->read_head, cp, i);

…...

}else{

…...

switch(flags) {

caseTTY_NORMAL:

n_tty_receive_char(tty,*p);

break;

……

}

if(tty->ops->flush_chars)

tty->ops->flush_chars(tty);

…...

}

…...

}

從上面這段代碼可以看到,if條件成立,明顯地是拷貝數據進ttyread_buf;進入else,在正常的狀態下會調用n_tty_receive_char,然後會調用put_tty_queue,在這個函數裏最終還是把數據拷貝到ttyread_buf中。

到此,tty驅動的讀操作數據鏈路基本上連通了。


uart_write

static int uart_write(struct tty_struct *tty,

const unsigned char *buf, int count)

{

……

port= state->uart_port;

circ= &state->xmit;

……

while(1){

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

…...

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

…...

}

……

uart_start(tty);

return ret;

}

上面代碼的意思是把要寫的數據拷貝到state的緩衝區裏。然後調用uart_start

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)

port->ops->start_tx(port);

}

調用了uart_port的操作函數集的start_tx

static void serial8250_start_tx(struct uart_port *port)

{

struct uart_8250_port *up = container_of(port, struct uart_8250_port, port);

……

transmit_chars(up);

…...

}

transmit_chars中會把state->xmit緩衝區的數據寫進串口發送數據寄存器,也就是數據到達硬件層。到此,寫操作的數據鏈路也連通。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章