linux驱动模型概述

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_registerplatform_driver_probe来注册驱动程序 。

通过id_table这种方式有什么好处呢,如果只是简单的比较name字段是否相同,那么一个驱动只能支持特定的一个设备,而如果通过id_table的方式呢,一个驱动可以支持很多个设备,而它们只是name字段不同而已。


2.2 平台设备

对于平台驱动程序,在驱动程序和设备匹配之前,struct platform_devicestatic structplatform_driver.driver.namename字段相同以匹配上。

/* 设备 */
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_devicesplatform_driver_registerplatform_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]
};

相关设备树的语法在这不做细说,可以参考官方文档或者相关书籍。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值