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