linux tty连wifi,打通linux的tty驱动的数据链路

一、首先把tty驱动在linux中的分层结构理清楚:

a4c26d1e5885305701be709a3d33442f.png

自上而下分为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驱动层。

a4c26d1e5885305701be709a3d33442f.png

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缓冲区的数据写进串口发送数据寄存器,也就是数据到达硬件层。到此,写操作的数据链路也连通。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值