新闻中心

EEPW首页>嵌入式系统>设计应用> 打通linux的tty驱动的数据链路

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

作者: 时间:2016-10-08 来源:网络 收藏

………

}

就是根据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)



关键词:

评论


相关推荐

技术专区

关闭