传统的驱动方式,驱动代码与设备数据混在一个程序文件中,这会导致开发不方便以及一些功能难以支持:
\qquad 为了使得一个驱动可以在不同平台上适配同一类但资源分配不同的设备。这时需要使得设备资源信息独立于驱动,驱动不再绑定具体设备。因此这种思路下就形成了总线设备驱动模型。
\qquad 这个总线、设备、驱动模型的目的在于使驱动只管驱动,设备只管设备资源,总线负责匹配设备和驱动。驱动则以标准途径拿到板级信息,这样,驱动就可以独立于具体的设备。
\qquad 在linux2.6以后的设备驱动模型中,需关心总线、设备和驱动这三个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
\qquad 在一个现实的设备中,一类外设本就是挂接在如PCI\USB\I2C\SPI等总线上。另一类则是不依附于此类总线,而是挂接在SOC内存空间。因此,在LINUX上,发明了一种虚拟的总线,称为platform总线,用于在驱动层面上来虚拟挂接这些设备与驱动。在platform虚拟总线上的设备称为platform_device,而驱动则称为platform_driver。
\qquad struct device来表示一个具体设备,主要提供具体设备相关的资源(如寄存器地址、GPIO、中断等等)。以面向对象的角度来看待,struct platform_device 就好比是struct device的子类。
\qquad struct device_driver来表示一个设备驱动,一个驱动可以支持多个操作逻辑相同的设备。以面向对象的角度来看待,platform_driver就好比是struct device_driver的子类。
\qquad 对于那些非依赖实体总线的设备,当构造了该设备的platform_device和驱动platform_driver后,内核就通过platform虚拟总线机制将上面的设备和驱动关联起来,进行管理和匹配。
/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 */
};
以上数据结构,最主要的是
struct platform_device_id{
char name[20]; 匹配用名称
kernel_ulong_t driver_data; 需要向驱动传输的其它数据
};
#include
struct resource {
resource_size_t start; 资源起始位置
resource_size_t end; 资源结束位置
const char *name;
unsigned long flags; 区分资源是什么类型的 struct resource *parent, *sibling, *child; // 资源指针,可以构成链表
};
我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ 这两种。start 和 end 的含义会随着 flags而变更,如
\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;
};
定义在(driver\base\platform.c)
作用
platform_device_register()函数用于向内核注册一个platform设备。把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
函数原型:
#include
int platform_device_register(struct platform_device *pdev)
主要的参数:
函数机制:
作用:
platform_device_unregister()函数用于从内核中注销一个platform设备。把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release
函数原型:
#include
void platform_device_unregister(struct platform_device *pdev)
主要参数:
函数机制:
/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))
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;
};
/include/linux/mod_devicetable.h
struct of_device_id
{
char name[32];//设备名
char type[32];//设备类型
char compatible[128]; //用于device和driver的match,重点
const void *data;
};
//用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束。用于设备树匹配
作用:
函数用于注册一个platform驱动。
platform_driver_register():
函数原型:
#include
int platform_driver_register(struct platform_driver *drv)
参数:
机制:
作用
platform_driver_unregister()函数用于注销一个platform驱动。
函数原型:
#include
void platform_driver_unregister(struct platform_driver *drv)
参数:
机制:
作用
platform_get_resource()函数用于从一个platform设备中获取资源信息。
函数原型:
#include
struct resource *platform_get_resource(struct platform_device *pdev,
unsigned int type,
unsigned int num)
参数:
返回值:
机制:
\qquad 在此之前,我们所有的驱动程序在insmod以后,都需要手动去mknod创建对应的设备文件。在platform中,就开始可以通过函数自动的建立对应的设备文件。
自动创建涉及到以下几个函数,:
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
#include
#include
#include
#include
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可以读取该设备。
头文件:
/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内核的一个设备类。它包含如下主要成员:
主要功能与作用:
class_create()函数用于在内核中创建一个新的设备类。
函数原型:
#include
struct class *class_create(struct module *owner, const char *name)
参数:
返回值:
功能:
class_destroy()函数用于销毁一个先前通过class_create()创建的设备类。
函数原型:
声明在include/linux/device.h头文件中。
void class_destroy(struct class *cls)
参数:
功能:
如果类中还存在设备, class_destroy()会返回错误,防止非法销毁现用的类。在这种情况下,需要先销毁所有属于该类的设备,才可以释放类本身。所以,class_destroy()为Linux内核提供了一个安全的设备类销毁机制。
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, ...)
参数:
返回值:
功能:
一个使用例子:
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));
/include/linux/err.h
static inline void * __must_check ERR_PTR(long error)
{
return (void *) error;
}
ERR_PTR宏用于生成代表错误的指针值。
格式:
ERR_PTR(errcode)
参数:
返回值:
功能:
例如:
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内核错误处理机制的基石。
原型:
/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)
参数:
返回值:
功能:
/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)
参数:
返回值:
功能:
例如:
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()使用,可以很好地处理内核指针返回值中的错误案例。
\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 从代码中可以看出。总共有四种匹配方式。
\qquad linux3.x之后,ARM LINUX倾向于根据设备树中的内容自动展开platform_device。ACPI匹配是Advanced Configuration and Power Management Interface 高级配置和电源管理接口。主要是PC上用,这里不介绍。
名称匹配只能是一对一的匹配,就是一个driver只能对应一个device。这种匹配方式的优先级最低
编译后
insmod hello_device.ko
insmod hello_driver.ko
设备中增加资源,驱动中访问资源
\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。
#ifndef _H_PUBLIC_
#define _H_PUBLIC_
#include
#define LED_ON _IO('a',1)
#define LED_OFF _IO('a',0)
#endif
/*************************************************************************
> File Name: led-access-device.c
************************************************************************/
#include
#include
#include
#include
#include
#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");
/*************************************************************************
> File Name: led-access-driver.c
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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("");
/*************************************************************************
> File Name: test.c
> Created Time: Wed 19 Apr 2023 02:33:42 PM CST
************************************************************************/
#include
#include
#include
#include
#include
#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;
}
dmesg -c
clear
lsmod /drv/led-access-device.ko
lsmod /drv/led-access-driver.ko
./test.elf /dev/led-access-0
这种匹配,一个driver可以匹配多个device。优先级次之。
注意事项:
ID匹配模式可以实现一个driver对应多个device。
这里给出一个可运行的实例来说明基于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:
Probe dev0
Probe dev1
这证明driver成功匹配了两个platform device,并调用了probe两次进行了不同的处理。
所以,这个例子清晰地展示了如何通过id_entry.name与id_table的字符串匹配来实现platform框架的一对多对应,主要步骤是:
在Linux 3.14版本中,platform框架引入了id匹配模式,它允许一个platform driver匹配多个platform device,实现一对多的对应关系。
此机制的实现是:
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通常具有不同的设备号和设备节点。
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进行以下区分处理:
和前面的例子一样,用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
#define LED_ON _IO('a',1)
#define LED_OFF _IO('a',0)
#endif
led-acc-dev2.c
/*************************************************************************
> File Name: led-acc-dev2.c
************************************************************************/
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#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;
}
\qquad 设备树匹配,无需编写device模块,只需编写driver模块,只需要构造struct platform_driver,该结构直接与设备树中的某个节点相匹配,内核会自动生成对应的platform_device。优先级最高。
注意事项:
1、由于是使用compatible属性进行与设备树的匹配,要求设备树下地的compatible属性值不能含空格。
2、 id_table可不设置,platform_driver中的device_driver.name成员必须要指定。
内核启动时根据设备树自动产生的设备 ------ 优先级最高
在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 的定义动态决定返回值。
注:当然该宏可用可不用。
还是以fs4412板上的led驱动做为实例。
设备树环境
代码
注意:
由于在系统完成本driver与设备树匹配后,会自动生成platform_device结构体,并在该结构体中的device.of_node存储了设备树的对应节点。
因此这里的pnode就直接从p_pltdev->dev.of_node中取出就好了。不需要再像初级设备树驱动那样用 pnode=of_find_node_by_path(“/fs4412_leds”);语句来取出设备树节点。
本实例和上一章中断管理里的例子是一样的,只是把匹配模式改为设备树匹配,并具由驱动自动创建设备树文件。
设备树
顶层 , 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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("");