Linux驱动之设备树
作者:互联网
14、设备树
1、什么是设备树?
设备树是一种描述硬件资源的数据结构, 它通过bootloader将硬件资源传给内核,使得内核和硬件资源描述相对独立。
2、设备树的由来
要想了解为什么会有设备树,设备树是怎么来的,我们就要先来回顾一下在没有设备树之前我们是怎么来写一个驱动程序的。以字符设备驱动代码框架为例,我们一起来回顾下。任何的设备驱动的编写,Linux已经为我们打好了框架,我们只要像做完形填空一样填写进去就可以了。
字符设备驱动框架
杂项设备驱动框架
通过这些框架,我们可以很容易编写我们的驱动代码,但是,当我们用这个框架非常熟练的时候,我们就会发现虽然这个方法很简单,但是非常不容易扩展,当我们有很多很多相似设备的时候,如果我们都是按照这个框架来完成,那就要写很多遍这个流程,但是多个相似设备之间真正有差异的地方只有框架的第四步,也就是初始化硬件的部分,其他步骤的代码基本都是一样的。这样就会造成大量的重复代码。但是,我们在编写驱动代码的时候,我们要尽量做到代码的复用,也就是一套驱动尽量可以兼任很多设备,如果我们还按照这个来编写就不太符合我们的规则了。
为了实现这个目标,我们就要把通用的代码和有差异的代码分离出来,来增强我们驱动代码的可移植性。所以,设备驱动分离的思想也就应运而生了,在Linux中,我们是在写代码的时候进行分离,分离是把一些不相似的东西放到了dev.c,把相似的东西放在了dri.c,如果我们有很多相似的设备或者平台,我们只要修改dev.c就可以了,这样我们重复性的工作就大大的减少了。这个就是平台总线的由来。
平台总线这个方法有什么弊端呢?
当我们用这个方法用习惯以后就会发现,假如soc不变,我们每换一个平台,都要修改C文件,并且还要重新编译。而且会在arch/arm/plat-xxx和arch/arm/mach-xxx下面留下
大量的关于板级细节的代码。并不是说这个方法不好,只是从 Linux的发展来看,这些代码相对于Linux内核来说就是“垃圾代码”,而且这些“垃圾代码”非常多,于是就有了LinuxTorvalds那句简单粗暴的话:
this whole ARM thing is a fucking pain in the ass为了改变这个现状,设备树也就被引进到ARM Linux上了,用来剔除相对内核来说的“垃圾代码”,即用设备树文件来描述这些设备信息,也就是代替device.c文件,虽然拿到了内核外面,但platform 匹配上基本不变,并且相比于之前的方法,使用设备树不仅可以去掉大量“垃圾代码”,并且采用文本格式,方便阅读和修改,如果需要修改部分资源,我们也不用在重新编译内核了,只需要把设备树源文件编译成二进制文件,在通过bootloader传递给内核就可以了。内核在对其进行解析和展开得到一个关于硬件的拓扑图。我们通过内核提供的接口获取设备树的节点和属性就可以了。即内核对于同一soc的不同主板,只需更换设备树文件dtb即可实现不同主板的无差异支持,而无需更换内核文件。
设备树就是代替了device文件,将硬件信息从内核中剥离出来, 设备树就是用来描述硬件资源的
3、 设备树的基本概念
1、为什么叫设备树
因为它的语法结构像树一样,所以管他叫设备树
2、常用名词解释
名称 | 功能 |
---|---|
DT | Device Tree 设备树 |
FDT | Flattened Device Tree // 展开设备树 // 开放固件,设备树起源于OF,所以我们在设备树中可以看到很多有of字母的函数 of开头的函数就是与设备树相关的 |
dts | device tree source 设备树源码 描述硬件资源 |
dtsi | device tree source include 更通用的设备树代码,也就是相同芯片但不同平台都可以用的代码 |
dtb | device tree blob DTS 编译后得到的DTB文件 二进制文件 |
dtc | device tree compiler 设备树编译器 |
4、设备树基本语法
设备树是描述硬件资源的文件,就是替代了平台总线的device文件
1、设备树的基本框架
(1) 设备树从根节点开始,每个设备都是一个节点。
(2) 节点和节点之间可以互相嵌套,形成父子关系
(3) 设备的属性用**key-value对(键值对)**来描述,每个属性用分号结束
2、设备树语法
2.1 节点
节点就好比一颗大树,从树的主干开始,然后一节一节的树枝,这个就叫节点。在代码中的节点是什么样子呢。我们把上面模板中的根节点摘出来,如先下所示就是根节点
2.1.1 根节点
//根节点
/ {
}; //分号结束
2.2.2 子节点
树枝就相当于设备树的子节点,同样我们把子节点摘出来就是根节点里面的node1和node2,
/ { //根节点
node1 { //子节点
};//分号不可少
noed2 { //子节点
};
};
2.2.2 孙子节点
/{ //根节点
node1{ //子节点
child-node1{ //孙子节点
};
};//分号不可少
noed2{ //子节点
};
};
2.2 节点名称
节点的命名有一个固定的格式。
<名称>[@<设备地址>]
<名称> 节点的名称也不是任意起的,一般要体现设备的类型而不是特点的型号,比如网口,应该命名为ethernet,而不是随意起一,个比如111
<设备地址> 就是用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址(并不是在driver中获取的地址),他主要是用来区分用
注意事项:
(1) 同一级的节点只要地址不一样,名字是可以不唯一的。
(2) 设备地址是一个可选选选,可以不写。但是为了区分和理解,一般都是写的
2.3 节点别名
当我们找一个节点的时候,我们必须书写完整的节点路径,如果节点名很长,那么我们在引用的时候就十分不方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名)。
uart8:serial@022288000
其中 uart8 就是这个节点的别名,serial@022288000 就是节点名称
2.4 节点的引用
一般往一个结点里面添加内容的时候,不会直接把添加的内容写到节点里面,而是通过节点的引用来添加
例如
&uart8 {
pinctrl-name = "default";
pinctrl-0 = <&pinctrl_uart8>;
status = "okey";
};
&uart8 表示引用节点别名为uart8的节点,并往这个节点里面添加一下内容
pinctrl-name = "default";
pinctrl-0 = <&pinctrl_uart8>;
status = "okey";
注意事项:
编译设备树的时候,相同的节点不同属性信息都会被合并,相同节点的相同属性会被重写,使用引用可以避免移植者四处找节点。如dts和dtsi里面都有根节点,但最终会合并成一个根节点。
2.5 属性
(1) reg属性
reg属性用来描述一个设备的地址范围。
格式:
reg=<add1 length1 [add2 length2]...>
例子
seral@02288000{
reg = <0x101F200 0x1000>;
};
其中 0x101F200 就是起始地址, 0x1000是长度
(2) #address-cells 和 #size-cells 属性
#address-cells 用来设置子节点中reg地址的数量
#size-cells 用来设置子节点中reg地址长度的数量
例子
cpu{
#address-cells = <1>;
#size-cells = <1>;
serial@02288000{
compatible = "serial";
reg = <0x101F200 0x1000>;
};
};
(3) compatible 属性
compatible 是一个字符串列表,可以在代码中进行匹配。 就是platform 中的device name 和 driver name进行匹配
compatible = "my_led"
(4) status 属性
status 属性的值类型是字符串,这里我们只要记住两个常用的即可,
值 | 描述 |
---|---|
okay | 设备可以正常使用 |
disable | 设备不能正常使用 |
3、自定义设备节点
这里以led为例,前面说过,设备树就是描述硬件资源的文本,也就是平台总线的device
在根节点下添加设备节点 名字是led myled是别名
/* 添加自定义节点*/
myled:led{
compatible="myled"; //描述
reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
status = "okay"; //okay 不是 okey
};
然后执行make dtbs
编译单独的设备树文件 make exynos4412-itop-elite.dtb 后面跟上我们需要生成的设备树文件即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YUMCUJzi-1619336495815)(linux驱动.assets/image-20210416175155914.png)]
再将设备树通过tftp copy到内存中运行
然后在/proc/devices 目录下便可看到新添加的设备节点led
5、获取设备树资源 of函数
device_node 结构体 描述节点
设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。Linux内核使用 device_node 结构体来描述一个节点,此结构体定义在include/linux/of.h 下
#include<linux/of.h>
struct device_node {
const char *name; // 节点名字
const char *type; // 设备类型
phandle phandle;
const char *full_name; //节点全名
struct fwnode_handle fwnode;
struct property *properties; //属性
struct property *deadprops; /* removed properties */
struct device_node *parent; //父节点
struct device_node *child; //子节点
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
property 结构体 描述属性
#include<linux/of.h>
struct property {
char *name; //属性名字 compatible 就是属性名字
int length; //属性长度 compatible = "test" 长度为5
void *value; //属性值 t例如 test
struct property *next; //下一个属性
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
获取设备树文件节点里面资源的步骤
1、查找我们要找的节点
2、获取我们需要的属性值
1、查找节点的常用of函数
(1) of_find_node_by_path 函数
#include<linux/of.h>
static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}
//成功返回节点, 失败返回NULL
path : 带有全路径的节点名,可以使用节点的别名, 比如上节添加自定义节点"/led" 就是这个节点的全路径名
(2) of_get_parent 函数
用于获取指定节点的父节点(如果有父节点的话)
#include<linux/of.h>
extern struct device_node *of_get_parent(const struct device_node *node);
//成功 找到的父节点 失败 NULL
node 要查找的父节点的节点(当前节点)
(3) of_get_next_child 函数
#include<linux/of.h>
extern struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
//成功返回子节点,失败返回NULL
node 父节点 (当前节点)
prev 前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始
这些都配合第一个函数of_find_node_by_path 来使用
2、获取设备节点的属性
#include<linux/of.h>
extern struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
//返回值 成功返回 找到的属性, 失败返回NULL
参数 | 描述 |
---|---|
np | 设备节点 |
name | 属性名字 |
lenp | 属性值的个数 |
1、读取整型属性 如reg
有些属性只有一个整型值,这四个函数就是用于读取这种只有一个整型值的属性,分别用于读取u8, u16,u32和u64类型的属性值
of_property_read_u8
of_property_read_u16
of_property_read_u32
of_property_read_u64
static inline int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
{
return of_property_read_u8_array(np, propname, out_value, 1);
}
static inline int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
{
return of_property_read_u16_array(np, propname, out_value, 1);
}
static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
{
return of_property_read_u32_array(np, propname, out_value, 1);
}
extern int of_property_read_u64(const struct device_node *np,
const char *propname, u64 *out_value);
//成功返回0 ,失败返回 负数
参数 | 描述 |
---|---|
np | 设备节点 |
proname | 要读取的属性的名字 |
out_value | 读取到的数值 |
2、读取整型数组 如reg
of_property_read_u8_array
of_property_read_u16_array
of_property_read_u32_array
of_property_read_u64_array
static inline int of_property_read_u8_array(const struct device_node *np,
const char *propname,
u8 *out_values, size_t sz);
static inline int of_property_read_u16_array(const struct device_node *np,
const char *propname,
u16 *out_values, size_t sz);
static inline int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values, size_t sz);
static inline int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values, size_t sz);
// 返回值 成功返回0, 失败返回 负数
参数 | 描述 |
---|---|
np | 设备节点 |
proname | 要读取的属性名字 |
out_value | 读取到的数组值,分别为u8, u16, u32, u64 |
sz | 要读取的数组元素的数量 为1 的时候就和上面的读取整型数一样 |
3、读取字符串属性
例如读取 status = "okay"
static inline int of_property_read_string(const struct device_node *np,
const char *propname,
const char **out_string);
//成功返回0 ,失败返回 负数
参数 | 描述 |
---|---|
np | 设备节点 |
proname | 要读取的属性名字 |
out_string | 读取到的字符串值 |
实例
在编写设备树文件时,status = “okay” 不能写错了,否则会匹配不上
/* 添加自定义节点*/
myled:led{
compatible="myled"; //描述
reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
status = "okay";
};
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
/* 1、查找我们要找的节点
2、获取我们需要的属性值
*/
struct device_node *dev_no;
static int hello_init(void)
{
//获取节点信息
dev_no = of_find_node_by_path("/led");
if(NULL == dev_no)
{
printk("of find node by path is error!\n");
return -1;
}
printk("device_node name:%s\n", dev_no->name);
printk("device_node type:%s\n", dev_no->type);
printk("device_node full_name:%s\n", dev_no->full_name);
printk("device_node property value:%s\n", dev_no->properties->value);
printk("device_node property name:%s\n", dev_no->properties->name);
/*获取 reg属性*/
u32 reg_value[4];
int ret = of_property_read_u32_array(dev_no, "reg", reg_value, 4);
if (ret != 0)
{
printk("of property read u32 array is error!\n");
return -1;
}
int i = 0;
for (i = 0; i < 4; i++)
{
printk("reg_value[%d] : 0x%x\n", i, reg_value[i]);
}
/*获取 status属性*/
const char * out_value = NULL;
ret = of_property_read_string(dev_no, "status", &out_value);
printk("device_node property status value :%s\n", out_value);
printk("hello world\n");
return 0;
}
static void hello_exit(void)
{
printk("byb byb\n"); // 这里打印函数需要用printk 因为printf是C库函数
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
4、of_get_named_gpio 函数 获取GPIO标号
此函数获取 GPIO 编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备数中类似<&gpl2 0 GPIO_ACTIVE_HIGH> 的属性信息转换为对应的GPIO编号
#include<linux/of_gpio.h>
int of_get_named_gpio(struct device_node *np,const char *propname, int index);
// 返回值 成功返回 GPIO编号, 失败返回 负数
参数 | 描述 |
---|---|
np | 设备节点 |
propname | 包含要获取GPIO信息的属性名 |
index | 因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0 |
5、设备树platform下的物理地址向虚拟地址映射 of_iomap函数
作用: **of_iomap 函数用于直接内存映射,**以前我们会通过ioremap函数来完成物理地址到虚拟地址的映射,在设备树的platform driver中 通常使用of_iomap函数 , 二者完成的功能都是相同的
void __iomem *of_iomap(struct device_node *device, int index);
//返回值 成功返回映射后的首地址, 失败返回NULL
参数 | 描述 |
---|---|
device | 设备节点 |
index | reg属性中要完成内存映射的段,如果reg属性只有一段的话index就设置为0 两段的话就是1 依次增加 |
6、设备树下的platform平台总线 driver部分
设备树与driver进行匹配
使用的是struct of_device_id 结构体中的 compatible=“xxx” 和平台总线相似,不过匹配的对象变了
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
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;
};
struct of_device_id {
char name[32];
char type[32];
char compatible[128]; //匹配值
const void *data;
};
driver与设备树进行匹配
struct platform_device_id id_table = {
.name = "myledxxx" //与设备树中的compatible相同
};
const struct of_device_id dev_node[] = { // 三者之间优先级最高
{.compatible="myledxxx"},
{}
};
struct platform_driver pdriver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled", // 与device的名字相同
.owner = THIS_MODULE,
.of_match_table = dev_node
},
.id_table = &id_table //这里是一个指针 // 优先级比driver.name 优先级高
};
int _init()
{
platform_driver_register(&pdriver); // 注册platform_driver
}
三者之间的优先级 of_match_table> id_table > driver.name
匹配成功后 进入probe函数
probe函数获取资源
在资源匹配成功后,内核会将资源至填充platform_device 那么我们需要的设备树信息也在其中
在 platform_device device device_node 下
1、直接获取
printk("name : %s\n", pdev->dev.of_node->name);
2、使用无设备树platform的获取方法
使用platform_get_resource 方法
led_resource_con = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取GPL2CON
if (NULL == led_resource_con)
{
printk("platform_get_resource is error!\n");
return -EBUSY;
}
led_resource_dat = platform_get_resource(pdev, IORESOURCE_MEM, 1); //获取GPL2DAT
if (NULL == led_resource_dat)
{
printk("platform_get_resource is error!\n");
return -EBUSY;
}
3、使用设备树的platform获取法
/* 有设备树的资源获取硬件资源 */
/*获取reg*/
u32 regbuf[4];
int ret = of_property_read_u32_array(pdev->dev.of_node, "reg", reg_buf, 4);
if (0 != ret)
{
printk("of property read u32 array is error!\n");
goto err_region;
}
printk("led_con:0x%x\n", regbuf[0]);
printk("led_con length :0x%x\n", regbuf[1]);
printk("led_dat:0x%x\n", regbuf[2]);
printk("led_dat length :0x%x\n", regbuf[3]);
设备树platform下的物理地址向虚拟地址映射 of_iomap函数
作用: **of_iomap 函数用于直接内存映射,**以前我们会通过ioremap函数来完成物理地址到虚拟地址的映射,在设备树的platform driver中 通常使用of_iomap函数 , 二者完成的功能都是相同的
void __iomem *of_iomap(struct device_node *device, int index);
//返回值 成功返回映射后的首地址, 失败返回NULL
参数 | 描述 |
---|---|
device | 设备节点 |
index | reg属性中要完成内存映射的段,如果reg属性只有一段的话index就设置为0 两段的话就是1 依次增加 |
myled:led{
compatible="myled"; //描述
reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
status = "okay";
};
例如这个设备树节点 index 就设置为2
问题1: 获取到的虚拟地址映射,如何操作; 因为只有一个地址,映射的地址区 如何确定con 和 dat
获取完资源然后注册杂项设备或者字符设备
7、deivce-tree platform的led杂项设备
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> //struct plantform_devide / plantform_driver
#include <linux/ioport.h> //
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h> //io_map
struct resource * led_resource_con;
struct resource * led_resource_dat;
static unsigned int * led_con; //ioremap 映射的con地址
static unsigned int * led_dat;
static unsigned int length_con,length_dat;
static unsigned int * Led_GPL2_add;
ssize_t led_read (struct file *file, char __user *user, size_t n, loff_t * loff)
{
return 0;
}
ssize_t led_write (struct file *file, const char __user *user, size_t n, loff_t * loff)
{
char buf[128] = {0};
if (copy_from_user(buf, user, n) != 0)
{
printk("copy from user is error!\n");
return -1;
}
if (buf[0] == 0x1)
{
*led_dat = 0x1;
}
else if (buf[0] == 0)
{
*led_dat = 0;
}
return 0;
}
int led_open (struct inode *inode, struct file *file)
{
//设置电平方向
*led_con |= 0x1; // output
return 0;
}
int led_release (struct inode *inode, struct file *file)
{
return 0;
}
/*file_operations 初始化*/
struct file_operations misc_fops = {
.owner = THIS_MODULE,
/*文件操作集*/
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*杂项设备初始化*/
struct miscdevice miscdev = {
.minor = MISC_DYNAMIC_MINOR, //自动分配次设备号
.name = "my_led",
.fops = &misc_fops,
};
int led_probe(struct platform_device *pdev) //这里的pdev 就是指向了 device 中的pdev
{
printk("led probe\n");
/*第一种获取硬件资源方法*/
// printk("%s\n", pdev->resource[0].name);
printk("name : %s\n", pdev->dev.of_node->name);
/*第二种*/
/*无设备树获取硬件资源*/
#if 0
led_resource_con = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取GPL2CON
if (NULL == led_resource_con)
{
printk("platform_get_resource is error!\n");
return -EBUSY;
}
led_resource_dat = platform_get_resource(pdev, IORESOURCE_MEM, 1); //获取GPL2DAT
if (NULL == led_resource_dat)
{
printk("platform_get_resource is error!\n");
return -EBUSY;
}
printk("led_con:0x%x\n", led_resource_con->start);
printk("led_dat:0x%x\n", led_resource_dat->start);
length_con = led_resource_con->end - led_resource_con->start + 1;
length_dat = led_resource_dat->end - led_resource_dat->start + 1;
#endif
/* 有设备树的资源获取硬件资源 */
/*获取reg*/
u32 regbuf[4];
int ret = of_property_read_u32_array(pdev->dev.of_node, "reg", regbuf, 4);
if (0 != ret)
{
printk("of property read u32 array is error!\n");
goto err_region;
}
printk("led_con:0x%x\n", regbuf[0]);
printk("led_con length :0x%x\n", regbuf[1]);
printk("led_dat:0x%x\n", regbuf[2]);
printk("led_dat length :0x%x\n", regbuf[3]);
/* 设备树下的 物理地址与虚拟地址的映射*/
#if 0
Led_GPL2_add = of_iomap(pdev->dev.of_node, 1);
if (NULL == Led_GPL2_add)
{
printk("of iomap is error!\n");
return -1;
}
led_con = Led_GPL2_add;
led_dat = Led_GPL2_add ;
printk("led_con:0x%x\n", led_con);
printk("led_dat:0x%x\n", led_dat);
#endif
/*将物理地址映射到虚拟地址*/
led_con = ioremap( regbuf[0], regbuf[1]);
if (NULL == led_con){
printk("ioremap is error!\n");
goto err_region;
}
led_dat = ioremap(regbuf[2], regbuf[3]);
if (NULL == led_dat){
printk("ioremap is error!\n");
goto err_region;
}
printk("led probe is success!\n");
return 0;
err_region:
release_mem_region(led_resource_con->start, length_con);
release_mem_region(led_resource_dat->start, length_dat);
return - EBUSY;
}
int led_remove(struct platform_device *pdev)
{
printk("led remove is success!\n");
return 0;
}
/*platform_driver初始化*/
struct platform_device_id id_table = {
.name = "myledxxx" //与设备树中的compatible相同
};
const struct of_device_id dev_node[] = { // 三者之间优先级最高
{.compatible="myledxxx"},
{}
};
struct platform_driver pdriver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled", // 与device的名字相同
.owner = THIS_MODULE,
.of_match_table = dev_node
},
.id_table = &id_table //这里是一个指针 // 优先级比driver.name 优先级高
};
static int hello_init(void)
{
int ret;
//注册平台驱动
ret = platform_driver_register(&pdriver);
if (0 != ret)
{
printk("platform driver register is error!\n");
return ret;
}
//注册杂项设备并生成设备节点
ret = misc_register(&miscdev);
if (ret != 0)
{
printk("misc deregister is error!\n");
return ret;
}
printk("hello world\n");
return 0;
}
static void hello_exit(void)
{
printk("byb byb\n"); // 这里打印函数需要用printk 因为printf是C库函数
platform_driver_unregister(&pdriver); //注销
misc_deregister(&miscdev); //卸载杂项设备
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
8、deivce-tree platform的led字符设备
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
/*
1、注册 platform driver
2、构建file_operations
3、获取硬件资源
4、注册字符设备驱动
4、生成字符设备节点
*/
static unsigned int * led_con;
static unsigned int * led_dat;
struct device *device_cdev;
struct class *class_cdev;
ssize_t led_read (struct file *file, char __user * user, size_t size, loff_t * loff )
{
printk("read is success!\n");
return 0;
}
ssize_t led_write (struct file *file, const char __user *user, size_t size, loff_t * loff)
{ char buf[128] = {0};
int ret ;
printk("write is success!\n");
ret = copy_from_user(buf, user, size);
if (0 != ret)
{
printk("copy form user is error!\n");
return ret;
}
if (buf[0] == 1 || buf[0] == 0)
* led_dat = buf[0];
return 0;
}
int led_open(struct inode *inode, struct file *file)
{
*led_con |= 0x1;
printk("open is success!\n");
return 0;
}
int led_release (struct inode *inode, struct file *file)
{
printk("release is success!\n");
return 0;
}
/*2、获取硬件资源 */
int ledprobe(struct platform_device *pdev)
{
u32 regbuf[4];
/*获取reg属性中的寄存器物理地址, 然后进行物理地址与虚拟地址的映射*/
int ret = of_property_read_u32_array(pdev->dev.of_node, "reg", regbuf, 4);
if (0 != ret)
{
printk("of property read u32 array is error!\n");
return -1;
}
printk("led_con:0x%x\n", regbuf[0]);
printk("led_con length :0x%x\n", regbuf[1]);
printk("led_dat:0x%x\n", regbuf[2]);
printk("led_dat length :0x%x\n", regbuf[3]);
/* 物理地址与虚拟地址映射*/
led_con = ioremap(regbuf[0], regbuf[1]);
if (NULL == led_con)
{
printk("ioremap is error!\n");
return -1;
}
led_dat = ioremap(regbuf[2], regbuf[3]);
if (NULL == led_dat)
{
printk("ioremap is error!\n");
return -1;
}
return 0;
}
int ledremove(struct platform_device *pdev)
{
return 0;
}
struct of_device_id of_match_table = { // 与设备树节点进行匹配
.compatible = "myledxxx"
};
/*1、初始化platform driver*/
struct platform_driver pdev = {
.probe = ledprobe, // 与 of_match_table 匹配成功后进入probe函数获取硬件资源
.remove = ledremove,
.driver = {
.name = "myledxxx", //无设备树时 使用.name 与device进行匹配
.owner = THIS_MODULE,
.of_match_table = &of_match_table,
}
};
//3、注册字符设备驱动
/*3.1 分配设备号*/
dev_t dev_number;
/*3.2 定义cdev*/
struct cdev cdev_;
/*3.3 构建file_operation结构体*/
struct file_operations fop = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
.read = led_read,
};
static int char_driver_init(void)
{
/*1、注册platform driver*/
int ret = platform_driver_register(&pdev);
if (0 != ret)
{
printk("platform driver register is error!\n");
return -1;
}
/*3.1 分配设备号(动态分配设备号)*/
ret = alloc_chrdev_region(&dev_number, 0, 1, "my_led");
if (0 != ret)
{
printk("alloc chrdev region is error!\n");
return ret;
}
/*3.4 初始化cdev*/
cdev_.owner = THIS_MODULE;
cdev_init(&cdev_, &fop);
/*3.5 注册字符设备到内核*/
ret = cdev_add(&cdev_, dev_number, 1);
if (0 != ret)
{
printk("cdev add is error!\n");
return -1;
}
/*4、生成设备节点*/
/*4.1 创建字符设备类*/
class_cdev = class_create(THIS_MODULE, "my_led");
if (NULL == class_cdev)
{
printk("class create is error!\n");
return -1;
}
/*生成设备节点*/
device_cdev = device_create (class_cdev, NULL, dev_number, NULL, "my_led");
if (NULL == device_cdev)
{
printk("device create is error!\n");
}
return 0;
};
static void char_driver_exit(void)
{
device_destroy(class_cdev, dev_number); // 卸载设备节点
class_destroy(class_cdev); //卸载设备类
cdev_del(&cdev_); //卸载cdev
iounmap(led_con); // 取消地址映射
iounmap(led_dat);
unregister_chrdev_region(dev_number, 1);// 注销设备号
platform_driver_unregister(&pdev); // 注销platform driver
}
module_init(char_driver_init);
module_exit(char_driver_exit);
MODULE_LICENSE("GPL");
关于设备树与driver匹配失败的问题
1、 烧写完设备树之后,进入**/sys/devices/platform** 目录下查看是否有我们添加的设备树节点
2、 driver部分的of_match_table 的 compatible 一定要和设备树中的 compatible 一样
3、 如果**/sys/devices/platform** 中找不到我们添加的设备树节点,那么查看设备树文件,status属性是否书写正确
status常用的两种属性 “okay” “disable”
标签:led,struct,Linux,device,驱动,printk,节点,设备 来源: https://blog.csdn.net/weixin_43937576/article/details/116130872