【嵌入式环境下linux内核及驱动学习笔记-(14)linux总线、设备、驱动模型之platform】
目录
- 1、新驱动架构的导入
- 1.1 传统驱动方式的痛点
- 1.2 总线设备驱动架构
- 2、platform 设备驱动
- 2.1 platform总线式驱动的架构思想
- 2.2 platform _device相关的数据类型
- 2.2.1 struct platform_device
- 2.2.2 struct platform_device_id
- 2.2.3 struct resource
- 2.2.4 struct device
- 2.3 platform_device 相关的接口函数
- 2.3.1 platform_device_register
- 2.3.2 platform_device_unregister
- 2.4 platform_driver相关的数据结构
- 2.4.1 struct plagform_driver
- 2.4.2 struct device_driver
- 2.2.5 of_device_id
- 2.5 platform_driver对应的接口函数
- 2.5.1 platform_driver_register()
- 2.5.2 platform_driver_unregister()
- 2.5.3 platform_get_resource
- 2.6 关于自动创建设备文件节点
- 2.6.1 自动创建的模板
- 2.6.2 涉及到的数据结构
- 2.6.2.1 struct class
- 2.6.3 操作函数
- 2.6.3.1 class_create
- 2.6.3.2 class_destroy
- 2.6.3.3 device_create
- 2.6.3.4 ERR_PTR
- 2.6.3.5 IS_ERR
- 2.6.3.6 PTR_ERR
- 3、platform平台总线的四种匹配模式
- 3.1第一种,名称匹配
- 3.1.1 名称匹配模板
- 3.1.2 一个用platform框架的led实例(name匹配)
- 公共文件public.h
- led-access-device.c
- led-access-driver.c
- test.c
- 测试的脚本load.sh
- 3.2 第二种,id匹配
- 3.2.1 ID匹配模板(第一、标准模板device.name 以及device.id_entry.name 与device.id_table.name)
- 3.2.1.1 匹配的一般结构
- 3.2.1.2 匹配中一对多的特殊处理
- 3.2.2 ID匹配模板(第二、device.id与driver.id_table.driver_data)
- 3.2.2.1 模板的一般结构
- 3.2.2.2 一对多的特殊处理
- 3.2.2.3 一个led实例
- 3.3第三种,设备树匹配
- 3.3.1 匹配重点
- 3.3.2 设备树匹配模式的关联框图:
- 3.3.3 实例
1、新驱动架构的导入
1.1 传统驱动方式的痛点
传统的驱动方式,驱动代码与设备数据混在一个程序文件中,这会导致开发不方便以及一些功能难以支持:
- 不支持热插拔
- 不支持一些针对所有设备的统一操作(如电源管理)
- 不能自动mknod
- 用户查看不了设备信息
- 设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动
1.2 总线设备驱动架构
\qquad 为了使得一个驱动可以在不同平台上适配同一类但资源分配不同的设备。这时需要使得设备资源信息独立于驱动,驱动不再绑定具体设备。因此这种思路下就形成了总线设备驱动模型。
\qquad 这个总线、设备、驱动模型的目的在于使驱动只管驱动,设备只管设备资源,总线负责匹配设备和驱动。驱动则以标准途径拿到板级信息,这样,驱动就可以独立于具体的设备。
2、platform 设备驱动
\qquad 在linux2.6以后的设备驱动模型中,需关心总线、设备和驱动这三个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
\qquad 在一个现实的设备中,一类外设本就是挂接在如PCI\USB\I2C\SPI等总线上。另一类则是不依附于此类总线,而是挂接在SOC内存空间。因此,在LINUX上,发明了一种虚拟的总线,称为platform总线,用于在驱动层面上来虚拟挂接这些设备与驱动。在platform虚拟总线上的设备称为platform_device,而驱动则称为platform_driver。
2.1 platform总线式驱动的架构思想
\qquad struct device来表示一个具体设备,主要提供具体设备相关的资源(如寄存器地址、GPIO、中断等等)。以面向对象的角度来看待,struct platform_device 就好比是struct device的子类。
\qquad struct device_driver来表示一个设备驱动,一个驱动可以支持多个操作逻辑相同的设备。以面向对象的角度来看待,platform_driver就好比是struct device_driver的子类。
\qquad 对于那些非依赖实体总线的设备,当构造了该设备的platform_device和驱动platform_driver后,内核就通过platform虚拟总线机制将上面的设备和驱动关联起来,进行管理和匹配。
2.2 platform _device相关的数据类型
2.2.1 struct platform_device
/include/linux/platform_device.h
struct platform_device {const char *name; 用于名称匹配int id; 设备id,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1bool id_auto;struct device dev; 设备模块必须包含该结构体u32 num_resources; 资源的数量 资源数组的元素个数struct resource *resource; 资源结构体 指向资源数组const struct platform_device_id *id_entry; 用于ID匹配struct mfd_cell *mfd_cell; /* MFD cell pointer */struct pdev_archdata archdata; /* arch specific additions */
};
以上数据结构,最主要的是
- name :(用于名称匹配时,必须与platform_driver . platform_device_id . name一致)
- dev : 父类,一些共用属性,需要提前写入devl.release回调函数。
- *resource : 设备的资源本详见下面的解释。
- *id_entry: (用于ID匹配)
2.2.2 struct platform_device_id
struct platform_device_id{char name[20]; 匹配用名称kernel_ulong_t driver_data; 需要向驱动传输的其它数据
};
2.2.3 struct resource
#include <linux/ioport.h>
struct resource {resource_size_t start; 资源起始位置 resource_size_t end; 资源结束位置const char *name; unsigned long flags; 区分资源是什么类型的 struct resource *parent, *sibling, *child; // 资源指针,可以构成链表
};
flags 指资源类型
我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ 这两种。start 和 end 的含义会随着 flags而变更,如
- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;注意不同MEM的地址值不能重叠
- flags为 IORESOURCE_IRQ 时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值
举例:
2.2.4 struct device
/include/linux/device.h
struct device {struct device *parent; 指定该设备的父设备,如果不指定(NULL),注册后的设备目录在/sys/device下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, devicecore doesn't touch it */struct dev_pm_info power;struct dev_pm_domain *pm_domain;#ifdef CONFIG_PINCTRLstruct dev_pin_info *pins;
#endif#ifdef CONFIG_NUMAint numa_node; /* NUMA node this device is close to */
#endifu64 *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. */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 memoverride */
#ifdef CONFIG_DMA_CMAstruct cma *cma_area; /* contiguous memory area for dmaallocations */
#endif/* arch specific additions */struct dev_archdata archdata;struct device_node *of_node; /* associated device tree node */struct acpi_dev_node acpi_node; /* associated ACPI 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;bool offline_disabled:1;bool offline:1;
};
2.3 platform_device 相关的接口函数
2.3.1 platform_device_register
定义在(driver\base\platform.c)
作用
platform_device_register()函数用于向内核注册一个platform设备。把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
函数原型:
#include <linux/platform_device.h>
int platform_device_register(struct platform_device *pdev)
主要的参数:
- pdev: platform_device类型,代表要注册的platform设备。
函数机制:
- 为pdev分配一个系统wide唯一的编号,并存储在pdev->id中。
- 将pdev添加到系统内部维护的platform设备列表中。
- 加载和注册pdev的platform驱动(如果已经注册)。
- 激活pdev的platform驱动的probe回调函数。
2.3.2 platform_device_unregister
作用:
platform_device_unregister()函数用于从内核中注销一个platform设备。把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release
函数原型:
#include <linux/platform_device.h>
void platform_device_unregister(struct platform_device *pdev)
主要参数:
- pdev:要注销的platform设备。
函数机制:
- 从系统内部的platform设备列表中移除pdev。
- 调用pdev的platform驱动的remove回调函数,宣告该驱动停止管理该设备。
- 释放与pdev相关的所有资源,如内存、IO端口等。
2.4 platform_driver相关的数据结构
2.4.1 struct plagform_driver
/include/linux/platform_device.h
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; 用于ID匹配能够支持的设备八字数组,用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束bool prevent_deferred_probe;
};#define to_platform_driver(drv) (container_of((drv), struct platform_driver, \driver))
2.4.2 struct device_driver
struct device_driver {const char *name; 驱动函数的名字,在对应总线的driver目录下显示struct bus_type *bus; 指定该驱动程序所操作的总线类型,必须与对应的struct device中的bus_type一样struct module *owner;const char *mod_name; /* used for built-in modules */bool suppress_bind_attrs; /* disables bind/unbind via sysfs */const struct of_device_id *of_match_table;const struct acpi_device_id *acpi_match_table;int (*probe) (struct device *dev); 探测函数int (*remove) (struct device *dev); 卸载函数,当设备从系统中删除时会调用void (*shutdown) (struct device *dev); 当系统关机时调用int (*suspend) (struct device *dev, pm_message_t state);int (*resume) (struct device *dev);const struct attribute_group **groups;const struct dev_pm_ops *pm;struct driver_private *p;
};
2.2.5 of_device_id
/include/linux/mod_devicetable.h
struct of_device_id
{char name[32];//设备名char type[32];//设备类型char compatible[128]; //用于device和driver的match,重点const void *data;
};
//用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束。用于设备树匹配
2.5 platform_driver对应的接口函数
2.5.1 platform_driver_register()
作用:
函数用于注册一个platform驱动。
platform_driver_register():
函数原型:
#include <linux/platform_driver.h>
int platform_driver_register(struct platform_driver *drv)
参数:
- drv:要注册的platform_driver结构体。
机制:
- 将drv添加到系统内核维护的platform驱动列表中。
- 遍历当前注册的所有platform设备,查找和drv匹配的设备。
- 对每个找到的设备,调用drv的probe回调函数。
- 如果drv->probe返回成功,则drv开始管理这个platform设备。
2.5.2 platform_driver_unregister()
作用
platform_driver_unregister()函数用于注销一个platform驱动。
函数原型:
#include <linux/platform_driver.h>
void platform_driver_unregister(struct platform_driver *drv)
参数:
- drv:要注销的platform_driver结构体。
机制:
- 从系统内核的platform驱动列表中移除drv。
- 遍历所有平台设备,找到被drv管理的设备。
- 对每个找到的设备,调用drv的remove回调函数。
- drv停止管理这些platform设备。
2.5.3 platform_get_resource
作用
platform_get_resource()函数用于从一个platform设备中获取资源信息。
函数原型:
#include <linux/platform_device.h>
struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num)
参数:
- pdev:平台设备。
- type: 资源类型,如IOMEM,IOPORT等。
- num:资源的序号,从0开始。
返回值:
- 成功时,返回指向 struct resource 的指针,否则返回 NULL。
机制:
- 从pdev的资源列表中查找序号为num的特定类型(type)的资源信息。
- 如果找到,返回指向该资源的struct resource结构体的指针。
- 如果未找到,或者num越界,返回NULL。
2.6 关于自动创建设备文件节点
\qquad 在此之前,我们所有的驱动程序在insmod以后,都需要手动去mknod创建对应的设备文件。在platform中,就开始可以通过函数自动的建立对应的设备文件。
2.6.1 自动创建的模板
自动创建涉及到以下几个函数,:
1. alloc设备号
alloc_chrdev_region(&devt, 0, 1, "mydev");2. 注册platform设备
platform_device_register_simple("mydev", -1, NULL); 3. 注册platform驱动
platform_driver_register(&mydev_driver); 4. 创建设备类
cls = class_create(THIS_MODULE, "mydev"); 5. 创建设备文件
device_create(cls, NULL, devt, NULL, "mydev%d", MINOR(devt));
实例模板:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>1、创建 设备类
struct class *mydev_class;2、实现 platform驱动结构体
struct platform_driver mydev_driver = {.probe = mydev_probe,.remove = mydev_remove,.driver = {.name = "mydev",.owner = THIS_MODULE,},
};3、 驱动probe回调函数
static int mydev_probe(struct platform_device *pdev)
{dev_t devt;int ret;3.1 申请设备号ret = alloc_chrdev_region(&devt, 0, 1, "mydev");if (ret < 0) return ret;3.2 创建设备类mydev_class = class_create(THIS_MODULE, "mydev"); if (IS_ERR(mydev_class)) { unregister_chrdev_region(devt, 1);return PTR_ERR(mydev_class); }3.3 在sysfs中创建设备文件节点device_create(mydev_class, NULL, devt, NULL, "mydev%d", MINOR(devt)); return 0;
}4、驱动remove回调函数
static int mydev_remove(struct platform_device *pdev)
{dev_t devt;devt = MKDEV(MAJOR(pdev->devt), MINOR(pdev->devt));4.1 释放设备号unregister_chrdev_region(devt, 1); 4.2 销毁设备类 class_destroy(mydev_class);return 0;
}static int __init mydev_init(void)
{platform_driver_register(&mydev_driver);
}static void __exit mydev_exit(void)
{platform_driver_unregister(&mydev_driver);
}module_init(mydev_init);
module_exit(mydev_exit);MODULE_AUTHOR("Alice");
MODULE_LICENSE("GPL");
编译并加载这个模块后,在/sys/class/目录下会出现mydev设备类目录。在该目录下会有一个mydev0的设备文件节点。
通过cat /sys/class/mydev/mydev0可以读取该设备。
2.6.2 涉及到的数据结构
2.6.2.1 struct class
头文件:
/include/linux/device.h
原型
struct class {const char *name;struct module *owner;struct class_attribute *class_attrs;const struct attribute_group **dev_groups;struct kobject *dev_kobj;int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, umode_t *mode);void (*class_release)(struct class *class);void (*dev_release)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct kobj_ns_type_operations *ns_type;const void *(*namespace)(struct device *dev);const struct dev_pm_ops *pm;struct subsys_private *p;
};
解释:
struct class结构体代表Linux内核的一个设备类。它包含如下主要成员:
- name: 类名称,如"tty"类,"net"类等。
- owner: 拥有此类的模块。一般为-> THIS_MODULE
- class_attrs: 类属性列表,用于sysfs。
- dev_groups: 设备默认属性组列表,用于sysfs。
- dev_kobj: 类内所有设备的kobject父对象。
- dev_uevent: 产生设备uevent的回调函数。
- devnode: 产生设备节点名称的回调函数。
- class_release: 释放类时的回调函数。
- dev_release: 释放类中的单个设备时的回调函数。
- suspend: 设备suspend回调函数。
- resume: 设备resume回调函数。
- ns_type: namespace操作的回调函数列表。
- namespace: 确定设备namespace的回调函数。
- pm: 设备电源管理操作的回调函数列表。
- p: 私有数据,只供类内部使用。
主要功能与作用:
- 代表一个设备类型或类别,如所有字符设备属于tty类,所有网络设备属于net类。
- 管理属于同一类的所有设备。
- 通过sysfs导出类属性与方法。
- 调用注册的回调函数来管理电源、uevent和命名空间等操作。
- 用于创建设备文件节点的名称,通过devnode回调函数。
- 存储设备驱动程序可以使用的所有回调函数。
2.6.3 操作函数
2.6.3.1 class_create
class_create()函数用于在内核中创建一个新的设备类。
函数原型:
#include <linux/device.h>
struct class *class_create(struct module *owner, const char *name)
参数:
- owner: 拥有这个类的模块。通常传入THIS_MODULE。
- name: 要创建的类的名称,如"net"、"tty"等。
返回值:
- 成功时,返回新创建类的描述符。
- 失败时,返回ERR_PTR。
功能:
- 为指定名称的设备类分配一个内核数据结构struct class。
- 如果类名已存在,返回错误。
- 新类的class_attr属性列表、dev_attrs属性组列表初设为空。
- 新类的uevent()、devnode()默认回调函数被设置。
- 新类与所有者模块owner相关,以实现当模块卸载时自动销毁设备类。
2.6.3.2 class_destroy
class_destroy()函数用于销毁一个先前通过class_create()创建的设备类。
函数原型:
声明在include/linux/device.h头文件中。
void class_destroy(struct class *cls)
参数:
- cls: 要销毁的类的描述符。
功能:
- 销毁名为cls的设备类。
- 如果类中还有未释放的设备,则不会销毁该类,而是返回错误。
- 类的所有属性及方法都会被移除。
- 用于释放与该类相关的所有内存。
- 该类的ID可以被重新使用。
如果类中还存在设备, class_destroy()会返回错误,防止非法销毁现用的类。在这种情况下,需要先销毁所有属于该类的设备,才可以释放类本身。所以,class_destroy()为Linux内核提供了一个安全的设备类销毁机制。
2.6.3.3 device_create
device_create()函数用于在sysfs中创建一个设备文件节点。
函数原型:
声明在include/linux/device.h头文件中。
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
参数:
- cls: 设备所属的类,由class_create()创建。
- parent: 父设备,如果是系统设备通常为NULL。
- devt: 设备号,由alloc_chrdev_region()分配得到。
- drvdata: 私有数据,传递给新设备。
- fmt: 设备文件命名格式,以"/dev/"开头。
返回值:
- 成功时,返回指向新建设备的struct device。
- 失败时,返回ERR_PTR。
功能:
- 为指定的主副设备号devt创建一个设备,并添加到指定类cls中。
- 在sysfs中创建设备文件节点,文件名根据fmt格式化字符串生成,通常位于/sys/class/cls_name目录下。
- 新设备从父设备parent继承属性,如果parent为NULL则直接从类cls继承属性。
- 设备与drvdata关联,该数据可由驱动获取。
- 通常用于从设备驱动中创建设备文件节点。
一个使用例子:
dev_t devt;
alloc_chrdev_region(&devt, 0, 1, "mydev");struct class *mydev_class;
mydev_class = class_create(THIS_MODULE, "mydev");device_create(mydev_class, NULL, devt, NULL, "mydev%d", MINOR(devt));
2.6.3.4 ERR_PTR
/include/linux/err.h
static inline void * __must_check ERR_PTR(long error)
{return (void *) error;
}
ERR_PTR宏用于生成代表错误的指针值。
格式:
ERR_PTR(errcode)
参数:
- errcode: 要表示的错误码(负数)。
返回值:
- 返回一个指向ERR_PTR的指针,代表给定的错误码。
功能:
- 很多内核函数会返回指针,用于表示函数的执行结果。
- 通过返回指向ERR_PTR的指针,可以表示函数执行失败,同时包含错误码信息。
- 然后可以用IS_ERR()判断是否为错误指针,用PTR_ERR()提取错误码。
- 这种设计使得内核函数可以用统一的指针返回值表示正常执行结果和错误结果。
例如:
void *do_something(void)
{if ( /* 出错 */ ) return ERR_PTR(-EINVAL);else return ptr; // 正常指针
}void *ret = do_something();
if (IS_ERR(ret)) {int errcode = PTR_ERR(ret);printk(KERN_ALERT "do_something failed: %d\n", errcode);
} else {// 使用ret
}
这里do_something()函数如果执行失败,会返回ERR_PTR(-EINVAL)错误指针。
调用者可以用IS_ERR()判断这是否是一个错误指针,如果是,再用PTR_ERR()获取表示的错误码。
所以ERR_PTR()为内核函数提供了一种使用指针统一返回正常和错误结果的简便方法。和IS_ERR()、PTR_ERR()配合使用,可以实现清晰高效的错误码传递和处理。
这种设计大大增强了内核API的易用性,消除了函数直接返回错误码会造成的调用者混淆的问题。通过判断指针类型,调用者可以清晰地确定函数的执行结果,这是一种典型的优雅设计。
ERR_PTR()和与之配套的IS_ERR()、PTR_ERR(),共同构成了Linux内核错误处理机制的基石。
2.6.3.5 IS_ERR
原型:
/include/linux/err.h
static inline long __must_check IS_ERR(__force const void *ptr)
{return IS_ERR_VALUE((unsigned long)ptr);
}
IS_ERR()宏用于判断一个函数的返回值是否代表一个错误(负值)。
格式:
IS_ERR(pointer)
参数:
- pointer: 要判断的函数返回值。
返回值:
- 如果pointer代表一个错误值(负数),返回1。
- 如果pointer代表一个正常值,返回0。
功能:
- 很多内核函数在失败时会返回一个指向ERR_PTR的指针,而不是直接返回负数。
- IS_ERR()宏可以测试这样的返回值,判断函数是否失败。
- 如果失败,可以使用PTR_ERR()宏获取实际的错误码。
2.6.3.6 PTR_ERR
/include/linux/err.h
static inline long __must_check PTR_ERR(__force const void *ptr)
{return (long) ptr;
}
PTR_ERR()宏用于将一个指向ERR_PTR的错误指针转换为实际的错误码。
格式:
int PTR_ERR(const void *ptr)
参数:
- ptr: 要判断的指针,如果是指向ERR_PTR的错误指针。
返回值:
- 如果ptr是错误指针,返回实际的错误码(负数)。
- 如果ptr是正常指针,返回0。
功能:
- 很多内核函数在失败时会返回一个指向ERR_PTR的指针,而不是直接返回负数。
- PTR_ERR()可以获取这种错误指针实际表示的错误码。
- 通常与IS_ERR()宏配合使用,先判断是否是错误指针,如果是再获取错误码。
例如:
void *ptr = somefunction();
if (IS_ERR(ptr)) {int err = PTR_ERR(ptr);printk(KERN_ALERT "Error %d\n", err);
} else {// 使用ptr
}
这里如果somefunction()失败返回错误指针,我们首先用IS_ERR()判断出这一点,然后用PTR_ERR()获取实际的错误码err。
如果somefunction()成功,ptr就是正常指针,IS_ERR()返回false,PTR_ERR()正确返回0。
所以PTR_ERR()为获取内核中指针类型的错误码提供了一种简洁高效的方法。配合IS_ERR()使用,可以很好地处理内核指针返回值中的错误案例。
3、platform平台总线的四种匹配模式
\qquad 与platform_driver是用于非依赖真实总线的设备驱动场景。因此如果是真实的总线设备,与platform_driver就会有i2c_driver、spi_driver、usb_driver、pci_driver等,所有的xxx_driver中都包含了struct device_driver结构体实例成员。它其实描述了各种xxx_driver在驱动意义上的一些共性。
\qquad 为满足设备与驱动的不同的匹配场景,内核在drivers/base/platform.c中定义了一个bus_type的实例platform_bus_type。在这个实例:
struct bus_type platform_bus_type = {.name = "platform",.dev_groups = platform_dev_groups,.match = platform_match,.uevent = platform_uevent,.pm = &platform_dev_pm_ops,
};
\qquad 在这个实例中,重点是.match这个成员函数,正是这个成员函数确定了platform_device和platform_driver之间是如何进行匹配的,有几种匹配方式,以及不同的匹配方式的优先级:
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);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}
\qquad 从代码中可以看出。总共有四种匹配方式。
- 一是基于设备树风格的匹配方式,这个方式的优先级最高。
- 二是基于ACPI风格的匹配,这个方式的优先级次之
- 三是基于ID的匹配方式,这个方式的优先级是第三
- 四是基于名称的匹配方式,这个方式的优先级最低
\qquad linux3.x之后,ARM LINUX倾向于根据设备树中的内容自动展开platform_device。
3.1第一种,名称匹配
名称匹配只能是一对一的匹配,就是一个driver只能对应一个device。这种匹配方式的优先级最低
3.1.1 名称匹配模板
编译后
insmod hello_device.ko
insmod hello_driver.ko
设备中增加资源,驱动中访问资源
3.1.2 一个用platform框架的led实例(name匹配)
\qquad 该实例是拿了10-内核内存管理中第3.3节的实例进行改变的。同时点量fs4412板上的三个led灯。其中驱动分为led-access-device.c 和 led-access-driver.c两部分,对应platform架构的device和driver两个部分。驱动driver里还使用了device_create函数,实现了自动创建设备文件节点。public.h是公共的头文件。test.c则是测试APP。
公共文件public.h
#ifndef _H_PUBLIC_
#define _H_PUBLIC_#include <asm/ioctl.h>#define LED_ON _IO('a',1)
#define LED_OFF _IO('a',0)#endif
led-access-device.c
/*************************************************************************> File Name: led-access-device.c************************************************************************/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>#define GPX2CON 0X11000C40
#define GPX2DAT 0X11000C44#define GPX1CON 0X11000C20
#define GPX1DAT 0X11000C24#define GPF3CON 0X114001E0
#define GPF3DAT 0X114001E4void led_device_release(struct device *dev){printk("device: led_device released\n");
}struct resource res[] = {[0] = {.start =GPX2CON , .end =GPX2CON+3 , .name = "GPX2CON", .flags =IORESOURCE_MEM },[1] = {.start =GPX2DAT , .end =GPX2DAT+3 , .name = "GPX2DAT", .flags =IORESOURCE_MEM },[2] = {.start =GPX1CON , .end =GPX1CON+3 , .name = "GPX1CON", .flags =IORESOURCE_MEM },[3] = {.start =GPX1DAT , .end =GPX1DAT+3 , .name = "GPX1DAT", .flags =IORESOURCE_MEM },[4] = {.start =GPF3CON , .end =GPF3CON+3 , .name = "GPF3CON", .flags =IORESOURCE_MEM },[5] = {.start =GPF3DAT , .end =GPF3DAT+3 , .name = "GPF3DAT", .flags =IORESOURCE_MEM },};struct platform_device led_device = {.name = "led_device",.dev.release =led_device_release ,.resource = res,.num_resources = ARRAY_SIZE(res),
};static int __init my_init(void){platform_device_register(&led_device);return 0;
}module_init(my_init);static void __exit my_exit(void){platform_device_unregister(&led_device);
}module_exit(my_exit);
MODULE_LICENSE("GPL");
led-access-driver.c
/*************************************************************************> File Name: led-access-driver.c************************************************************************/#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/device.h>#include "public.h"/*1、定义重要的变量及结构体*/
struct x_dev_t {struct cdev my_dev; //cdev设备描述结构体变量atomic_t have_open; //记录驱动是否被打开的原子变量struct class *led_class; //设备类unsigned long volatile __iomem *gpx2con; //指向对应控制寄存器的虚拟地址unsigned long volatile __iomem *gpx2dat; //指向对应数据寄存器的虚拟地址unsigned long volatile __iomem *gpx1con; //指向对应控制寄存器的虚拟地址unsigned long volatile __iomem *gpx1dat; //指向对应数据寄存器的虚拟地址unsigned long volatile __iomem *gpf3con; //指向对应控制寄存器的虚拟地址unsigned long volatile __iomem *gpf3dat; //指向对应数据寄存器的虚拟地址
};struct x_dev_t *pcdev;/*所有驱动函数声明*/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
ssize_t aio_read (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t aio_write (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int iterate (struct file *, struct dir_context *);
unsigned int poll (struct file *, struct poll_table_struct *);
long unlocked_ioctl (struct file *, unsigned int, unsigned long);
long compat_ioctl (struct file *, unsigned int, unsigned long);
int mmap (struct file *, struct vm_area_struct *);
int open (struct inode *, struct file *);
int flush (struct file *, fl_owner_t id);
int release (struct inode *, struct file *);
int fsync (struct file *, loff_t, loff_t, int datasync);
int aio_fsync (struct kiocb *, int datasync);
int fasync (int, struct file *, int);
int lock (struct file *, int, struct file_lock *);
ssize_t sendpage (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long get_unmapped_area(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int check_flags(int);
int flock (struct file *, int, struct file_lock *);
ssize_t splice_write(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t splice_read(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int setlease(struct file *, long, struct file_lock **);
long fallocate(struct file *file, int mode, loff_t offset, loff_t len);
int show_fdinfo(struct seq_file *m, struct file *f);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={.owner = THIS_MODULE,.open = open,.release = release,.unlocked_ioctl = unlocked_ioctl,};void led_init(struct platform_device *dev){struct resource *res;//设置GPX2CON的28-31位为0b0001,输出模式res = platform_get_resource(dev,IORESOURCE_MEM,0);pcdev->gpx2con = ioremap(res->start,4);res = platform_get_resource(dev,IORESOURCE_MEM,1);pcdev->gpx2dat = ioremap(res->start,4);writel((readl(pcdev->gpx2con) & (~(0xf << 28))) | (0x1 << 28) , pcdev->gpx2con );//设置gpx1con的0-3位为0b0001,输出模式res = platform_get_resource(dev,IORESOURCE_MEM,2);pcdev->gpx1con = ioremap(res->start,4);res = platform_get_resource(dev,IORESOURCE_MEM,3);pcdev->gpx1dat = ioremap(res->start,4);writel((readl(pcdev->gpx1con) & (~(0xf << 0))) | (0x1 << 0) , pcdev->gpx1con );//设置gpf3con的16-19位为0b0001,输出模式res = platform_get_resource(dev,IORESOURCE_MEM,4);pcdev->gpf3con = ioremap(res->start,4);res = platform_get_resource(dev,IORESOURCE_MEM,5);pcdev->gpf3dat = ioremap(res->start,4);writel((readl(pcdev->gpf3con) & (~(0xf << 16))) | (0x1 << 16) , pcdev->gpf3con );
}void led_cntl(int cmd){if (cmd ){ //开writel(readl(pcdev->gpx2dat)|(1 << 7),pcdev->gpx2dat );writel(readl(pcdev->gpx1dat)|(1 << 0),pcdev->gpx1dat );writel(readl(pcdev->gpf3dat)|(1 << 4),pcdev->gpf3dat );}else{writel(readl(pcdev->gpx2dat)&(~(1<< 7)), pcdev->gpx2dat);writel(readl(pcdev->gpx1dat)&(~(1<< 0)), pcdev->gpx1dat);writel(readl(pcdev->gpf3dat)&(~(1<< 4)), pcdev->gpf3dat);}
}int led_probe (struct platform_device *dev){int unsucc =0;dev_t devno;int major,minor;pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);/*2、创建 devno及设备类 */unsucc = alloc_chrdev_region(&devno , 0 , 1 , "led-platform-name");if (unsucc){printk(" creating devno faild\n");return -1;}major = MAJOR(devno);minor = MINOR(devno);pcdev->led_class = class_create(THIS_MODULE,"led_dev_class");if (IS_ERR(pcdev->led_class)){unsucc = PTR_ERR(pcdev->led_class);goto err;} printk("device : led-platform-name devno major = %d ; minor = %d;\n",major , minor);/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*//*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/cdev_init(&pcdev->my_dev , &fops);pcdev->my_dev.owner = THIS_MODULE;/*4、注册cdev结构体到内核链表中*/unsucc = cdev_add(&pcdev->my_dev,devno,1);if (unsucc){printk("driver : cdev add faild\n");goto err;}//创建设备文件节点,这样在/dev/中就有了一个led-platform-0的设备文件节点device_create(pcdev->led_class , NULL , devno , NULL , "/dev/led-platform-%d",MINOR(devno) );//初始化原子量have_open为1atomic_set(&pcdev->have_open,1);//初始化led2led_init(dev);printk("device the driver led-platform-name initalization completed\n");return 0;err:cdev_del(&pcdev->my_dev);unregister_chrdev_region(pcdev->my_dev.dev , 1);printk("***************the driver led_platform-name exit************\n");return unsucc;}int led_remove(struct platform_device *dev)
{cdev_del(&pcdev->my_dev);unregister_chrdev_region(pcdev->my_dev.dev , 1);class_destroy(pcdev->led_class);kfree(pcdev);printk("***************the driver timer-second exit************\n");return 0;
}struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver.name = "led_device",
};static int __init my_init(void){platform_driver_register(&led_driver);return 0;
}static void __exit my_exit(void)
{platform_driver_unregister(&led_driver); }/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);pf->private_data = (void *)p;//在open函数中对原子量have_open进行减1并检测。=0,允许打开文件,<0则不允许打开if (atomic_dec_and_test(&p->have_open)){printk("driver:led-platform-name driver is opened\n");return 0 ;}else{printk("driver:device led-platform-name can't be opened again\n");atomic_inc(&p->have_open);//原子量=-1,记得这里要把原子量加回到0return -1;} }
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){struct x_dev_t *p = (struct x_dev_t *)pf->private_data;printk("driver:led-platform-name is closed \n");iounmap(p->gpx2con);iounmap(p->gpx2dat);atomic_set(&p->have_open,1);return 0;
}long unlocked_ioctl (struct file *pf, unsigned int cmd, unsigned long arg){//struct x_dev_t *p = pf->private_data;switch(cmd){case LED_ON:led_cntl(1);break;case LED_OFF:led_cntl(0);break;default:break;}return 0;
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
test.c
/*************************************************************************> File Name: test.c> Created Time: Wed 19 Apr 2023 02:33:42 PM CST************************************************************************/#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "public.h"int main (int argc , char **argv){int fd0,fd1;if (argc <2){printf("argument is too less\n");return -1;}else{fd0 = open(argv[1] , O_RDONLY );while (fd0){printf("led on......\n");ioctl(fd0,LED_ON);sleep(2);printf("led off......\n");ioctl(fd0,LED_OFF);sleep(2);}}close(fd0);return 0;}
测试的脚本load.sh
dmesg -cclearlsmod /drv/led-access-device.kolsmod /drv/led-access-driver.ko./test.elf /dev/led-access-0
3.2 第二种,id匹配
这种匹配,一个driver可以匹配多个device。优先级次之。
注意事项:
- 在platform_device中有两个name,一个是platform_device的name成员,另外是成员id_entry的name成员,这两个成员不一致也没关系
- 在platform_driver中的id_table成员可以是数组,用于匹配多个不同的设备,模板如下
ID匹配模式可以实现一个driver对应多个device。
3.2.1 ID匹配模板(第一、标准模板device.name 以及device.id_entry.name 与device.id_table.name)
3.2.1.1 匹配的一般结构
3.2.1.2 匹配中一对多的特殊处理
这里给出一个可运行的实例来说明基于id_entry和id_table的名字匹配如何实现platform框架的一对多对应。
platform device代码:
static struct platform_device dev0 = {.name = "mydev",.id_entry = {.name = "dev0", },
};static struct platform_device dev1 = {.name = "mydev",.id_entry = {.name = "dev1",},
};static int __init mydev_init(void)
{platform_device_register(&dev0);platform_device_register(&dev1);return 0;
}
这里定义了两个platform device,并分别指定了id_entry.name为"dev0"和"dev1"。
platform driver代码:
static struct platform_driver my_driver = {.probe = my_probe,.driver = {.name = "my_driver",.owner = THIS_MODULE,.id_table = my_id_table,},
};static struct platform_device_id my_id_table[] = {{ .name = "dev0" }, { .name = "dev1" },{ },
};static int my_probe(struct platform_device *dev)
{if (!strcmp(dev->id_entry.name, "dev0")) {dev_t devt = MKDEV(440, 0);register_chrdev_region(devt, 1, "mydev0");device_create(mydev_class, NULL, devt, NULL, "mydev0");printk("Probe dev0\n");} else if(!strcmp(dev->id_entry.name, "dev1")) {dev_t devt = MKDEV(440, 1); register_chrdev_region(devt, 1, "mydev1");device_create(mydev_class, NULL, devt, NULL, "mydev1"); printk("Probe dev1\n");}return 0;
}
这里driver的id_table指定要匹配id_entry.name为"dev0"和"dev1"的两个device。
在probe函数中,通过字符串比较dev->id_entry.name来识别不同device:
- 如果是"dev0",申请设备号440:0,创建/dev/mydev0,并打印信息。
- 如果是"dev1",申请设备号440:1,创建/dev/mydev1,并打印信息。
编译运行后,将得到:
Probe dev0
Probe dev1
这证明driver成功匹配了两个platform device,并调用了probe两次进行了不同的处理。
所以,这个例子清晰地展示了如何通过id_entry.name与id_table的字符串匹配来实现platform框架的一对多对应,主要步骤是:
- 为不同platform device指定不同的id_entry.name。
- driver的id_table指定要匹配的多个device name。
- 在probe函数中,通过字符串比较dev->id_entry.name识别不同device。
- 根据不同device进行不同处理,如申请不同设备号、创建不同设备节点等。
这种基于字符串匹配的方法同样实现了一对多,使一个platform driver成功对应多个不同的platform device。
3.2.2 ID匹配模板(第二、device.id与driver.id_table.driver_data)
在Linux 3.14版本中,platform框架引入了id匹配模式,它允许一个platform driver匹配多个platform device,实现一对多的对应关系。
此机制的实现是:
- platform device在注册时可以指定id。如果不指定id,则使用device的name作为默认id。
- platform driver在probe时,可以声明要匹配的id表,这是一个id的数组。
- 内核的匹配逻辑会遍历driver的id表,查找是否有与之匹配的platform device。如果有,则调用driver的probe来绑定device。
- 一个driver可以声明多个id,所以它可以匹配多个platform device。
3.2.2.1 模板的一般结构
platform device代码:
static struct platform_device dev0 = {.name = "mydev",.id = 0,
};static struct platform_device dev1 = {.name = "mydev",.id = 1,
};static int __init mydev_init(void)
{platform_device_register(&dev0);platform_device_register(&dev1);return 0;
}
这里注册了两个platform device,id分别为0和1。
platform driver代码:
static struct platform_driver my_driver = {.probe = my_probe,.driver = {.name = "my_driver",.owner = THIS_MODULE,.id_table = my_id_table,},
};static struct platform_device_id my_id_table[] = {{ "mydev", 0 },{ "mydev", 1 },{ },
};static int my_probe(struct platform_device *dev)
{if (dev->id == 0) {printk("Probe device 0\n");} else if (dev->id == 1) {printk("Probe device 1\n");}return 0;
}static int __init my_driver_init(void)
{platform_driver_register(&my_driver);return 0;
}
这里platform driver注册时声明了id匹配表,指定要匹配id为0和1的两个platform device。
在probe回调函数中,根据device的id来区分并打印不同信息。
编译运行后,将得到:
Probe device 0
Probe device 1
这表明driver成功匹配了id为0和1的两个platform device,调用了两次probe函数。
所以,通过这个例子可以清晰地了解platform框架的一对多匹配是如何实现的:
- 注册多个platform device,指定不同的id。
- platform driver声明id匹配表,指定要匹配的多个device id。
- 内核会根据driver的id匹配表,依次匹配所有指定id的platform device,并调用driver的probe回调函数。
- 在probe函数中,可以通过device的id字段识别不同的device,进行相应处理。
3.2.2.2 一对多的特殊处理
platform框架的一对多匹配中,多个device通常具有不同的设备号和设备节点。
driver的probe函数需要根据不同的device进行不同的处理,主要包括:
1、 申请不同的设备号:
if (dev->id == 0) {dev_t devt = MKDEV(440, 0);register_chrdev_region(devt, 1, "mydev0");
} else if (dev->id == 1) {dev_t devt = MKDEV(440, 1);register_chrdev_region(devt, 1, "mydev1");
}
这里根据device的id为不同device申请不同的设备号,如440:0和440:1。
2、创建设备类
if (!mydev_class) {mydev_class = class_create(THIS_MODULE, "mydev"); // 第一次调用时创建类}
3、创建不同的设备节点文件:
if (dev->id == 0) {device_create(mydev_class, NULL, MKDEV(440, 0), NULL, "mydev0");
} else if (dev->id == 1) {device_create(mydev_class, NULL, MKDEV(440, 1), NULL, "mydev1");
}
这里也根据device id为不同device在/dev下创建不同名的设备节点文件,如/dev/mydev0和/dev/mydev1。
4、不同device可能需要不同的处理,可以根据id在probe中添加不同逻辑:
if (dev->id == 0) {// mydev0需要的特有处理
} else if (dev->id == 1) {// mydev1需要的特有处理
}
//共有的处理逻辑
所以,在platform框架的一对多匹配中,driver的probe函数需要根据不同 device的id进行以下区分处理:
- 申请不同的设备号
- 创建不同的设备节点
- 根据需要添加不同device相关的逻辑
这才能真正实现一对多,使一个driver successfully对应与管理多个功能与属性不同的platform device。
而id字段就是实现这种区分与管理的关键依据,它为driver提供了识别多个device的手段。
所以,platform框架的这种一对多设计,要求driver的probe函数需要具备根据不同device id进行区分处理的能力,这也是设计这种driver的关键所在。
3.2.2.3 一个led实例
和前面的例子一样,用fs4412上的led来做这个platform虚拟总线id匹配的实验。两个device文件,分别是led-acc-dev2.c 和 led-acc-dev3.c。一个driver文件led-acc-driver.c。
public.h
#ifndef _H_PUBLIC_
#define _H_PUBLIC_#include <asm/ioctl.h>#define LED_ON _IO('a',1)
#define LED_OFF _IO('a',0)#endif
led-acc-dev2.c
/*************************************************************************> File Name: led-acc-dev2.c************************************************************************/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>#define GPX2CON 0X11000C40
#define GPX2DAT 0X11000C44#define GPX1CON 0X11000C20
#define GPX1DAT 0X11000C24#define GPF3CON 0X114001E0
#define GPF3DAT 0X114001E4void led_device_release(struct device *dev){printk("device: led_platform_id2 released\n");
}struct resource res[] = {[0] = {.start =GPX2CON , .end =GPX2CON+3 , .name = "GPX2CON", .flags =IORESOURCE_MEM },[1] = {.start =GPX2DAT , .end =GPX2DAT+3 , .name = "GPX2DAT", .flags =IORESOURCE_MEM },[2] = {.start =GPX1CON , .end =GPX1CON+3 , .name = "GPX1CON", .flags =IORESOURCE_MEM },[3] = {.start =GPX1DAT , .end =GPX1DAT+3 , .name = "GPX1DAT", .flags =IORESOURCE_MEM },[4] = {.start =GPF3CON , .end =GPF3CON+3 , .name = "GPF3CON", .flags =IORESOURCE_MEM },[5] = {.start =GPF3DAT , .end =GPF3DAT+3 , .name = "GPF3DAT", .flags =IORESOURCE_MEM },[6] = {},
};
struct platform_device_id id_entry={.name = "led_p",
};
struct platform_device led_device = {.name = "led_platform_id2",.id = 0 , //用于第二种id匹配.id_entry = &id_entry,.dev.release =led_device_release ,.resource = res,.num_resources = ARRAY_SIZE(res),
};static int __init my_init(void){platform_device_register(&led_device);return 0;
}module_init(my_init);static void __exit my_exit(void){platform_device_unregister(&led_device);
}module_exit(my_exit);
MODULE_LICENSE("GPL");
led-acc-dev3.c
/*************************************************************************> File Name: led-acc-dev3.c************************************************************************/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>#define GPX2CON 0X11000C40
#define GPX2DAT 0X11000C44#define GPX1CON 0X11000C20
#define GPX1DAT 0X11000C24#define GPF3CON 0X114001E0
#define GPF3DAT 0X114001E4void led_device_release(struct device *dev){printk("device: led_platform_id3 released\n");
}struct resource res[] = {[0] = {.start =GPX2CON , .end =GPX2CON+3 , .name = "GPX2CON", .flags =IORESOURCE_MEM },[1] = {.start =GPX2DAT , .end =GPX2DAT+3 , .name = "GPX2DAT", .flags =IORESOURCE_MEM },[2] = {.start =GPX1CON , .end =GPX1CON+3 , .name = "GPX1CON", .flags =IORESOURCE_MEM },[3] = {.start =GPX1DAT , .end =GPX1DAT+3 , .name = "GPX1DAT", .flags =IORESOURCE_MEM },[4] = {.start =GPF3CON , .end =GPF3CON+3 , .name = "GPF3CON", .flags =IORESOURCE_MEM },[5] = {.start =GPF3DAT , .end =GPF3DAT+3 , .name = "GPF3DAT", .flags =IORESOURCE_MEM },[6] = {},
};struct platform_device_id id_entry={.name = "led_platform_id3",
};
struct platform_device led_device = {.name = "led_platform_id2",.id = 1, //用于第二种id匹配.id_entry = &id_entry,.dev.release =led_device_release ,.resource = res,.num_resources = ARRAY_SIZE(res),
};static int __init my_init(void){platform_device_register(&led_device);return 0;
}module_init(my_init);static void __exit my_exit(void){platform_device_unregister(&led_device);
}module_exit(my_exit);
MODULE_LICENSE("GPL");
led-acc-driver.c
/*************************************************************************> File Name: led-acc-driver.c************************************************************************/#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/device.h>#include "public.h"#define DEV_NUM 2 //这个驱动要定义2个设备/*1、定义重要的变量及结构体*/
struct x_dev_t {struct cdev my_dev; //cdev设备描述结构体变量atomic_t have_open; //记录驱动是否被打开的原子变量unsigned long volatile __iomem *gpx2con; //指向对应控制寄存器的虚拟地址unsigned long volatile __iomem *gpx2dat; //指向对应数据寄存器的虚拟地址unsigned long volatile __iomem *gpx1con; //指向对应控制寄存器的虚拟地址unsigned long volatile __iomem *gpx1dat; //指向对应数据寄存器的虚拟地址unsigned long volatile __iomem *gpf3con; //指向对应控制寄存器的虚拟地址unsigned long volatile __iomem *gpf3dat; //指向对应数据寄存器的虚拟地址
};struct x_dev_t *pcdev;
struct class *led_class=NULL; //设备类
int major=0 , minor=0; ///*所有驱动函数声明*/long unlocked_ioctl (struct file *, unsigned int, unsigned long);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={.owner = THIS_MODULE,.open = open,.release = release,.unlocked_ioctl = unlocked_ioctl,};void led_init(struct platform_device *dev){struct resource *res;//设置GPX2CON的28-31位为0b0001,输出模式res = platform_get_resource(dev,IORESOURCE_MEM,0);pcdev->gpx2con = ioremap(res->start,4);res = platform_get_resource(dev,IORESOURCE_MEM,1);pcdev->gpx2dat = ioremap(res->start,4);writel((readl(pcdev->gpx2con) & (~(0xf << 28))) | (0x1 << 28) , pcdev->gpx2con );//设置gpx1con的0-3位为0b0001,输出模式res = platform_get_resource(dev,IORESOURCE_MEM,2);pcdev->gpx1con = ioremap(res->start,4);res = platform_get_resource(dev,IORESOURCE_MEM,3);pcdev->gpx1dat = ioremap(res->start,4);writel((readl(pcdev->gpx1con) & (~(0xf << 0))) | (0x1 << 0) , pcdev->gpx1con );//设置gpf3con的16-19位为0b0001,输出模式res = platform_get_resource(dev,IORESOURCE_MEM,4);pcdev->gpf3con = ioremap(res->start,4);res = platform_get_resource(dev,IORESOURCE_MEM,5);pcdev->gpf3dat = ioremap(res->start,4);writel((readl(pcdev->gpf3con) & (~(0xf << 16))) | (0x1 << 16) , pcdev->gpf3con );
}void led_cntl(int cmd){if (cmd ){ //开writel(readl(pcdev->gpx2dat)|(1 << 7),pcdev->gpx2dat );writel(readl(pcdev->gpx1dat)|(1 << 0),pcdev->gpx1dat );writel(readl(pcdev->gpf3dat)|(1 << 4),pcdev->gpf3dat );}else{writel(readl(pcdev->gpx2dat)&(~(1<< 7)), pcdev->gpx2dat);writel(readl(pcdev->gpx1dat)&(~(1<< 0)), pcdev->gpx1dat);writel(readl(pcdev->gpf3dat)&(~(1<< 4)), pcdev->gpf3dat);}
}/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*//*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
int cdev_setup(struct x_dev_t *p_dev , dev_t devno , struct platform_device *dev){int unsucc ;cdev_init(&p_dev->my_dev , &fops);p_dev->my_dev.owner = THIS_MODULE;//注册cdev结构体到内核链表中unsucc = cdev_add(&p_dev->my_dev , devno , 1);if (unsucc){printk("driver : cdev add faild\n");return -1;}//创建设备文件节点,这样在/dev/中就有了一个led-platform-0的设备文件节点device_create(led_class , NULL , devno , NULL , "/dev/led-platform-id2%d",MINOR(devno) );//初始化原子量have_open为1atomic_set(&p_dev->have_open,1);//初始化两个设备的led2led_init(dev);return 0;
}//这里的probe会根据所要匹配的n个设备,持行n次。所以devno与以
int led_probe (struct platform_device *dev){int unsucc =0;dev_t devno ;if (!pcdev){pcdev = kzalloc(sizeof(struct x_dev_t)*DEV_NUM, GFP_KERNEL);if (!pcdev){unsucc = -1;goto err1;}}/*2、创建 devno及设备类 */if (major == 0){ //没有设备号,才分配unsucc = alloc_chrdev_region(&devno , 0 , DEV_NUM , "led-platform-id2");if (unsucc){printk(" diver:creating devno faild\n");goto err2;}else{major = MAJOR(devno);minor = MINOR(devno);}}printk("driver:devno %d : %d \n",major , minor);//创建设备类if (!led_class){led_class = class_create(THIS_MODULE,"led_dev_class");if (IS_ERR(led_class)){unsucc = PTR_ERR(led_class);goto err2;}}/*4、注册cdev结构体到内核链表中,这里有两个设备*/devno = MKDEV(major,dev->id);if (cdev_setup(pcdev + dev->id , devno, dev) == 0){printk("device : led-platform-id2-[%d] devno major = %d ; minor = %d;\n",dev->id,MAJOR(devno) , MINOR(devno));printk("driver:the led_platform_id2[%d] initalization completed\n",dev->id);}else{printk("driver:the led_platform_id2[%d] initalization faild\n",dev->id);}printk("device the driver led-platform-id2[%d] initalization completed\n",dev->id);return 0;
err2:kfree(pcdev);
err1:printk("***************the driver led_platform-id2 err************\n");return unsucc;
}int led_remove(struct platform_device *dev)
{int i=0;for (i=0;i<DEV_NUM;i++){cdev_del(&(pcdev+i)->my_dev);unregister_chrdev_region((pcdev+i)->my_dev.dev,1);}class_destroy(led_class);kfree(pcdev);printk("***************the driver led_platform_id2 exit************\n");return 0;
}struct platform_device_id id_table[] = {[0] = { "led_platform_id2", 0},[1] = { "led_platform_id2",1},[2] = {},
};struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver.name = "led_device",.id_table = id_table,
};static int __init my_init(void){platform_driver_register(&led_driver);return 0;
}static void __exit my_exit(void)
{platform_driver_unregister(&led_driver); }/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);pf->private_data = (void *)p;//在open函数中对原子量have_open进行减1并检测。=0,允许打开文件,<0则不允许打开if (atomic_dec_and_test(&p->have_open)){printk("driver:led-platform-id2 driver is opened\n");return 0 ;}else{printk("driver:device led-platform-id2 can't be opened again\n");atomic_inc(&p->have_open);//原子量=-1,记得这里要把原子量加回到0return -1;} }
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){struct x_dev_t *p = (struct x_dev_t *)pf->private_data;printk("driver:led-platform-id2 is closed \n");iounmap(p->gpx2con);iounmap(p->gpx2dat);atomic_set(&p->have_open,1);return 0;
}
long unlocked_ioctl (struct file *pf, unsigned int cmd, unsigned long arg){//struct x_dev_t *p = pf->private_data;switch(cmd){case LED_ON:led_cntl(1);break;case LED_OFF:led_cntl(0);break;default:break;}return 0;
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
load.sh
dmesg -cclearinsmod /drv/led-acc-dev2.ko
insmod /drv/led-acc-dev3.koinsmod /drv/led-acc-driver.ko
test.c
/*************************************************************************> File Name: test.c> Created Time: Wed 19 Apr 2023 02:33:42 PM CST************************************************************************/#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "public.h"int main (int argc , char **argv){int fd0,fd1;if (argc <2){printf("argument is too less\n");return -1;}else{fd0 = open(argv[1] , O_RDONLY );while (fd0){printf("led on......\n");ioctl(fd0,LED_ON);sleep(2);printf("led off......\n");ioctl(fd0,LED_OFF);sleep(2);}}close(fd0);return 0;
}
3.3第三种,设备树匹配
\qquad 设备树匹配,无需编写device模块,只需编写driver模块,只需要构造struct platform_driver,该结构直接与设备树中的某个节点相匹配,内核会自动生成对应的platform_device。优先级最高。
注意事项:
1、由于是使用compatible属性进行与设备树的匹配,要求设备树下地的compatible属性值不能含空格。
2、 id_table可不设置,platform_driver中的device_driver.name成员必须要指定。
内核启动时根据设备树自动产生的设备 ------ 优先级最高
3.3.1 匹配重点
3.3.2 设备树匹配模式的关联框图:
3.3.3 实例
还是以fs4412板上的led驱动做为实例。
注意:
由于在系统完成本driver与设备树匹配后,会自动生成platform_device结构体,并在该结构体中的device.of_node存储了设备树的对应节点。
因此这里的pnode就直接从p_pltdev->dev.of_node中取出就好了。不需要再像初级设备树驱动那样用 pnode=of_find_node_by_path(“/fs4412_leds”);语句来取出设备树节点。
相关文章:

【嵌入式环境下linux内核及驱动学习笔记-(14)linux总线、设备、驱动模型之platform】
目录 1、新驱动架构的导入1.1 传统驱动方式的痛点1.2 总线设备驱动架构 2、platform 设备驱动2.1 platform总线式驱动的架构思想2.2 platform _device相关的数据类型2.2.1 struct platform_device2.2.2 struct platform_device_id2.2.3 struct resource2.2.4 struct device 2.3…...

绝地求生 压q python版
仅做学习交流,非盈利,侵联删(狗头保命) 一、概述 1.1 效果 总的来说,这种方式是通过图像识别来完成的,不侵入游戏,不读取内存,安全不被检测。 1.2 前置知识 游戏中有各种不同的q械…...

云原生技术中的容器技术有哪些?
文章目录 云原生技术中的容器技术有哪些1、云原生的含义2、容器的含义3、云原生的技术的基石:容器技术4、容器技术有哪些? 结语 云原生技术中的容器技术有哪些 在现今的安全行业中云原生安全技术中的容器安全技术有哪些呢,很多用户都不知道具体的含义以…...

Gin中间件的详解 ,用Jwt-go 和 Gin 的安全的登陆的中间件
学习目标: Gin 在不同的group 设置不同的中间件或者过滤器 Gin 的group下的路由上中间件或过滤器 用Jwt-go 和 Gin 的安全的登陆的中间件 JWT 类,它基本有所有基本功能,包括:GenerateToken,GenerateRefreshToken, ValidateToken, ParseToken 学习内容: 1. Gin 在不同的g…...

Nginx网站部署
Nginx网站部署 一、访问状态统计配置二、基于授权的访问控制三、基于客户端的访问控制四、基于域名的 Nginx 虚拟主机五、基于IP 的 Nginx 虚拟主机六、基于端口的 Nginx 虚拟主机 一、访问状态统计配置 1.先使用命令/usr/local/nginx/sbin/nginx -V 查看已安装的 Nginx 是否包…...

Hadoop优化
1.小文件 影响: 元数据的瓶颈在于文件的数量,无论单个文件的大小 资源大材小用 优化 计算:使用combininputformat提前合并小文件 JVM重用 存储:归档 2.map端 环形缓冲区-区域大小、溢写比列 提前combinerÿ…...

FPGA设计的指导性原则 (中)
1.6基本设计思想与技巧之二:串并转换 串并转换是FPGA设计的一个重要技巧,从小的着眼点讲,它是数据流处理的常用手 段,从大的着眼点将它是面积与速度互换思想的直接体现。串并转换的实现方法多种多样, 根据数据的排序和数量的要求,可以选用寄存器、RAM等实现。前面在乒乓…...

开源创新 协同融合|2023 开放原子全球开源峰会开源协作平台分论坛即将启幕
由开放原子开源基金会主办,阿里云、CSDN 等单位共同承办的开源协作平台分论坛即将于 6 月 12 日上午在北京经开区北人亦创国际会展中心隆重召开。作为 2023 开放原子全球开源峰会的重要组成部分,开源协作平台分论坛将聚焦于开源代码平台的创新功能、用户…...

第四章 相似矩阵与矩阵对角化
引言 题型总结中推荐例题有蓝皮书的题型较为重要,只有吉米多维奇的题型次之。码字不易,如果这篇文章对您有帮助的话,希望您能点赞、评论、收藏,投币、转发、关注。您的鼓励就是我前进的动力! 知识点思维导图 补充&…...

课程11:仓储层Repository实现、AutoMapper自动映射
课程简介目录 🚀前言一、Repository项目1.1创建Repository项目1.2 添加类1.2.1、添加类 RolePermissionRepositiory1.2.2、添加项目引用1.2.3、注入数据库上下文1.3 RolePermissionRepositiory接口的实现二、Repository注入2.1 提取接口2.2 添加项目依赖2.3 项目入口添加依赖…...

关于作用域的那些事(进阶)
一、作用域 原理: 作用域 > 房子 > 除了对象的{}都构成一个作用域 作用域 > 为了区别变量.不同作用域内声明的变量是各不相同的.(就算名字相同). 作用域语法: let x 10; (全局变量). if () {块级作用域 let y 20; (局部变量)} for () {块级作用…...

小技巧notebook
小技巧notebook 1、MybatisPlus 批量保存 从BaseMapper接口方法可知,mybatis plus mapper只有根据id批量删除和查询,没有批量保存(insert 、update),要实现也很简单,需要定义一个Service Service Slf4j …...

【2451. 差值数组不同的字符串】
来源:力扣(LeetCode) 描述: 给你一个字符串数组 words ,每一个字符串长度都相同,令所有字符串的长度都为 n 。 每个字符串 words[i] 可以被转化为一个长度为 n - 1 的 差值整数数组 difference[i] &…...

Java面试-每日十题
目录 1.try-catch-finally中的finally的执行机制 2.什么是Exception和Error 3.Throw和Throws的区别 4.Error与Exception区别 5.Java中的I/O流是什么,分为几类 6.I/O与NI/O 7.常用的I/O的类有哪些 8.字符流与字节流的区别 9.Java反射创建对象 10.什么是类的…...

java.awt.datatransfer.Clipboard剪切板获取String字符串文本
java.awt.datatransfer.Clipboard剪切板获取String字符串文本 有两种方法获取 直接从Clipboard获得 (String) systemClipboard.getData(DataFlavor.stringFlavor);从Clipboard获得Transable再获得String (String) systemClipboard.getContents(null).getTransferData(DataFlav…...

HCIA——VLAN
目录 1,什么是VLAN: 2,如何实现VLAN: 3,VLAN的划分方式: 4,交换机接口类型: 1,Access接口: 2,Trunk接口:允许将一个接口划分给多…...

测试分析流程及输出项
测试分析 一、确认测试范围 根据测试项目的不同需求,有大致几类测试项目类型:商户平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析,并给出对应…...

OO设计原则
OO设计原则:SOLID SOLID SRP(The Single Responsibility Principle,单一责任原则) 不应有多于1个的原因使得一个类发生变化一个类,一个责任 OCP(The Open-Closes Principle,开放-封闭原则&…...

《深入理解计算机系统(CSAPP)》第5章 优化程序性能 - 学习笔记
写在前面的话:此系列文章为笔者学习CSAPP时的个人笔记,分享出来与大家学习交流,目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记,在复习回看时发现部分内容存在一些小问题,因时间紧张来不及再次整理…...

【Spring Boot】033-使用 `@ResponseBody` 注解代替`ServletResponse`?
【Spring Boot】033-使用 ResponseBody 注解代替ServletResponse? 文章目录 【Spring Boot】033-使用 ResponseBody 注解代替ServletResponse?0、全局总结一、ResponseBody 注解与 ServletResponse 比较1、ResponseBody 注解2、ServletResponse3、总结 二…...

【openGauss实战13】闪回技术
📢📢📢📣📣📣 哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】!😜&am…...

Top大学教授:青年学者,请避免这些写作问题→
在科研初期,很多作者由于缺乏经验和指导,糊里糊涂地发了一些质量较低的论文。 为了帮助青年科学家提高写作能力,比利时鲁汶大学的Blocken教授(同时也是Building & Environment、Journal of Wind Engineering & Industrial…...

使用midjourney搞出一套三国人物画像!
当下已进入如火如荼的全民AI时代,最近体验了下midjourney,使用它的以图生图功能生成出来一套三国人物画像,和大家分享下使用心得。 使用midjourney的准备工作 下载工具 使用midjourney生产图片依赖的工具和流程,大致如下&#x…...

ELK日志分析系统
ELK日志分析系统 日志主要包括系统日志/var/log 应用日志 安全日志secure, rsyslog远程传输日志进行汇总集中化管理,日志统计和检索又成为一件比较麻烦的事情,、 1、完整日志系统基本特征 收集:能够采集多种来源的日志数据 …...

整型在内存中的存储
目录 一、为什么内存中存储补码? 二、大小端概念 百度笔试试题: 几道小题: 一、为什么内存中存储补码? 上一节我们了解了原码,反码,补码的概念(http://t.csdn.cn/N0grg)ÿ…...

子集-回溯算法
1题目 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入:nums [1,2,3] 输出:[[],[1],[2],[1…...

公司study three
ctrlwind:新建桌面 ctrlwin 箭头 切换桌面 WIN CTRL F4 删除桌面 stream foreach遍历 instFormModifytracesList.stream().forEach(s->{ s.setModifyUser(sysUserTemplate.getNameById(s.getModifyUser()));});拼接 String collect2 peopleList.stream()…...

【运维】speedtest测试
目录 docker 布署 布署云端 docker布署 云端放置于已有容器里 librespeed/speedtest: Self-hosted Speedtest for HTML5 and more. Easy setup, examples, configurable, mobile friendly. Supports PHP, Node, Multiple servers, and more (github.com) docker 布署 获取…...

CycloneDDS开源代码在Linux系统上编译生成可执行文件的详细步骤
cyclonedds开源代码在Linux系统上编译生成可执行文件的详细步骤 1 远程仓库CycloneDDS源码下载2 创建build目录3 进入build目录4 指定安装路径前缀5 编译 cmake --build6 编译完成后进行安装7 版本构建并编译7.1 虚拟机网络桥接7.2 镜像源添加7.3 CUnit单元测试工具安装7.4 编译…...

PLL锁相环的一部分--鉴频鉴相器
鉴频鉴相器作为锁相环的一部分也是有相对应的独立芯片. 鉴频鉴相器芯片主要有以下几种: LM565/LM565C 鉴频鉴相器芯片XR2211CP 鉴频鉴相器芯片NE567 比较器、鉴频、鉴相 ICMC1496/LM1496 综合运算放大器与调制/解调器 ICLM567 比较器、鉴频、鉴相 ICMC100EP2100 高…...