打通linux的tty驅(qū)動(dòng)的數(shù)據(jù)鏈路
一、首先把tty驅(qū)動(dòng)在linux中的分層結(jié)構(gòu)理清楚:
自上而下分為TTY核心層、TTY線路規(guī)程、TTY驅(qū)動(dòng)。
二、TTY核心層與線路規(guī)程層分析
用戶空間的程序直接對(duì)tty核心層進(jìn)行讀寫等相關(guān)操作,在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 drivern");
device_create(tty_class,NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
………
}
以上的一段初始化代碼可以獲取以下信息:
注冊(cè)了一個(gè)字符驅(qū)動(dòng),用戶空間操作對(duì)應(yīng)到tty_fops結(jié)構(gòu)體里的函數(shù):
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,
};
對(duì)于字符設(shè)備驅(qū)動(dòng),我們知道,讀寫操作一一對(duì)應(yīng)于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是根據(jù)設(shè)備號(hào)device,通過查找tty_drivers全局鏈表來查找tty_driver.
tty_init_dev是初始化一個(gè)tty結(jié)構(gòu)體:
tty->driver= driver;
tty->ops= driver->ops;
并建立線路規(guī)程:
ldops= tty_ldiscs[N_TTY];
ld->ops= ldops;
tty->ldisc= ld;
其實(shí)tty_ldiscs[N_TTY]在console_init中確定,該函數(shù)在內(nèi)核啟動(dòng)的時(shí)候調(diào)用。
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的調(diào)用,實(shí)則上就是應(yīng)用driver->ops->open.這樣,我們就從tty核心層到tty驅(qū)動(dòng)層了。
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);
………
}
從以上這個(gè)函數(shù)里,可以看到tty_write調(diào)用路線規(guī)程的write函數(shù),所以,我們來看ldisc中的write函數(shù)是怎樣的。經(jīng)過一些操作后,最終調(diào)用:
tty->ops->flush_chars(tty);
tty->ops->write(tty,b, nr);
顯然,這兩個(gè)函數(shù),都調(diào)用了tty_driver操作函數(shù),因?yàn)樵谥暗膖ty_open函數(shù)中有了tty->ops=driver-> ops這樣的操作。那么這個(gè)tty_driver是怎樣的呢,在TTY系統(tǒng)中,tty_driver是需要在驅(qū)動(dòng)層注冊(cè)的。注冊(cè)的時(shí)候就初始化了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里,也調(diào)用了線路規(guī)程的對(duì)應(yīng)read函數(shù)。不同的是,這個(gè)read沒有調(diào)用tty_driver里ops的read,而是這樣:
uncopied= copy_from_read_buf(tty, &b, &nr);
uncopied+= copy_from_read_buf(tty, &b, &nr);
從函數(shù)名來看copy_from_read_buf,就是從read_buf這個(gè)緩沖區(qū)拷貝數(shù)據(jù)。實(shí)際上是在tty->read_buf的末尾 tty->read_tail中讀取數(shù)據(jù)。那么read_buf中的數(shù)據(jù)是怎么來的呢?猜想,那肯定是tty_driver干的事了。
tty_ioctl:
long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
……
switch(cmd) {
case… …… :[!--empirenews.page--]
………
}
}
就是根據(jù)cmd的值進(jìn)行相關(guān)操作,有對(duì)線路規(guī)程操作的,有直接通過tty_driver操作的。
三、TTY驅(qū)動(dòng)層分析
接下來看,TTY驅(qū)動(dòng)層是怎樣的:
TTY驅(qū)動(dòng)層是根據(jù)不同的硬件操作來完成相應(yīng)的操作,這里我們以串口為例。
串口作為一個(gè)標(biāo)準(zhǔn)的設(shè)備,把共性的分離出來,就成了uart層,特性成了serial層。
主要是serial層作為一個(gè)驅(qū)動(dòng)模塊加載。以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是在配置內(nèi)核的時(shí)候定義的,表示支持串口的個(gè)數(shù)。
static struct uart_driver serial8250_reg = {
。owner =THIS_MODULE,
。driver_name ="serial",
。dev_name ="ttyS",
。major =TTY_MAJOR,
。minor =64,
。cons =SERIAL8250_CONSOLE,
};
在驅(qū)動(dòng)層里有幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu):
structuart_driver;
structuart_state ;
structuart_port;
structtty_driver;
structtty_port;
實(shí)際上,理清了這幾個(gè)結(jié)構(gòu)體的關(guān)系,也就理清了TTY驅(qū)動(dòng)層。
uart_register_driver:
這個(gè)函數(shù)主要是向TTY核心層注冊(cè)一個(gè)TTY驅(qū)動(dòng):
retval= tty_register_driver(normal);
其中normal是tty_driver.
另外,還會(huì)對(duì)tty_driver和uart_driver之間進(jìn)行某些賦值和指針連接。我們最關(guān)心的是,給tty_driver初始化了操作函數(shù)uart_ops,這樣,在tty核心層就可以通過uart_ops來對(duì)UART層進(jìn)行操作。
serial8250_register_ports:
最重要的兩個(gè)函數(shù):serial8250_isa_init_ports和uart_add_one_port
serial8250_isa_init_ports主要的工作是初始化uart_8250_port:開啟定時(shí)器和初始化uart_port.
uart_add_one_port顧名思議,就是為uart_driver增加一個(gè)端口,在uart_driver里的state指向NR個(gè)slot, 然后,這個(gè)函數(shù)的主要工作就是為slot增加一個(gè)port.這樣,uart_driver就可以通過port對(duì)ops操作函數(shù)集進(jìn)行最底層的操作。
現(xiàn)在來分析下連接部分,也就是tty_driver如何工作,如何連接tty核心層(或者ldisc層)和串口層uart_port.關(guān)于操作部分主要是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);
………
}
調(diào)用了uart_port的操作函數(shù)ops的startup,在這個(gè)函數(shù)里作了一些串口初始化的工作,其中有申請(qǐng)接收數(shù)據(jù)中斷或建立超時(shí)輪詢處理。
在startup里面申請(qǐng)了接收數(shù)據(jù)中斷,那么這個(gè)中斷服務(wù)程序就跟讀操作密切相關(guān)了,從tty核心層的讀操作可知,接收到的數(shù)據(jù)一定是傳送到read_buf中的?,F(xiàn)在來看是中斷服務(wù)程序。
調(diào)用receive_chars來接收數(shù)據(jù),在receive_chars中,出現(xiàn)了兩個(gè)傳輸數(shù)據(jù)的函數(shù):
tty_insert_flip_char和tty_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);
}
當(dāng)當(dāng)前的tty_buffer空間不夠時(shí)調(diào)用tty_insert_flip_string_flags,在這個(gè)函數(shù)里會(huì)去查找下一個(gè)tty_buffer,并將數(shù)據(jù)放到下一個(gè)tty_buffer的char_buf_ptr里。
那么char_buf_ptr的數(shù)據(jù)怎樣與線路規(guī)程中的read_buf關(guān)聯(lián)的呢,我們看,在初始化tty_buffer的時(shí)候,也就是在tty_buffer_init函數(shù)中:
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);
}
在函數(shù)的最后,初始化了一個(gè)工作隊(duì)列。
而這個(gè)隊(duì)列在什么時(shí)候調(diào)度呢,在驅(qū)動(dòng)層里receive_chars的最后調(diào)用了tty_flip_buffer_push這個(gè)函數(shù)。
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數(shù)據(jù)到tty_buffer的時(shí)候有兩種方式,一種是flush_to_ldisc,另一種就是調(diào)度tty緩沖區(qū)的工作隊(duì)列。
flush_to_ldisc是隊(duì)列調(diào)用的函數(shù):
static void flush_to_ldisc(struct work_struct *work)[!--empirenews.page--]
{
……
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);
………
}
……
}
這個(gè)函數(shù)主要的功能是,從tty_buffer中找到數(shù)據(jù)緩沖區(qū)char_buf_ptr,并將這個(gè)緩沖區(qū)指針傳遞給線路規(guī)程的操作函數(shù)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條件成立,明顯地是拷貝數(shù)據(jù)進(jìn)tty的read_buf;進(jìn)入else,在正常的狀態(tài)下會(huì)調(diào)用n_tty_receive_char,然后會(huì)調(diào)用put_tty_queue,在這個(gè)函數(shù)里最終還是把數(shù)據(jù)拷貝到tty的read_buf中。
到此,tty驅(qū)動(dòng)的讀操作數(shù)據(jù)鏈路基本上連通了。
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;
}
上面代碼的意思是把要寫的數(shù)據(jù)拷貝到state的緩沖區(qū)里。然后調(diào)用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);
}
調(diào)用了uart_port的操作函數(shù)集的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中會(huì)把state->xmit緩沖區(qū)的數(shù)據(jù)寫進(jìn)串口發(fā)送數(shù)據(jù)寄存器,也就是數(shù)據(jù)到達(dá)硬件層。到此,寫操作的數(shù)據(jù)鏈路也連通。