刚开始学习ldd3时,驱动架构以为是围绕这device,device_driver,bus,等等建立起来的。确实是这样,通过这样在内核建立了一个驱动框架去管理它们。然而却错误的理解为实际的驱动硬件的工作也是在那几个结构上,大错特错。举个例子,像块设备驱动,操作硬件工作是在gendisk结构里面实现的,通过register_disk,则可以根据disk里面的设备号在内核建立起device,sysfs,devfs里面的模型。而内核在操作一个块设备时是通过block_device,这个结构跟device,device_driver那些驱动架构的数据结构没有半毛钱关系,它只是跟disk挂钩,而这个block_device是嵌入在inode结构里面。内核真正操作设备是根据相应设备文件的inode,block_device,disk,bio,address_space等等来操作的。但这些结构不是平白无故的得来的,正式通过注册设备驱动,通过device,device_driver,bus,sysfs这些玩意在内核建立架构,在与那些真正的操作设备的block_device,disk建立起很微妙的关系。在打开一个设备时,会跟据在内核的已经注册的信息,来初始化block_device,inode等等。
所以ldd3上所述的驱动模型是上层的比较抽象的,用来管理真正的设备驱动,像device_driver,里面只包括一些probe,suspend,resume,shutdown,remove,还有一些电源管理方法,这都是一些比较上层的操作,还有一些将device,device_driver封装的像platform_driver,platform_device,pci_dev,pci_driver,等等是对设备的分类吧,易于管理。
对于每一种设备,在内核的模型和真实的硬件驱动建立起的关系都不同,但总体思路差不多。例如tty驱动。操作一个物理串口是,经过tty_core->line_disc->tty_driver,注册一个物理串口驱动时,相应的ops会赋值给tty_driver里面。例如
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;
BUG_ON(drv->state);
/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
if (!drv->state)
goto out;
normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out_kfree;
drv->tty_driver = normal;
normal->owner
= drv->owner;
normal->driver_name
= drv->driver_name;
normal->name
= drv->dev_name;
normal->major
= drv->major;
normal->minor_start
= drv->minor;
normal->type
= TTY_DRIVER_TYPE_SERIAL;
normal->subtype
= SERIAL_TYPE_NORMAL;
normal->init_termios
= tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags
= TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
port->ops = &uart_port_ops;
port->close_delay = 500;
/* .5 seconds */
port->closing_wait = 30000;
/* 30 seconds */
tasklet_init(&state->tlet, uart_tasklet_action,
(unsigned long)state);
}
retval = tty_register_driver(normal);
if (retval >= 0)
return retval;
put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}
uart_driver会调用串口的底层驱动,uart_port里面的ops是底层驱动。
uart_port是在probe过程中赋值给tty_driver里面的。
用户信息输出到串口流程,file_ops=tty_ops tty_ops-> tty_sturct->ldisc->write -> tty_driver->write.
tty_core--->tty_ldisc--->tty_driver--->uart_driver--->uart_port这条路线是不动的,封装好的对于不同的串口驱动,例如在一个xxxx.c文件里面,对于的串口的各种操作如设置,发送,接收都会封装在uart_ops里面,注册驱动后,probe函数会将uart_ops赋给上层的uart_port进而与tty核心连接起来,对于不同的设备,我们只要设计自己的xx_uart_druver,uart_port,不用管tty层。uart_port就是对应一个物理串口,包括串口的各种属性与ops,它会呗注册。
uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)--> tty_register_device(drv->tty_driver, uport->line, uport->dev).
tty线程规程以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,如PPP、Bluetooth。
tty设备驱动到串口驱动中间经过了一层serial_core
对于tty驱动层主要有几个重要的结构
serial_core实现了UART设备的通用TTY驱动层(称为串口核心层),这样UART驱动的主要任务演变成了实现serial_core中定义的一组uart_xxx接口而非tty_xxx接口,见如下的对应关系
----------------设备方法-----------------------------设备注册------------------------设备信息
------------tty_operations------------------------tty_driver---------------------------tty-struct // tty核心层定义,)
+ + +
+ + +
--------------uart_ops-----------------------------uart_driver---------------------------uart_port // 串口核心层定义serial_core.c(串口核心层实现的结构体
阅读(1210) | 评论(0) | 转发(0) |