一、首先把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_driver里ops的read,而是这样:
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);
其中normal是tty_driver。
另外,还会对tty_driver和uart_driver之间进行某些赋值和指针连接。我们最关心的是,给tty_driver初始化了操作函数uart_ops,这样,在tty核心层就可以通过uart_ops来对UART层进行操作。
serial8250_register_ports:
最重要的两个函数:serial8250_isa_init_ports和uart_add_one_port
serial8250_isa_init_ports主要的工作是初始化uart_8250_port:开启定时器和初始化uart_port。
uart_add_one_port顾名思议,就是为uart_driver增加一个端口,在uart_driver里的state指向NR个slot,然后,这个函数的主要工作就是为slot增加一个port。这样,uart_driver就可以通过port对ops操作函数集进行最底层的操作。
现在来分析下连接部分,也就是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的操作函数ops的startup,在这个函数里作了一些串口初始化的工作,其中有申请接收数据中断或建立超时轮询处理。
在startup里面申请了接收数据中断,那么这个中断服务程序就跟读操作密切相关了,从tty核心层的读操作可知,接收到的数据一定是传送到read_buf中的。现在来看是中断服务程序。
调用receive_chars来接收数据,在receive_chars中,出现了两个传输数据的函数:
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);
}
当当前的tty_buffer空间不够时调用tty_insert_flip_string_flags,在这个函数里会去查找下一个tty_buffer,并将数据放到下一个tty_buffer的char_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条件成立,明显地是拷贝数据进tty的read_buf;进入else,在正常的状态下会调用n_tty_receive_char,然后会调用put_tty_queue,在这个函数里最终还是把数据拷贝到tty的read_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缓冲区的数据写进串口发送数据寄存器,也就是数据到达硬件层。到此,写操作的数据链路也连通。