linux驱动模型概述
linux 设备的驱动模型大致可以分为三类,也是不断发展的过程。
从传统驱动模型 --> 总线设备驱动模型 -->设备树模型。
可以参考相关的例子代码。
01 字符设备驱动例子
02 总线设备驱动模型例子
03 设备树模型例子
1. 传统驱动模型
传统驱动模型将引脚号写死,无法通过外部配置修改。
设备号的释放和分配有动态和静态两种方法
静态方法:int register_chrdev_region(dev_t first, unsigned int count, char *name); //写死了设备号
动态方法:int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char* name); //内核自动分配设备号
分配和注册字符设备
- (1)使用alloc_chrdev_region()保留一个主设备号和一定范围的次设备号。
- (2)使用class_create()创建自己的设备类,该函数在/sys/class中定义。
- (3)创建一个struct file_operation(传递给cdev_init),每一个设备都需要创建,并调用call_init和cdev_add()注册这个设备。
- (4)调用device_create()创建每个设备,并给它们一个合适的名字。这样,就可在/dev目录下创建出设备。
2. 总线设备驱动
总线设备驱动模型将驱动分开为三部分(总线,设备和驱动部分)。对应于驱动程序也是分为三个部分,平台驱动程序、平台设备和总线匹配。在本文中,平台设备是指依靠伪平台总线的设备。
处理平台设备实际上需要两个步骤。
- 注册管理设备的平台驱动程序(具有唯一的名称)。
- 注册平台设备(与驱动程序具有相同的名称)及其资源,以便内核获取设备位置。
2.1 平台驱动程序
平台驱动程序专用于不基于传统总线的设备。I2C设备或SPI设备是平台设备,但分别依赖于I2C或SPI总线,而不是平台总线。 对于平台驱动程序一切都需要手动完成。平台驱动程序必须实现probe函数,在插入模块或设备声明时,内核调用它。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
在内核中注册平台驱动程序很简单,只需在init函数中调用platform_driver_register()
或platform_driver_probe()
(模块加载时),这两个函数区别如下
- platform_driver_register():注册驱动程序并将其放入由内核维护的驱动程序列表中,以便每当发现新的匹配时就可以按需调用其probe()函数。为防止驱动程序在该列表中插入和注册,请使用下一个函数
- platform_driver_probe():调用该函数后,内核立即运行匹配循环,检查是否有平台设备名称匹配,如果匹配则调用驱动程序的probe(),这意味着设备存在;否则,驱动程序将被忽略。此方法可防止延迟探测,因为它不会在系统上注册驱动程序。在这里,probe函数被放置在__init部分,当内核启动完成时这个部分被释放,从而防止了延迟探测并减少驱动程序的内存占用。如果100%确定设备存在于系统中,请使用此方法
每个总线都有特定的宏来注册驱动程序,以下是其中的一部分
- module_platform_driver(struct platform_driver):用于平台驱动程序,专用于传统物理总线以外的设备
- module_spi_driver (struct spi_driver):用于SPI驱动程序。
- module_i2c_driver (struct i2c_driver):用于I2C驱动程序。
- module_pci_driver(struct pci_driver):用于PCI驱动程序。
- module_usb_driver(struct usb_driver):用于USB驱动程序。
- module_mdio_driver(struct mdio_driver):用于MDIO
如果不知道驱动程序需要哪个总线,因为它是平台驱动程序,所以应该使用platform_driver_register
或platform_driver_probe
来注册驱动程序 。
通过id_table这种方式有什么好处呢,如果只是简单的比较name字段是否相同,那么一个驱动只能支持特定的一个设备,而如果通过id_table的方式呢,一个驱动可以支持很多个设备,而它们只是name字段不同而已。
2.2 平台设备
对于平台驱动程序,在驱动程序和设备匹配之前,struct platform_device
和static structplatform_driver.driver.name
的name
字段相同以匹配上。
/* 设备 */
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
平台资源与平台数据上,有两种方法可以把有关设备所需的资源(IRQ、DMA、内存区域、I/O端口、总线)和数据(要传递给驱动程序的任何自定和私有数据结构)通知内核 。
- 设备配置,非设备树方法
- 设备树
非设备树方法
(1)资源
资源代表设备在硬件方面的所有特征元素,以及设备所需的所有元素,以便设置使其正常运行 ,一旦提供了资源,就需要在驱动中获取并使用它们。probe功能是获取它们的好地方
struct resource {
resource_size_t start;//资源的开始/结束位置
resource_size_t end;
const char *name;//标识或描述资源
unsigned long flags;//资源类型的掩码,如IORESOURCE_BUS
unsigned long desc;
struct resource *parent, *sibling, *child;
};
(2) 平台数据
所有类型不属于上一部分所列举资源类型的其他数据都属于这里(如GPIO),
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to its driver. */
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this device */
void *platform_data; /* Platform specific data, device core doesn't touch it */
void *driver_data; /* Driver data, set and get with dev_set/get_drvdata */
struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
const struct dma_map_ops *dma_ops;
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
u64 bus_dma_mask; /* upstream dma_mask constraint */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */
struct removed_region *removed_mem;
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
bool state_synced:1;
};
struct platform_device
都包含struct device
字段,该字段又包含struct platform_data
字段。通常,应该将数据嵌入结构中,将其传递到platform_device.device.platform_data
字段 。
(3) 声明平台设备
在函数platform_device_register()
内实现声明。
下面是一个例子将资源,平台数据和声明实现。假若要声明的平台设备需要两个gpios号码作为平台数据,一个中断号和两个内存区域作为资源。
/*
* 除IRQ或内存外的其他数据必须嵌入一个结构中,并传递到
* platform_device.device.platform_data
*/
struct my_gpios {
int reset_gpio;
int led_gpio;
};
/*平台数据*/
static struct my_gpios needed_gpios = {
.reset_gpio = 47,
.led_gpio = 41,
};
/* 资源组 */
static struct resource needed_resources[] = {
[0] = { /* 第一内存区域 */
.start = JZ4740_UDC_BASE_ADDR,
.end = JZ4740_UDC_BASE_ADDR + 0x10000 - 1,
.flags = IORESOURCE_MEM,
.name = "mem1",
},
[1] = {
.start = JZ4740_UDC_BASE_ADDR2,
.end = JZ4740_UDC_BASE_ADDR2 + 0x10000 -1,
.flags = IORESOURCE_MEM,
.name = "mem2",
},
[2] = {
.start = JZ4740_IRQ_UDC,
.end = JZ4740_IRQ_UDC,
.flags = IORESOURCE_IRQ,
.name = "mc",
},
};
/* 声明 */
static struct platform_device my_device = {
.name = "my-platform-device",
.id = 0,
.dev = {
.platform_data = &needed_gpios,
},
.resource = needed_resources,
.num_resources = ARRY_SIZE(needed_resources),
};
platform_device_register(&my_device);
设备树的方法就引出了第三种驱动模型,设备树模型。后面会细说
2.3 总线匹配
平台驱动程序和平台设备怎么关联在一起?就需要总线。
总线注册过程
start_kernel
rest_init
/*创建一个内核线程*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
kernel_init
do_basic_setup
driver_init
platform_bus_init
/*注册平台总线*/
bus_register(&platform_bus_type);
内核通过以下方式触发总线匹配循环,调用由总线驱动程序注册的核心匹配函数(platform_match),以检查是否有已注册的驱动程序与该设备匹配 ,如果没有匹配,则什么都不会发生;如果发现匹配,则内核将通知(通过netlink套接字通信机制)设备管理器(udev/mdev),由它加载(如果尚未加载)与设备匹配的驱动程序。一旦驱动程序加载完成,其probe()函数就立即执行。 总线匹配循环在每个设备或驱动程序注册时被触发 (platform_device_register, platform_add_devices,platform_driver_register, platform_driver_probe)
/*
·type:这可以是i2c、spi、acpi、of、platform、usb、pci,也可以是在include/linux/mod_devicetable.h中找到的其他任何总线。这取决于设备所在总线,或者想要使用的匹配机制
·name:这是XXX_device_id数组上的指针,用于设备匹配。对于I2C设备,结构是i2c_device_id。对于SPI设备,则应该是spi_device_id,依此类推。对于设备树的Open Firmware(开放固件,OF)匹配机制,则必须使用of_device_id
*/
#define MODULE_DEVICE_TABLE(type, name)
平台驱动和设备匹配顺序
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* 在设置driver_override时,只绑定到匹配的驱动程序 */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* OF匹配识别 */
if (of_driver_match_device(dev, drv))
return 1;
/* 尝试ACPI匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;
/* 匹配ID表,对于平台总线为platform_device_id */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL; //
/* 最后比较平台设备的platform_device.name和平台驱动platform_driver.driver.name */
return (strcmp(pdev->name, drv->name) == 0);
}
从上面的match函数可以看出平台和驱动匹配的顺序。
- (1) platform_device.driver_override 和 platform_driver.driver->name 匹配;
- (2) platform_device.dev.of_node的compatible属性 和 platform_driver.driver->of_match_table 匹配;
- (3) platform_device.name 和 platform_driver.id_table->name 匹配;
- (4) platform_device.name 和 platform_driver.driver->name 匹配;
3. 设备树
设备树驱动方式是在总线设备驱动模型的基础上进一步发展出来的,其目的是为了取代平台总线,在上述可知,平台总线驱动模型中,编写驱动程序需要实现平台设备的注册,还有驱动程序的实现。平台设备指定了资源,比如说配置的引脚编号,配置的时钟参数或者其他参数,而在驱动程序中则进行获取平台的配置参数,实现驱动的功能。驱动依赖于平台设备指定的资源。
设备树方式取代了平台设备这个操作,不需要在编写平台设备的代码,只需要实现设备树文件,同时驱动上也是同样的获取设备树的配置参数,并且实现驱动功能,这方面和总线设备驱动模型是一样的,简单地说,设备树的模型方式就是用设备树配置方式取代了平台设备的配置,驱动上还是使用相似的功能。
设备树采用json式格式化风格配置系统资源。设备树属性取值
- < > array of cellss 32位的数据
- “ ” 字符串
- 字符序列 [ ] 16进制表示一个或多个字节每个byte使用2个16进制数表示
- 三者可组合 ,用逗号分隔开
设备树同一级别的节点名字不能够相同,不同的级别可以相同;
#Devicetree node格式:
[label:]node-name[@unit-address]{
[properties definitions]
[child nodes]
};
相关设备树的语法在这不做细说,可以参考官方文档或者相关书籍。