首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

打通linux的tty驱动的数据链路2

打通linux的tty驱动的数据链路2



………
}

就是根据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)
返回列表