显然,这两个函数,都调用了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里。