文章目录
- Linux设备驱动模型
- Linux 中的设备驱动模型组成
- 为什么要使用设备驱动模型
- 设备驱动模型的底层架构
- sysfs 目录
- kobject
- kobj_type
- kset
- 设备驱动模型三大组件
- 总线
- 设备
- 驱动
- 平台设备驱动
- 概述
- 平台设备驱动工作原理
- 核心变量与函数
- 工作流程
- 不使用设备树的平台设备驱动
- platform_driver
- platform_device
- 平台设备驱动代码编写
- 带设备树的平台设备驱动代码
- 设备树
- 语法
- 标准属性
- 追加数据
- 常用of 操作函数
- 带设备树驱动编写
Linux设备驱动模型
Linux 中的设备驱动模型组成
(1)类class、总线bus、设备device、驱动driver
(2)kobject和对象生命周期
(3)sysfs
(4)udev
为什么要使用设备驱动模型
我对于Linux 引入设备驱动模型的理解是就在于将一份驱动代码分成两份,一份代码是通用的也就是驱动driver ,令一份代码不是通用的会随着板子CPU的不同,发生改变。
设备驱动模型的底层架构
sysfs 目录
通过sys文件系统下面的目录和文件可以清楚的了解到Linux系统中的设备情况和组织关系。
sysfs提供一种可以显式描述内核对象,对象属性以及对象关系的方法。
sysfs在内核空间的组成要素 | 在用户空间的体现 |
kobject | 目录 |
attribute | 文件 |
relationship | 链接 |
kobject
树形结构中每一个目录与一个kobject对象相对应,其包含了目录的组织结构和名字等信息。在Linux系统中, kobject结构体是组成设备驱动模型的基本结构。
(1)kobject提供了最基本的设备对象管理能力,每一个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录,而不是文件。
(2)各种对象最基本单元,提供一些公用型服务如:对象引用计数、维护对象链表、对象上锁、对用户空间的表示
(3)设备驱动模型中的各种对象其内部都会包含一个kobject
(4)地位相当于面向对象体系架构中的总基类
struct kobject {
const char *name;//kobject的名字,且作为一个目录的名字
struct list_head entry;//连接下一个kobject结构
struct kobject *parent;//指向父亲kobject结构体,如果父设备存在
struct kset *kset; //指向kset集合
struct kobj_type *ktype; //指向kobject的属性描述符
struct sysfs_dirent *sd; //对应sysfs的文件目录
struct kref kref; //kobject的引用计数
unsigned int state_initialized:1; //表示该kobject是否初始化
unsigned int state_in_sysfs:1; //表示是否加入sysfs中
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
kobj_type
使用该kobject设备的共同属性
(1)很多书中简称为ktype,每一个kobject都需要绑定一个ktype来提供相应功能
(2)关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store)
(2)关键点2:attribute,提供在sysfs中以文件形式存在的属性,其实就是应用接口
struct kobj_type {
void (*release)(struct kobject *kobj);//释放kobject和其占用的函数
const struct sysfs_ops *sysfs_ops; //操作一个属性数组的方法
struct attribute **default_attrs; //属性数组的方法
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
kset
kset是具有相同类型的kobject的集合
kset包含了一个kobject,其实它相当于一个链表节点,虽然Kobject包含了kset元素
(1)kset的主要作用是做顶层kobject的容器类
(2)kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构
(3)可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起
struct kset {
struct list_head list; //连接链表
spinlock_t list_lock; //链表的自旋锁
struct kobject kobj; //内嵌的kobject结构体,说明kset本身也是一个目录
const struct kset_uevent_ops *uevent_ops; //热插拔事件
};
设备驱动模型三大组件
不管是平台总线还是IIC总线都都有这样的调用路线:
当系统发现了新设备或者新驱动就会掉用相应总线的Match()进行匹配,当找到后就会掉用相对应的总线的Probe函数,最后Probe函数再调用驱动自己的Probe函数
总线
struct bus_type {
const char *name; //总线类型名
struct bus_attribute *bus_attrs; //总线属性和导出到sysfs中的方法
struct device_attribute *dev_attrs; //设备属性和导出到sysfs中的方法
struct driver_attribute *drv_attrs; //驱动程序属性和导出到sysfs中的方法
//匹配函数,检验参数2中的驱动是否支持参数1中的设备
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
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 dev_pm_ops *pm; //关于电源管理的操作符
struct bus_type_private *p; //总线的私有数据
};
设备
在Linux设备驱动模型中,每一个设备都由一个device结构体来描述。device结构体包含了设备所具有的一些通用信息。对于驱动开发人员来说,当遇到新设备时,需要定义一个新的设备结构体,将device作为新结构体的成员。这样就可以在新结构体中定义新设备的一些信息,而设备通用的信息就使用device结构体来表示。使用device结构体的另一个好处是,可以通过device轻松地将新设备加入设备驱动模型的管理中。
device中的大多数函数被内核使用,驱动开发人员不用关注
(1)struct device是硬件设备在内核驱动框架中的抽象
(2)device_register用于向内核驱动框架注册一个设备
(3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device
struct device {
struct klist_klist children;/*连接子设备的链表*/
struct device *parent;/*指向父设备的指针*/
struct kobject kobj;/*内嵌的kobject结构体*/
char bus_id[BUS ID SIZE];/*连接到总线上的位置*/
unsigned uevent suppress:1;/*是否支持热插拔事件*/
const char init_name;/*设备的初始化名字*/
struct device_type *type;/*设备相关的特殊处理函数*/
struct bus_type *bus;/*指向连接的总线指针*/
struct device_driver *driver;/*指向该设备的驱动程序*/
void *driver data;/*指向驱动程序私有数据的指针*/
struct dev_pm info power;/*电源管理信息*/
dev t deyt;/*设备号*/
struct class *class;/*指向设备所属类*/
struct attribute_group **groups;/*设备的组属性*/
void (*release) (struct device *dev);/*释放设备描述符的回调函数*/
}
驱动
(1)struct device_driver是驱动程序在内核驱动框架中的抽象
(2)关键元素1:name,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
(3)关键元素2: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 */
#if defined(CONFIG_OF)
const struct of_device_id *of_match_table;
#endif
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;//设备驱动私有数据
};
平台设备驱动
概述
并不所有设备都会有实体总线,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。
系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
match()成员函数,正是此成员函数确定了platform_device和platform_driver之间是如何进行匹配
有4种可能性,
一是基于设备树风格的匹配;
二是基于ACPI风格的匹配;
三是匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内);
第四种是匹配platform_device设备名和驱动的名字。
由以上分析可知,在设备驱动中引入platform的概念至少有如下好处。
1)使得设备被挂接在一个总线上,符合Linux 2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能。
2)隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
3)让一个驱动支持多个设备实例。譬如DM9000的驱动只有一份,但是我们可以在板级添加多份DM9000的platform_device,它们都可以与唯一的驱动匹配
平台设备驱动工作原理
核心变量与函数
(1)platform工作体系都定义在drivers/base/platform.c中
(2)两个结构体:platform_device和platform_driver
(3)两个接口函数:platform_device_register和platform_driver_register
struct platform_device {
const char * name; // 平台总线下设备的名字
int id; //设备名加ID名就得到了设备文件文件名
struct device dev; // 所有设备通用的属性部分
u32 num_resources; // 设备使用到的resource的个数
struct resource * resource; // 设备使用到的资源数组的首地址
const struct platform_device_id *id_entry; // 设备ID表
/* arch specific additions */
struct pdev_archdata archdata; // 自留地,用来提供扩展性的
};
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表
};
在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
工作流程
(1)第一步:系统启动时在bus系统中注册platform
(2)第二步:内核移植的人负责提供platform_device
(3)第三步:写驱动的人负责提供platform_driver
(4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了
(1)platdata其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称····)
(2)这些数据在设备和驱动match之后,会由设备方转给驱动方。驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。
(3)这样做的好处是:驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。
不使用设备树的平台设备驱动
使用平台设备API只需要引用下面的头文件
include/linux/platform_device.h
platform_driver
1 struct platform_driver {
2 int (*probe)(struct platform_device *);
3 int (*remove)(struct platform_device *);
4 void (*shutdown)(struct platform_device *);
5 int (*suspend)(struct platform_device *, pm_message_t state);
6 int (*resume)(struct platform_device *);
7 struct device_driver driver;
8 const struct platform_device_id *id_table;
9 bool prevent_deferred_probe;
10 };
当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用
platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,platform_driver_register 函数
原型如下所示:
int platform_driver_register (struct platform_driver *driver)
函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。
还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,
platform_driver_unregister 函数原型如下:
void platform_driver_unregister(struct platform_driver *drv)
函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
platform_device
22 struct platform_device {
23 const char *name;
24 int id;
25 bool id_auto;
26 struct device dev;
27 u32 num_resources;
28 struct resource *resource;
29
30 const struct platform_device_id *id_entry;
31 char *driver_override; /* Driver name to force a match */
32
33 /* MFD cell pointer */
34 struct mfd_cell *mfd_cell;
35
36 /* arch specific additions */
37 struct pdev_archdata archdata;
38 };
include/linux/ioport.h
18 struct resource {
19 resource_size_t start;
20 resource_size_t end;
21 const char *name;
22 unsigned long flags;
23 struct resource *parent, *sibling, *child;
24 };
在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,
然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:
int platform_device_register(struct platform_device *pdev)
函数参数和返回值含义如下:
pdev:要注册的 platform 设备。
返回值:负数,失败;0,成功。
如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform
设备,platform_device_unregister 函数原型如下:
void platform_device_unregister(struct platform_device *pdev)
函数参数和返回值含义如下:
pdev:要注销的 platform 设备。
返回值:无。
platform 设备信息框架如下所示:
平台设备驱动代码编写
该代码直接在globalmem驱动程序上修改 其实就上将之前在init 函数中做的工作转移到probe 函数 ,exit 函数中所作的工作转移到remove函数中,同时添加从设备驱动中获取参数的代码。
/*
* @Copyright:
* @FileNames:
* @Description: 申请全局内存4096 并使用该内存进行 用户和内核之间的数据交换
* @Author:
* @Date: 2022-07-29 09:03:02
* @Version: V1.0
* @LastEditTime: 2022-08-01 15:05:34
*/
#include <linux/module.h>//MOUDLE_XXX moudle_xxx
#include <linux/fs.h> //file_operations register_chrdev_region alloc_chrdev_region
#include <linux/init.h> //__init __exit
#include <linux/cdev.h> //cdev
#include <linux/uaccess.h>//copy_to_user copy_from_user
#include <linux/device.h> // device class
#include <linux/platform_device.h>
#define GLOBAL_MEM_SIZE 0X1000
#define GLOBAL_MEM_DEBUG 1
#define GLOBAL_MEM_CLASS_NAME "global_mem" //创建的类名称
#define GLOBAL_MEM_NODE_NAME "global_mem_0" //创建的节点名称
#define GLOBAL_MEM_MAJOR 250
//CMD
#define GLOBAL_MEM_CLEAR 0X01 //清理内存 命令
typedef struct {
//设备驱动变量
struct cdev cdev;
//设备号变量
dev_t devid;
int major;
int minor;
//设备节点相关变量
struct class *class;
struct device *device;
//用户变量
unsigned char mem[GLOBAL_MEM_SIZE];
}global_mem_t;
global_mem_t global_mem;
/**
* @function: global_mem_open
* @description: 打开设备 并将内存清零
* @input:
* @output:
* @return {*}
* @param {inode} *inode
* @param {file} *flip
*/
static int global_mem_open(struct inode *inode ,struct file *flip)
{
printk("OPEN GLOBAL MEM\r\n");
flip->private_data=(void *)(&global_mem);
return 0;
}
/**
* @function: global_mem_release
* @description: 释放设备节点
* @input:
* @output:
* @return {*}
* @param {inode} *inode
* @param {file} *flip
*/
static int global_mem_release(struct inode *inode ,struct file *flip)
{
printk("RELEASE GLOBAL MEM\r\n");
return 0;
}
/**
* @function: global_mem_read
* @description: 拷贝数据到用户区
* @input:
* @output:
* @return {*}
* @param {file*} flip 文件结构体
* @param {char __user} *buf 从用户区拷贝出来的数据
* @param {size_t} size 传入数据大小
* @param {loff_t} *ppos 当前数据位置
*/
static ssize_t global_mem_read(struct file* flip, char __user *buf,size_t size ,loff_t *ppos)
{
int ret=0;
printk("READ GLOBAL MEM\r\n");
global_mem_t* dev=(global_mem_t*) flip->private_data;
//当前文件指针所处于的位置
unsigned long p=*ppos;
//需要读出的个数
unsigned int count=(unsigned int)size;
//检查读取位置合法性
if(p>=GLOBAL_MEM_SIZE)return 0;
if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
//拷贝数据到用户区
ret=copy_to_user(buf,dev->mem+p,count);
if (ret<0){
ret=-EFAULT;
}else{
*ppos+=count;
ret=count;
if(GLOBAL_MEM_DEBUG)printk("READ %d BYTES FROM KERNEL At %d\n",count,p);
}
return ret;
}
/**
* @function: global_mem_write
* @description:
* @input:
* @output:
* @return {*}
* @param {file*} flip
* @param {char __user} *buf
* @param {size_t} size
* @param {loff_t} *ppos
*/
static ssize_t global_mem_write(struct file* flip, const char __user *buf,size_t size ,loff_t *ppos)
{
int ret=0;
printk("WRITE GLOBAL MEM\r\n");
global_mem_t* dev=(global_mem_t*) flip->private_data;
//当前文件指针所处于的位置
unsigned long p=*ppos;
//需要读出的个数
unsigned int count=(unsigned int)size;
//检查读取位置合法性
if(p>=GLOBAL_MEM_SIZE)return 0;
if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
ret=copy_from_user(dev->mem+p,buf,count);
if(ret<0)ret=-EFAULT;
else{
*ppos+=count;
ret=count;
if(GLOBAL_MEM_DEBUG)printk("WRITE %d BYTES TO KERNEL AT %d\n",(int)count,(int)p);
}
return ret;
}
static loff_t global_mem_llseek(struct file* flip ,loff_t offset,int orig)
{
loff_t ret=0;
if(orig==0)//从头开始偏移
{
if(offset<0){
ret=-EINVAL;
goto end;
}
if((unsigned int) offset>GLOBAL_MEM_SIZE){
ret=-EINVAL;
goto end;
}
flip->f_pos=(unsigned int) offset;
ret=flip->f_pos;
}else if(orig==1)//从当前位置开始偏移
{
if(flip->f_pos+offset>GLOBAL_MEM_SIZE){
ret=-EINVAL;
goto end;
}
flip->f_pos+=offset;
ret=flip->f_pos;
}else{
ret=-EINVAL;
}
end:
return ret;
}
/**
* @function: global_mem_ioctl
* @description:
* @input:
* @output:
* @return {*}
* @param {file*} flip
* @param {unsigned int} cmd
* @param {unsigned long} arg
*/
static long global_mem_ioctl(struct file* flip,unsigned int cmd,unsigned long arg)
{
global_mem_t *dev =(global_mem_t *) flip->private_data;
switch(cmd){
case GLOBAL_MEM_CLEAR:
memset(dev->mem,0,sizeof(dev->mem));
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations global_mem_fops={
.owner=THIS_MODULE,
.open=global_mem_open,
.release=global_mem_release,
.read=global_mem_read,
.write=global_mem_write,
.llseek=global_mem_llseek,
.unlocked_ioctl=global_mem_ioctl
};
/**
* @function: static void global_mem_setup_cdev(int minor_index)
* @description: 根据一个从设备号生成cdev
* @input:
* @output:
* @return {*}
*/
static void global_mem_setup_cdev(int minor_index)
{
int ret;
//得到设备号
int devno=MKDEV(global_mem.major,minor_index);
cdev_init(&global_mem.cdev,&global_mem_fops);
global_mem.cdev.owner=THIS_MODULE;
ret=cdev_add(&global_mem.cdev,devno,1);
if(ret)printk("CDEV ADD ERROR:%d\n",minor_index);
}
static int globalmem_probe(struct platform_device* dev)
{
int ret;
struct resource* globalmem_resource;
//从设备出获取信息
globalmem_resource= platform_get_resource(dev, IORESOURCE_MEM, 0);
if(globalmem_resource!=NULL)
printk("START:%d END :%d\n",globalmem_resource->start,globalmem_resource->end);
//如果定义了静态主设备号 就采用静态申请的方式
global_mem.major=GLOBAL_MEM_MAJOR;
//得到需要注册的设备号
global_mem.devid=MKDEV(global_mem.major,global_mem.minor);
if(global_mem.major!=0){//从devno 静态申请 一个设备号
ret=register_chrdev_region(global_mem.devid,1,"globalmem");
}else{//从devno 动态申请一个设备号
ret=alloc_chrdev_region(&global_mem.devid,0,1,"globalmem");
global_mem.major=MAJOR(global_mem.devid);
global_mem.minor=MINOR(global_mem.devid);
}
memset(global_mem.mem,0,sizeof(global_mem.mem));
if(GLOBAL_MEM_DEBUG){
printk("主设备号:%d\n",global_mem.major);
printk("从设备号:%d\n",global_mem.minor);
}
global_mem_setup_cdev(global_mem.minor);
//创建设备 创建类
global_mem.class = class_create(THIS_MODULE, GLOBAL_MEM_CLASS_NAME);
global_mem.device = device_create(global_mem.class, NULL, global_mem.devid, NULL, GLOBAL_MEM_NODE_NAME);
return 0;
}
static int globalmem_remove(struct platform_device* dev)
{
cdev_del(&global_mem.cdev);
unregister_chrdev_region(global_mem.devid,1);
//删除设备 删除类
device_destroy(global_mem.class, global_mem.devid);
class_destroy(global_mem.class);
return 0;
}
static struct platform_driver globalmem_driver={
.driver={
.name="global_mem",
},
.probe=globalmem_probe,
.remove=globalmem_remove,
};
/**
* @function: global_mem_init
* @description: 申请设备号 并且注册cdev
* @input: void
* @output:
* @return {*}
*/
static int __init global_mem_init(void)
{
return platform_driver_register(&globalmem_driver);;
}
module_init(global_mem_init);
static void __exit global_mem_exit(void)
{
platform_driver_unregister(&globalmem_driver);;
}
module_exit(global_mem_exit);
MODULE_AUTHOR("YURI");
MODULE_LICENSE("GPL");
/*
* @Copyright:
* @FileNames:
* @Description:
* @Author:
* @Date: 2022-08-01 14:22:40
* @Version: V1.0
* @LastEditTime: 2022-08-01 15:10:30
*/
#include <linux/module.h> //module_xxx
#include <linux/init.h> //__init
#include <linux/platform_device.h>
static struct resource globalmem_resources[]={
[0]={
.start=0,
.end=100,
.flags=IORESOURCE_MEM,
}
};
static void globalmem_device_release(struct device *dev)
{
printk("globalmem device released!\r\n");
}
static struct platform_device globalmem_device={
.name="global_mem",
.id=-1,
.num_resources = ARRAY_SIZE(globalmem_resources),
.resource=globalmem_resources,
.dev={
.release=&globalmem_device_release,
}
};
static int __init globalmem_device_init(void)
{
return platform_device_register(&globalmem_device);
}
static void __exit globalmem_device_exit(void)
{
platform_device_unregister(&globalmem_device);
}
module_init(globalmem_device_init);
module_exit(globalmem_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YURI");
带设备树的平台设备驱动代码
设备树
设备树代码位于 arch/arm/boot/dts
在dts 文件夹下的Makefile可以添加自己的设备树文件
语法
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。
1 / {
2 aliases {
3 can0 = &flexcan1;
4 };
5
6 cpus {
7 #address-cells = <1>;
8 #size-cells = <0>;
9
10 cpu0: cpu@0 {
11 compatible = "arm,cortex-a7";
12 device_type = "cpu";
13 reg = <0>;
14 };
15 };
16
17 intc: interrupt-controller@00a01000 {
18 compatible = "arm,cortex-a7-gic";
19 #interrupt-cells = <3>;
20 interrupt-controller;
21 reg = <0x00a01000 0x1000>,
22 <0x00a02000 0x100>;
23 };
24 }
第 2、6 和 17 行,aliases、cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:
node-name@unit-address
其中“node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是 UART1 外设。“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:
①、字符串
compatible = “arm,cortex-a7”;
上述代码设置 compatible 属性的值为字符串“arm,cortex-a7”。
②、32 位无符号整数
reg = <0>;
上述代码设置 reg 属性的值为 0,reg 的值也可以设置为一组值,比如:
reg = <0 0x123456 100>;
③、字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:
compatible = “fsl,imx6ull-gpmi-nand”, “fsl, imx6ul-gpmi-nand”;
标准属性
1、compatible 属性
compatible 属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible 属性的值是一个字符串列表,compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,compatible 属性的值格式如下所示:
“manufacturer,model”
其中 manufacturer 表示厂商,model 一般是模块对应的驱动名字。
一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
2、model 属性
model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,比如:
model = “wm8960-audio”;
3、status 属性
status 属性看名字就知道是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息
4、#address-cells 和#size-cells 属性
这两个属性的值都是无符号 32 位整形,#address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。
#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位),
#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。
5、reg 属性
reg 属性前面已经提到过了,reg 属性的值一般是(address,length)对。reg 属性一般用于描
述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息
6、ranges 属性
ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,ranges 是一个地址映射/转换表,ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
ranges = <0x0 0xe0000000 0x00100000>;
此属性值指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000
追加数据
1 &i2c1 {
2 /* 要追加或修改的内容 */
3 };
常用of 操作函数
设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。
OF 函数原型都定义在 include/linux/of.h
Linux 内核使用 device_node 结构体来描述一个节点
49 struct device_node {
50 const char *name; /* 节点名字 */
51 const char *type; /* 设备类型 */
52 phandle phandle;
53 const char *full_name; /* 节点全名 */
54 struct fwnode_handle fwnode;
55
56 struct property *properties; /* 属性 */
57 struct property *deadprops; /* removed 属性 */
58 struct device_node *parent; /* 父节点 */
59 struct device_node *child; /* 子节点 */
60 struct device_node *sibling;
61 struct kobject kobj;
62 unsigned long _flags;
63 void *data;
64 #if defined(CONFIG_SPARC)
65 const char *path_component_name;
66 unsigned int unique_id;
67 struct of_irq_controller *irq_trans;
68 #endif
69 };
寻找节点
1、of_find_node_by_name 函数
of_find_node_by_name 函数通过节点名字查找指定的节点,函数原型如下:
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
函数参数和返回值含义如下:
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值:找到的节点,如果为 NULL 表示查找失败。
2、of_find_node_by_type 函数
of_find_node_by_type 函数通过 device_type 属性查找指定的节点,函数原型如下:
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
函数参数和返回值含义如下:
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type:要查找的节点对应的 type 字符串,也就是 device_type 属性值。
返回值:找到的节点,如果为 NULL 表示查找失败。
3、of_find_compatible_node 函数
of_find_compatible_node 函数根据 device_type 和 compatible 这两个属性查找指定的节点,
函数原型如下:
struct device_node *of_find_compatible_node(struct device_node *from,const char *type,const char *compatible)
函数参数和返回值含义如下:
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type:要查找的节点对应的 type 字符串,也就是 device_type 属性值,可以为 NULL,表示
忽略掉 device_type 属性。
compatible:要查找的节点所对应的 compatible 属性列表。
返回值:找到的节点,如果为 NULL 表示查找失败
4、of_find_matching_node_and_match 函数
of_find_matching_node_and_match 函数通过 of_device_id 匹配表来查找指定的节点,函数原
型如下:
struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)
函数参数和返回值含义如下:
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
matches:of_device_id 匹配表,也就是在此匹配表里面查找节点。
match:找到的匹配的 of_device_id。
返回值:找到的节点,如果为 NULL 表示查找失败
5、of_find_node_by_path 函数
of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下:
inline struct device_node *of_find_node_by_path(const char *path)
函数参数和返回值含义如下:
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
返回值:找到的节点,如果为 NULL 表示查找失败
提取属性值
35 struct property {
36 char *name; /* 属性名字 */
37 int length; /* 属性长度 */
38 void *value; /* 属性值 */
39 struct property *next; /* 下一个属性 */
40 unsigned long _flags;
41 unsigned int unique_id;
42 struct bin_attribute attr;
43 };
1、of_find_property 函数
of_find_property 函数用于查找指定的属性,函数原型如下:
property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
函数参数和返回值含义如下:
np:设备节点。
name: 属性名字。
lenp:属性值的字节数
返回值:找到的属性。
2、of_property_count_elems_of_size 函数
of_property_count_elems_of_size 函数用于获取属性中元素的数量,比如 reg 属性值是一个
数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下:
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size)
3、of_property_read_u32_index 函数
of_property_read_u32_index 函数用于从属性中获取指定标号的 u32 类型数据值(无符号 32
位),比如某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值,此
函数原型如下:
int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index,
u32 *out_value)
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
index:要读取的值标号。
out_value:读取到的值
返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有
要读取的数据,-EOVERFLOW 表示属性值列表太小。
4、 of_property_read_u8_array 函数
of_property_read_u16_array 函数
of_property_read_u32_array 函数
of_property_read_u64_array 函数
这 4 个函数分别是读取属性中 u8、u16、u32 和 u64 类型的数组数据,比如大多数的 reg 属
性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。这四个函数的原型
如下:
int of_property_read_u8_array(const struct device_node *np,
const char *propname,
u8 *out_values,
size_t sz)
int of_property_read_u16_array(const struct device_node *np,
const char *propname,
u16 *out_values,
size_t sz)
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz)
int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,
5、of_property_read_u8 函数
of_property_read_u16 函数
of_property_read_u32 函数
of_property_read_u64 函数
有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性,分别用
于读取 u8、u16、u32 和 u64 类型属性值,函数原型如下:
int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
int of_property_read_u64(const struct device_node *np,
const char *propname,
u64 *out_value)
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值。
返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没
有要读取的数据,-EOVERFLOW 表示属性值列表太小。
6、of_property_read_string 函数
of_property_read_string 函数用于读取属性中字符串值,函数原型如下:
int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string)
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值:0,读取成功,负值,读取失败。
带设备树驱动编写
内核会将所有设备树中没有挂在总线上的节点添加成平台设备驱动,这样只需要编写驱动即可
添加自己的设备树文件,并在根下面添加
global_mem{
#address-cells=<1>;
#size-cells=<1>;
compatible="global_mem";
status="okay";
reg=<
0X020C406C 0X04
0X020E0068 0X04
0X020E02F4 0X04
0X0209C000 0X04
0X0209C004 0X04
>;
};
编写驱动文件 ,和一般平台驱动的不同之出在于一般平台驱动通过名字匹配设备
而设备树通过兼容性表匹配设备,当自己添加的globalmem_of_match 和设备树
compatible属性匹配上即可匹配成功。
/*
* @Copyright:
* @FileNames:
* @Description: 申请全局内存4096 并使用该内存进行 用户和内核之间的数据交换
* @Author:
* @Date: 2022-07-29 09:03:02
* @Version: V1.0
* @LastEditTime: 2022-08-01 17:02:24
*/
#include <linux/module.h>//MOUDLE_XXX moudle_xxx
#include <linux/fs.h> //file_operations register_chrdev_region alloc_chrdev_region
#include <linux/init.h> //__init __exit
#include <linux/cdev.h> //cdev
#include <linux/uaccess.h>//copy_to_user copy_from_user
#include <linux/device.h> // device class
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#define GLOBAL_MEM_SIZE 0X1000
#define GLOBAL_MEM_DEBUG 1
#define GLOBAL_MEM_CLASS_NAME "global_mem" //创建的类名称
#define GLOBAL_MEM_NODE_NAME "global_mem_0" //创建的节点名称
#define GLOBAL_MEM_MAJOR 250
//CMD
#define GLOBAL_MEM_CLEAR 0X01 //清理内存 命令
typedef struct {
//设备驱动变量
struct cdev cdev;
//设备号变量
dev_t devid;
int major;
int minor;
//设备节点相关变量
struct class *class;
struct device *device;
//用户变量
unsigned char mem[GLOBAL_MEM_SIZE];
//设备树变量
struct device_node *node;
}global_mem_t;
global_mem_t global_mem;
/**
* @function: global_mem_open
* @description: 打开设备 并将内存清零
* @input:
* @output:
* @return {*}
* @param {inode} *inode
* @param {file} *flip
*/
static int global_mem_open(struct inode *inode ,struct file *flip)
{
printk("OPEN GLOBAL MEM\r\n");
flip->private_data=(void *)(&global_mem);
return 0;
}
/**
* @function: global_mem_release
* @description: 释放设备节点
* @input:
* @output:
* @return {*}
* @param {inode} *inode
* @param {file} *flip
*/
static int global_mem_release(struct inode *inode ,struct file *flip)
{
printk("RELEASE GLOBAL MEM\r\n");
return 0;
}
/**
* @function: global_mem_read
* @description: 拷贝数据到用户区
* @input:
* @output:
* @return {*}
* @param {file*} flip 文件结构体
* @param {char __user} *buf 从用户区拷贝出来的数据
* @param {size_t} size 传入数据大小
* @param {loff_t} *ppos 当前数据位置
*/
static ssize_t global_mem_read(struct file* flip, char __user *buf,size_t size ,loff_t *ppos)
{
int ret=0;
printk("READ GLOBAL MEM\r\n");
global_mem_t* dev=(global_mem_t*) flip->private_data;
//当前文件指针所处于的位置
unsigned long p=*ppos;
//需要读出的个数
unsigned int count=(unsigned int)size;
//检查读取位置合法性
if(p>=GLOBAL_MEM_SIZE)return 0;
if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
//拷贝数据到用户区
ret=copy_to_user(buf,dev->mem+p,count);
if (ret<0){
ret=-EFAULT;
}else{
*ppos+=count;
ret=count;
if(GLOBAL_MEM_DEBUG)printk("READ %d BYTES FROM KERNEL At %d\n",count,p);
}
return ret;
}
/**
* @function: global_mem_write
* @description:
* @input:
* @output:
* @return {*}
* @param {file*} flip
* @param {char __user} *buf
* @param {size_t} size
* @param {loff_t} *ppos
*/
static ssize_t global_mem_write(struct file* flip, const char __user *buf,size_t size ,loff_t *ppos)
{
int ret=0;
printk("WRITE GLOBAL MEM\r\n");
global_mem_t* dev=(global_mem_t*) flip->private_data;
//当前文件指针所处于的位置
unsigned long p=*ppos;
//需要读出的个数
unsigned int count=(unsigned int)size;
//检查读取位置合法性
if(p>=GLOBAL_MEM_SIZE)return 0;
if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
ret=copy_from_user(dev->mem+p,buf,count);
if(ret<0)ret=-EFAULT;
else{
*ppos+=count;
ret=count;
if(GLOBAL_MEM_DEBUG)printk("WRITE %d BYTES TO KERNEL AT %d\n",(int)count,(int)p);
}
return ret;
}
static loff_t global_mem_llseek(struct file* flip ,loff_t offset,int orig)
{
loff_t ret=0;
if(orig==0)//从头开始偏移
{
if(offset<0){
ret=-EINVAL;
goto end;
}
if((unsigned int) offset>GLOBAL_MEM_SIZE){
ret=-EINVAL;
goto end;
}
flip->f_pos=(unsigned int) offset;
ret=flip->f_pos;
}else if(orig==1)//从当前位置开始偏移
{
if(flip->f_pos+offset>GLOBAL_MEM_SIZE){
ret=-EINVAL;
goto end;
}
flip->f_pos+=offset;
ret=flip->f_pos;
}else{
ret=-EINVAL;
}
end:
return ret;
}
/**
* @function: global_mem_ioctl
* @description:
* @input:
* @output:
* @return {*}
* @param {file*} flip
* @param {unsigned int} cmd
* @param {unsigned long} arg
*/
static long global_mem_ioctl(struct file* flip,unsigned int cmd,unsigned long arg)
{
global_mem_t *dev =(global_mem_t *) flip->private_data;
switch(cmd){
case GLOBAL_MEM_CLEAR:
memset(dev->mem,0,sizeof(dev->mem));
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations global_mem_fops={
.owner=THIS_MODULE,
.open=global_mem_open,
.release=global_mem_release,
.read=global_mem_read,
.write=global_mem_write,
.llseek=global_mem_llseek,
.unlocked_ioctl=global_mem_ioctl
};
/**
* @function: static void global_mem_setup_cdev(int minor_index)
* @description: 根据一个从设备号生成cdev
* @input:
* @output:
* @return {*}
*/
static void global_mem_setup_cdev(int minor_index)
{
int ret;
//得到设备号
int devno=MKDEV(global_mem.major,minor_index);
cdev_init(&global_mem.cdev,&global_mem_fops);
global_mem.cdev.owner=THIS_MODULE;
ret=cdev_add(&global_mem.cdev,devno,1);
if(ret)printk("CDEV ADD ERROR:%d\n",minor_index);
}
static int globalmem_probe(struct platform_device* dev)
{
int ret,i;
struct property *proper;
u32 regdata[10];
global_mem.node=of_find_node_by_path("/global_mem");
if(global_mem.node==NULL){
printk("DO NOT FIND DTS\n");
}
proper = of_find_property(global_mem.node, "compatible", NULL);
printk("compatible:%s\n", (char*)proper->value);
proper = of_find_property(global_mem.node, "status", NULL);
printk("status:%s\n", (char*)proper->value);
ret = of_property_read_u32_array(global_mem.node, "reg", regdata, 10);
for(i = 0; i < 10; i++)
printk("%#X ", regdata[i]);
printk("\n");
//如果定义了静态主设备号 就采用静态申请的方式
global_mem.major=GLOBAL_MEM_MAJOR;
//得到需要注册的设备号
global_mem.devid=MKDEV(global_mem.major,global_mem.minor);
if(global_mem.major!=0){//从devno 静态申请 一个设备号
ret=register_chrdev_region(global_mem.devid,1,"globalmem");
}else{//从devno 动态申请一个设备号
ret=alloc_chrdev_region(&global_mem.devid,0,1,"globalmem");
global_mem.major=MAJOR(global_mem.devid);
global_mem.minor=MINOR(global_mem.devid);
}
memset(global_mem.mem,0,sizeof(global_mem.mem));
if(GLOBAL_MEM_DEBUG){
printk("主设备号:%d\n",global_mem.major);
printk("从设备号:%d\n",global_mem.minor);
}
global_mem_setup_cdev(global_mem.minor);
//创建设备 创建类
global_mem.class = class_create(THIS_MODULE, GLOBAL_MEM_CLASS_NAME);
global_mem.device = device_create(global_mem.class, NULL, global_mem.devid, NULL, GLOBAL_MEM_NODE_NAME);
return 0;
}
static int globalmem_remove(struct platform_device* dev)
{
cdev_del(&global_mem.cdev);
unregister_chrdev_region(global_mem.devid,1);
//删除设备 删除类
device_destroy(global_mem.class, global_mem.devid);
class_destroy(global_mem.class);
return 0;
}
static const struct of_device_id globalmem_of_match[]={
{ .compatible = "global_mem" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, globalmem_of_match);
static struct platform_driver globalmem_driver={
.driver={
.name="global_mem",
.of_match_table = globalmem_of_match,
},
.probe=globalmem_probe,
.remove=globalmem_remove,
};
/**
* @function: global_mem_init
* @description: 申请设备号 并且注册cdev
* @input: void
* @output:
* @return {*}
*/
static int __init global_mem_init(void)
{
return platform_driver_register(&globalmem_driver);;
}
module_init(global_mem_init);
static void __exit global_mem_exit(void)
{
platform_driver_unregister(&globalmem_driver);;
}
module_exit(global_mem_exit);
MODULE_AUTHOR("YURI");
MODULE_LICENSE("GPL");