1.最初Linux驱动架构
Linux驱动会在初始化函数中向内核注册file_operations结构体,结构体里面就包含一些基本的open,close函数。Linux驱动中也会去实现这些函数。并且相对应的硬件信息也在这个驱动中。以LED为例,驱动程序中会将LED的引脚地址映射成虚拟地址,然后在open函数里面进行写操作。
当APP调用open函数的时候,就会通过一系列转换,最后调用到驱动中的open函数。(这边就不具体描述APP怎么调用到驱动中的open函数)。
弊端:
可以发现这种驱动,设备和驱动没有分离。也就是说,设备的信息是硬编码在驱动代码中的,会给驱动程序造成极大的限制。如果硬件有改动,那么必然要修改驱动代码,驱动的通用性将会非常的差。
2.总线、设备和驱动
以USB总线为例:当接入一个USB设备时,USB总线会立即感知到这件事,并去遍历所有注册在USB总线上的驱动,然后调用驱动中的一段代码来测探是否能够驱动刚插入的USB设备,如果可以,那么总线完成驱动和设备之间的绑定。
Linux设备模型为这三种对象各自定义了对应的类:
- struct bus_type代表总线
- srtuct device代表设备
- struct device_driver代表驱动
这样就将设备的硬件信息和驱动分离开来。设备专门用来描述硬件相关的信息,而驱动和设备绑定成功后,驱动负责从设备中动态获取这些资源信息,当设备的资源改变后,只是设备改变而已,驱动的代码可以不做任何修改,这就大大提高了驱动代码的通用性。
通常情况下,总线已经在内核中实现好,我们只需要写对应总线的驱动,有时候还会编写相应的设备注册代码。
3.平台设备及其驱动
platform总线:有的设备并没有对应的物理总线,比如LED、RTC和蜂鸣器等。为此,内核专门开发一种虚拟总线——platform总线,用来连接这些没有物理总线的设备或者一些不支持热插拔的设备。
平台设备是用struct platform_device结构来表示的。
4.Linux设备树
在Linux内核源码的ARM体系结构引入设备树之前,相关的BSP代码中充斥了大量的平台设备(Platform Device)代码,而这些代码大多数都是重复的。之前的内核移植工作有很大一部分工作就是在复制一份BSP代码,并修改BSP代码中和目标板中与特定硬件相关的平台设备信息。
设备树是一个描述硬件的数据结构。它只是提供了一种语言,将硬件配置从Linux内核源码中提取出来。
处理器访问硬件设备的方式
1.内存方式
2.I/O接口 寄存器
3.管脚 可以对芯片进行复位、接收来自设备的中断信号
X86平台中 I/O地址空间与内存地址空间是分开的,寄存器位于I/O空间时称为I/O端口
ARM平台中I/O地址空间与内存地址空间是统一编址的,也称I/O内存,是系统中访问速度最快的内存
//实例代码
platform_driver_register( &therm_of_driver );
static struct platform_driver therm_of_driver = {
.driver = {
.name = "temperature",
.of_match_table = therm_of_match,
},
.probe = therm_of_probe,
.remove = therm_of_remove,
};
therm_of_probe
i2c_add_driver
static struct i2c_driver g4fan_driver = {
.driver = {
.name = "therm_windtunnel",
},
.attach_adapter = do_attach,
.probe = do_probe,
.remove = do_remove,
.id_table = therm_windtunnel_id,
};
do_attach
i2c_new_probed_device
ad5064_init
ad5064_spi_register_driver
static struct spi_driver ad5064_spi_driver = {
.driver = {
.name = "ad5064",
},
.probe = ad5064_spi_probe,
.remove = ad5064_spi_remove,
.id_table = ad5064_spi_ids,
};
spi_register_driver(&ad5064_spi_driver);
sdrv->driver.bus = &spi_bus_type;
driver_register(&sdrv->driver);
ad5064_i2c_register_driver
static struct i2c_driver ad5064_i2c_driver = {
.driver = {
.name = "ad5064",
},
.probe = ad5064_i2c_probe,
.remove = ad5064_i2c_remove,
.id_table = ad5064_i2c_ids,
};
i2c_add_driver(&ad5064_i2c_driver);
i2c_register_driver
driver->driver.bus = &i2c_bus_type;
res = driver_register(&driver->driver);
driver_register
other = driver_find(drv->name, drv->bus);
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv);//bus_add_driver - Add a driver to the bus.
bus = bus_get(drv->bus);//获取总线
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
driver_attach(drv);
driver_create_file
sysfs_create_file
//probe
//驱动在probe中创建设备
ad5064_spi_probe
ad5064_probe
ret = iio_device_register(indio_dev); //创建设备
device_add
/**
* device_attach - try to attach device to a driver.
* @dev: device.
*
* Walk the list of drivers that the bus has and call
* driver_probe_device() for each pair. If a compatible
* pair is found, break out and return.
*
* Returns 1 if the device was bound to a driver;
* 0 if no matching driver was found;
* -ENODEV if the device is not registered.
*
* When called for a USB interface, @dev->parent lock must be held.
*/
device_attach
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
driver_probe_device
if (!device_is_registered(dev))
really_probe
dev->bus->probe(dev);
//总线
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type); //这里调用总线的注册函数。
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
驱动模型
//bus
struct bus_type
//device
struct device
//driver
truct device_driver
1、注册驱动
注册一个驱动,首先把驱动链入到驱动(driver)链表中,然后从设备(device)链表中逐个寻找,看有没有可以关联的设备。如果找到关联的设备,就执行probe函数。
具体流程如下:
platform_driver_register-》driver_register-》bus_add_driver-》driver_attach-》bus_for_each_dev-》__driver_attach
-》driver_probe_device-》really_probe
2、添加设备
添加一个设备,首先把设备链入到设备(device)链表中,然后从驱动(driver)链表中逐个寻找,看有没有可以关联的驱动。如果找到关联的驱动,就执行probe函数。
具体流程如下:
device_add-》bus_add_device-》bus_probe_device-》device_attach-》bus_for_each_drv-》__device_attach
-》driver_probe_device-》really_probe
一个现实的linux设备和驱动通常都需要挂接在一种总线上,比较常见的总线有USB、PCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。基于这样的背景下,2.6内核加入了platform虚拟总线。platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口。platform总线对加入到该总线的设备和驱动分别封装了两个结构体——platform_device和platform_driver。并且提供了对应的注册函数。
主机驱动和外设驱动分离
1)主机端的驱动。根据具体的IC、SPI、USB等控制器的硬件手册,操作具体的IPC、SPI、USB等控制器,产生总线的各种波形。
2)连接主机和外设的纽带。外设不直接调用主机端的驱动来产生波形,而是调一个标准的API。由这个标准的API把这个波形的传输请求间接“转发”给了具体的主机端驱动。当然,在这里,最好把关于波形的描述也以某种数据结构标准化。
3)外设端的驱动。外设接在I-C、SPI、USB这样的总线上,但是它们本身可以是触摸屏、网卡、声卡或者任意一种类型的设备。我们在相关的i2c_driver、spi_driver、usb_driver这种xxx_driver的probe () 函数中去注册它具体的类型。当这些外设要求IP℃、SPI、USB等去访问它的时候,它调用“连接主机和外设的纽带”模块的标准API。
4)板级逻辑。板级逻辑用来描述主机和外设是如何互联的,它相当于一个“路由表”。假设板子上有多个SPI控制器和多个SPI外设,那究竟谁接在谁上面管理互联关系,既不是主机端的责任,也不是外设端的责任,这属于板级逻辑的责任。这部分通常出现在arch/arm/mach-xxx下面或者arch/arm/bootldts下面。
- 驱动与设备树
platform_get_resource(pdev, IORESOURCE_IRQ, 0)
常见的资源标志就是IORESOURCE_MEM ,IORESOURCE_REG 和 IORESOURCE_IRQ 等,与设备树中的对应关系如下: