目录
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,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1
bool 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
\qquad 在Linux中,系统创建的设备对象是struct device类型的对象。struct device是Linux内核中用于表示设备的数据结构,它包含了设备的各种属性和资源信息。
头文件路径 :/include/linux/device.h
struct device {
struct device *parent; 指向父设备的指针。在设备树中,设备节点可以有层次结构,这个成员用于指示设备
的父设备。如果不指定(NULL),注册后的设备目录在/sys/device下。
struct device_private *p;
struct kobject kobj; 存储设备对象的内核对象。kobject是内核对象的通用表示,用于实现设备对象的层次结构和管理。
const char *init_name; 设备的初始名称。通常是设备树节点的名称。
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; 指向设备驱动的指针。设备对象与设备驱动相关联,用于表示设备与驱动之间的绑定关系。
void *platform_data; 指向平台数据的指针。平台数据是设备特定的信息,用于传递给驱动程序。
struct dev_pm_info power; 设备电源管理信息。它存储了设备的电源状态和电源相关的信息。
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
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. */
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 */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_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。ACPI匹配是Advanced Configuration and Power Management Interface 高级配置和电源管理接口。主要是PC上用,这里不介绍。
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 0X114001E4
void 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为1
atomic_set(&pcdev->have_open,1);
//初始化led2
led_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,记得这里要把原子量加回到0
return -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 -c
clear
lsmod /drv/led-access-device.ko
lsmod /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 0X114001E4
void 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 0X114001E4
void 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为1
atomic_set(&p_dev->have_open,1);
//初始化两个设备的led2
led_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,记得这里要把原子量加回到0
return -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 -c
clear
insmod /drv/led-acc-dev2.ko
insmod /drv/led-acc-dev3.ko
insmod /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.1.1 宏(of_match_ptr)的说明
在include/linux/of.h中的定义是这样的
#ifdef CONFIG_OF //该标识意为系统支持设备树
#define of_match_ptr(_ptr) (_ptr)
#else
#define of_match_ptr(_ptr) NULL
\qquad
在 Linux 内核中,CONFIG_OF 是一个配置选项,用于启用或禁用设备树支持。选择启用这个选项可以允许内核使用设备树来描述硬件设备和平台特定的信息。
\qquad
当 CONFIG_OF 被定义时,of_match_ptr 宏的定义是:
#define of_match_ptr(_ptr) (_ptr)
这样定义的目的是方便使用指针来定义设备树匹配表。
\qquad
通常在驱动程序中,我们会定义一个设备树匹配表,其中包含一组匹配规则。每个匹配规则由设备树中的 compatible 属性和相应的处理函数组成。使用 of_match_ptr 宏可以简化这个过程,只需要将指针传递给宏,宏会将指针原样返回,作为设备树匹配表的一部分。
假设我们有一个驱动程序,并且定义了一个设备树匹配表 lcd_dt_ids
,代码如下所示:
static const struct of_device_id lcd_dt_ids[] =
{
{ .compatible = “tiny4412, lcd_s702”, },
{},
};
如果 CONFIG_OF 被定义,我们可以使用 of_match_ptr 宏将指针 my_driver_match 传递给设备树匹配表:
static struct platform_driver lcd_driver =
{
.driver = {
.name = “lcd_s702”,
.of_match_table = of_match_ptr(lcd_dt_ids),
},
.probe = lcd_probe,
.remove = lcd_remove,
};
这样,.driver_data 字段的值将是 my_driver_match 指针的值,作为设备树匹配表的一部分,用于设备的匹配。
\qquad
当 CONFIG_OF 没有被定义时,of_match_ptr 宏的定义是
#define of_match_ptr(_ptr) NULL
这样定义的目的是在没有设备树支持时,将设备树匹配表的指针设置为 NULL。
\qquad
综上所述,of_match_ptr 宏的作用是简化设备树匹配表的定义,并提供了一种灵活的方式来处理设备树匹配表的指针,根据 CONFIG_OF 的定义动态决定返回值。
注:当然该宏可用可不用。
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”);语句来取出设备树节点。
3.3.4 一个用设备树匹配的中断按键的platform实例
本实例和上一章中断管理里的例子是一样的,只是把匹配模式改为设备树匹配,并具由驱动自动创建设备树文件。
硬件说明
设备树
顶层 , cpu中断控制器节点
该节点定义在设备树文件在arch/arm/boot/dts/exynos4.dtsi中,系统已帮我们写好,需要知道其含义,以及该含义的出处
上层,gpio中断控制器节点
该节点定义在设备树 文件在arch/arm/boot/dts/exynos4x12-pinctrl.dtsi ,系统已帮我们写好,需要知道其含义及出处
按键节点的设备树定义在设备树文件arch/arm/boot/bdts/exynos4412-fs4412.dts
程序代码
/*************************************************************************
> File Name:platform-key-irq.c
驱动程序根据应用层的flag标志决定是否采用阻塞或非阻塞的工作方式
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/string.h>
#include "public.h"
/*1、定义重要的变量及结构体*/
#define DEV_NUM 3
struct x_dev_t{
struct cdev my_dev; //cdev设备描述结构体变量
wait_queue_head_t rq; //读等待队列
spinlock_t lock;
unsigned int gpio_num;
struct key_data_t key_data;
int IRQ;
};
struct x_dev_t *x_dev;
struct class *key_class=NULL;//设备类
int major=0 , minor=0; //用于标示当前已申请的设备号
/*所有驱动函数声明*/
loff_t llseek (struct file *, loff_t, int);
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={
.open = open,
.release = release,
.read = read,
};
//中断处理函数
irqreturn_t irq_handler(int irq,void *p){
struct x_dev_t *xdev =(struct x_dev_t *)p;
spin_lock(&xdev->lock);
xdev-> key_data.key_num = xdev->IRQ;
xdev-> key_data.statue = gpio_get_value(xdev->gpio_num);
xdev->key_data.new = 1;
printk("driver: Interrupt is happend , IRQ = %d\n" , xdev->key_data.key_num);
printk("driver: key status is %d\n",xdev->key_data.statue);
spin_unlock(&xdev->lock);
return IRQ_HANDLED;
}
/*初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
static int cdev_setup(struct x_dev_t *p_dev , dev_t devno , struct platform_device *pdev){
int unsucc =0;
int ret = 0;
struct device_node *IRQ_NODE=NULL;
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;
}
spin_lock_init( &p_dev->lock); //初始化自旋锁,为1
init_waitqueue_head(&p_dev->rq);//22初始化读等待队列
//读取设备树key节点 和gpio属性
IRQ_NODE = pdev->dev.of_node;//这里直接从platform_device结构中读取
p_dev->gpio_num = of_get_named_gpio(IRQ_NODE,"key_gpio" ,0 );
if (IRQ_NODE){
p_dev->IRQ = irq_of_parse_and_map(IRQ_NODE ,0);
if (p_dev->IRQ == 0){
printk("deiver:device_node is %s\n",IRQ_NODE->name);
printk("driver:IRQ is not found , MINOR is %d\n ",MINOR(devno));
return -1;
}
printk("driver: IRQ=%d--------\n",p_dev->IRQ);
ret = request_irq(p_dev->IRQ , irq_handler,IRQF_TRIGGER_FALLING , "fs4412_key2-4",p_dev);
if (ret){
return -1;
}
}else{
printk("driver : IRQ_NODE is not found,MINOR is %d\n", MINOR(devno));
return -1;
}
//创建设备文件了点
device_create(key_class,NULL,devno,NULL,"/dev/key_platform_%s",IRQ_NODE->name);
return 0;
}
int probe(struct platform_device *pdev)
{
dev_t devno;
int unsucc =0;
if (!x_dev){//只能创建一次
x_dev = kzalloc(sizeof(struct x_dev_t)*DEV_NUM , GFP_KERNEL);
if (!x_dev){
unsucc = -1;
goto err1;
}
memset(x_dev , 0 , sizeof(struct x_dev_t)* DEV_NUM);
}
/*创建 devno */
if (!major){ //主设备号只能创建一次
unsucc = alloc_chrdev_region(&devno , 0 , DEV_NUM , "platform_key_irq");
if (unsucc){
printk(" driver : creating devno is failed\n");
unsucc = -1;
goto err2;
}else{
major = MAJOR(devno);
minor = MINOR(devno);
printk("driver :platform_key_irq_%s major = %d ; minor = %d\n",pdev->dev.of_node->full_name,major,minor);
}
}
/*创建设备类*/
if(!key_class){
key_class = class_create(THIS_MODULE , "platform_key_class");
if (IS_ERR(key_class)){
unsucc = PTR_ERR(key_class);
goto err2;
}
}
/* 初始化cdev结构体,并联cdev结构体与file_operations.*/
/*注册cdev结构体到内核链表中*/
devno = MKDEV(major , minor);
unsucc= cdev_setup(x_dev+minor , devno , pdev) ;
minor++;
if (unsucc == 0){
printk("deiver : the driver platform-key-irq[%s] initalization completed\n", pdev->dev.of_node->full_name);
} else{
printk("deiver : the driver key-irq[%s] initalization failed\n", pdev->dev.of_node->full_name);
unsucc = -1;
goto err2;
}
return 0;
err2:
kfree(x_dev);
err1:
printk("driver:driver is faild\n");
return unsucc;
}
int remove(struct platform_device *pdev)
{
int i=0;
dev_t devno;
devno = x_dev->my_dev.dev;
for (i=0 ; i<DEV_NUM ; i++){
cdev_del(&(x_dev+i)->my_dev);
free_irq((x_dev+i)->IRQ , (x_dev+i));
}
unregister_chrdev_region(devno , DEV_NUM);
kfree(x_dev);
printk("***************the driver key_platform_irq exit************\n");
return 0;
}
struct of_device_id fs4412_key_id[] ={
{.compatible="fs4412,key2"},
{.compatible="fs4412,key3"},
{},
};
struct platform_driver pdriver = {
.probe = probe,
.remove = remove,
.driver = {
.name = "fs4412_key",
.of_match_table = fs4412_key_id,
},
};
static int __init my_init(void)
{
platform_driver_register(&pdriver);
return 0;
}
static void __exit my_exit(void)
{
platform_driver_unregister(&pdriver);
}
/*驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){
int minor = MINOR(pnode->i_rdev);
int major = MAJOR(pnode->i_rdev);
// pf->private_data = (void*)mem_dev; //把全局变量指针放入到struct file结构体里
struct x_dev_t *p = container_of(pnode->i_cdev , struct x_dev_t , my_dev);
pf->private_data = p; //把全局变量指针放入到struct file结构体里
if (pf->f_flags & O_NONBLOCK){ //非阻塞
printk("driver : block_memory[%d , %d] is opened by nonblock mode\n",major , minor);
}else{
printk("driver : block_memory[%d , %d] is opened by block mode\n",major,minor);
}
return 0;
}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
printk("driver:platform_key_irq is closed \n");
return 0;
}
/*file_operations结构全成员函数.read的具体实现*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
int res;
struct x_dev_t *pdev = pf->private_data;
if (pf->f_flags & O_NONBLOCK ){ // nonblock
if (pdev->key_data.new == 0){
printk("driver:no new interrupt\n");
return 0;
}
goto copy;
}
wait_event_interruptible(pdev->rq,(pdev->key_data.new >0));
copy:
spin_lock(&pdev->lock);
res = copy_to_user(buf , &pdev->key_data, sizeof(struct key_data_t));
spin_unlock(&pdev->lock);
if (res == 0)
return sizeof(struct key_data_t);
else
return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");