设备树插件定义和作用
设备树插件(Device Tree Overlay) 是一种用于设备树(Device Tree)的扩展机制。
设备树插件允许在运行时动态修改设备树结构的内容,以便添加、修改或删除设备节点和属性。嵌入式驱动开发者将不用再重新启动系统的情况下对硬件进行配置修改。
在早期的时候,每个嵌入式驱动开发者都因为引入了设备树而感到兴奋和激动。因为这使得开发者不用再写两套代码,且代码移植性也变得非常灵活。
然而设备树还有个不太方便的地方,就是每次配置完设备树,还得重新编译后烧录到板卡,重启系统才能完成配置。这使得驱动开发者非常头疼,有可能因为更改个GPIO属性都得重新过一遍烧录过程。所以在linux4.4以后引入了动态设备树。设备树插件是实现的方式。
一、预备知识
如果不想关注原理,只是简单使用。可以跳过本节。
设备树插件是基于configfs实现的。所以本节介绍configfs的相关知识。
1. 什么是configfs
在kernel目录下的Documentation/filesystems/configfs有官方说明文档configfs.txt 。其中是这样说明的:
- configfs是与sysfs功能相反的基于ram的文件系统。sysfs是基于文件系统查看内核对象,configfs是基于文件系统管理内核对象或配置项
- 对于sysfs,一个对象是在内核中创建和删除的。内核控制着sysfs表示的生命周期,和sysfs只是一个窗口而已。而configfs配置项表示的生命周期完全由用户空间驱动,内核模块支持这样项目的必须作出响应。
- 它可以被编译成模块或者编入内核
2. 如何编写
- 认识相关结构体
/************configfs子系统结构体********/
struct configfs_subsystem {
struct config_group su_group; //config_group结构体变量
struct mutex su_mutex;
}
/************注册和注销API********/
int configfs_register_subsystem(struct configfs_subsystem *subsys);
void configfs_unregister_subsystem(struct configfs_subsystem *subsys);
/************config_group结构体********/
struct config_group {
struct config_item cg_item;
struct list_head cg_children;
struct configfs_subsystem *cg_subsys;
struct list_head default_groups;
struct list_head group_entry;
};
/************初始化API********/
void config_group_init(struct config_group *group);
void config_group_init_type_name(struct config_group *group,const char *name,struct config_item_type *type);
/************config_item结构体********/
struct config_item {
char *ci_name;
char ci_namebuf[UOBJ_NAME_LEN];
struct kref ci_kref;
struct list_head ci_entry;
struct config_item *ci_parent;
struct config_group *ci_group;
struct config_item_type *ci_type;
struct dentry *ci_dentry;
}
/************初始化API********/
void config_item_init(struct config_item *);
void config_item_init_type_name(struct config_item *, const char *name,struct config_item_type *type);
/************config_item属性以及动作相关类********/
struct config_item_type {
struct module *ct_owner;
struct configfs_item_operations *ct_item_ops;
struct configfs_group_operations *ct_group_ops;
struct configfs_attribute **ct_attrs;
struct configfs_bin_attribute **ct_bin_attrs;
};
/************config_item动作结构体********/
struct configfs_item_operations {
void (*release)(struct config_item *);
int (*allow_link)(struct config_item *src,struct config_item *target);
void (*drop_link)(struct config_item *src,struct config_item *target);
}
/************config_item属性结构体********/
struct configfs_attribute {
char *ca_name;
struct module *ca_owner;
umode_t ca_mode;
ssize_t (*show)(struct config_item *, char *);
ssize_t (*store)(struct config_item *, const char *, size_t);
};
struct configfs_bin_attribute {
struct configfs_attribute cb_attr;
void *cb_private;
size_t cb_max_size;
};
结构体相关性
- 编写-创建一个子系统并创建group
/**第二步:实现配置项相关类**/
static struct config_item_type myconfigfs_item={
.ct_owner=THIS_MODULE,
.ct_item_ops=NULL,
.ct_attrs=NULL,
.ct_bin_attrs=NULL
};
/**第一步:创建configfs子系统结构体变量**/
static struct configfs_subsystem myconfigfs={
.su_group={
.cg_item={
.ci_namebuf="myconfig",
.ci_type=&myconfigfs_item
},
},
};
static int myconfigfs_init(void){
printk("Hello World!\n");
/**第三步:初始化子系统group**/
config_group_init(&myconfigfs.su_group); //注册子系统前需要初始化group
/**第四步:注册子系统**/
configfs_register_subsystem(&myconfigfs); ///注册子系统
return 0;
}
static void myconfigfs_exit(void){
/**第五步:添加卸载模块时注销子系统**/
configfs_unregister_subsystem(&myconfigfs);
printk("bye bye\n");
}
通过编译为KO文件并加载到开发板上,可以在/sys/kernel/config下查看到一个名为myconfig的group
- 编写-在子系统group中创建group
/**第二步:实现myconfig_group的config_item_type**/
static struct config_item_type myconfigfs_item={
.ct_owner=THIS_MODULE,
.ct_group_ops=NULL
};
static struct configfs_subsystem myconfigfs={
.su_group={
.cg_item={
.ci_namebuf="myconfig",
.ci_type=&myconfigfs_item
},
},
};
static struct config_item_type mygroup_item={
.ct_owner=THIS_MODULE,
.ct_item_ops=NULL,
.ct_attrs=NULL,
.ct_bin_attrs=NULL
};
/**第一步:创建config_group结构体变量**/
static struct config_group myconfig_group;
static int myconfigfs_init(void){
printk("Hello World!\n");
config_group_init(&myconfigfs.su_group);
configfs_register_subsystem(&myconfigfs);
/**第二步:初始化config_group**/
config_group_init_type_name(&myconfig_group,"mygroup",&mygroup_item);
/**第四步:在子系统group下注册一个group**/
configfs_register_group(&myconfigfs.su_group, &myconfig_group);
return 0;
}
static void myconfigfs_exit(void){
configfs_unregister_subsystem(&myconfigfs);
printk("bye bye\n");
}
通过编译为KO文件并加载到开发板上,可以在/sys/kernel/config/myconfig下查看到一个名为mygroup的group
- 编写-在group中创建item
struct myitem{
struct config_item item;
};
/**第六步:实现rmdir相关函数**/
void mymake_item_release(struct config_item *item)
{
struct myitem *myitem=container_of(item, struct myitem, item);
kfree(myitem);
}
/**第五步:实现rmdir**/
struct configfs_item_operations mymake_item_ops={
.release=&mymake_item_release
};
/**第四步:mkdir的相关项目的动作实现**/
static struct config_item_type mymake_item={
.ct_owner=THIS_MODULE,
.ct_item_ops=&mymake_item_ops
};
/**第三步:mkdir的相关函数**/
static struct config_item *myconfig_group_make_item(struct config_group *group, const char *name){
struct myitem *myitem;
myitem=kzalloc(sizeof(*myitem), GFP_KERNEL);
config_item_init_type_name(&myitem->item,name, &mymake_item);//Initialze required item to created
return &myitem->item;
}
/**第二步:实现相关的动作--mkdir**/
struct configfs_group_operations myconfig_group_ops={
.make_item=myconfig_group_make_item,//achieve a function for make item
};
/**第一步:在子系统group下注册的group中实现相关动作**/
struct config_item_type myconfig_group_item={
.ct_owner=THIS_MODULE,
.ct_group_ops=&myconfig_group_ops
};
static struct config_group myconfig_group;
static struct config_item_type myconfigfs_item={
.ct_owner=THIS_MODULE
};
static struct configfs_subsystem myconfigfs={
.su_group={
.cg_item={
.ci_namebuf="my_configfs",
.ci_type=&myconfigfs_item
}
}
};
static int myconfigfs_init(void){
printk("Hello World!\n");
config_group_init(&myconfigfs.su_group);
configfs_register_subsystem(&myconfigfs);
config_group_init_type_name(&myconfig_group, "myconfig_group",&myconfig_group_item);
configfs_register_group(&myconfigfs.su_group, &myconfig_group);
return 0;
}
static void myconfigfs_exit(void){
configfs_unregister_subsystem(&myconfigfs);
printk("bye bye\n");
}
通过编译为KO文件并加载到开发板上,可以在/sys/kernel/config/myconfig/mygroup下使用mkdir创建group,使用rmdir可以删除相关group
- 编写-实现读写属性
/**第三步:实现读写函数**/
ssize_t myread_show(struct config_item *item, char *page)
{
struct myitem *myitem=container_of(item, struct myitem, item);
memcpy(page, myitem->addr, myitem->size);
printk("%s\n",__FUNCTION__);
return myitem->size;
}
ssize_t mywrite_store(struct config_item *item, const char *page, size_t size)
{
struct myitem *myitem=container_of(item, struct myitem, item);
myitem->addr=kmemdup(page, size, GFP_KERNEL);
myitem->size=size;
printk("%s\n",__FUNCTION__);
return myitem->size;
}
/**第二步:通过宏定义实现属性的读写**/
CONFIGFS_ATTR_RO(my, read);
CONFIGFS_ATTR_WO(my, write);
static struct configfs_attribute *myattrib[]={
&myattr_read,
&myattr_write,
NULL
};
struct configfs_group_operations myconfig_group_ops={
.make_item=myconfig_group_make_item,//achieve a function for make item
};
/**第一步:在config_item_type中添加属性**/
struct config_item_type myconfig_group_item={
.ct_owner=THIS_MODULE,
.ct_group_ops=&myconfig_group_ops,
.ct_attrs=myattrib
};
通过编译为KO文件并加载到开发板上,可以在/sys/kernel/config/myconfig/mygroup下使用mkdir创建group,在此group下将会有write和read两个属性。可以通过echo对write写相关参数,通过cat read查看参数值
二、内核配置
若实现设备树插件,还需要对内核进行相关配置
以上操作后,编译烧录到开发板。将在/sys/kernel下看到config目录,说明自动挂载上了。
如果不能自动挂载,则使用
mount -t configfs none /sys/kernel/config
接着配置内核支持设备树插件
三、移植驱动
关于设备树插件的相关驱动,我是通过网上下载得到的,就直接编译移植了(可以通过insmod加载KO方式也可以直接编译进内核)
相关文件放入如下网盘:
链接: https://pan.baidu.com/s/1uuwqCwlZNkSLorEWTmkFrQ 提取码: 5dbv
移植过程为一般驱动编译加载一样,这里不赘述。移植完成后将在sys/kernel/config下查看到一个名为device-tree的文件夹
四、设备树插件语法
1. 插件头部声明
/dts-v1/;
/plugin/;
2. 插件节点名称用于定义要添加、修改或删除的设备节点以及属性
有如下三种表示形式(以rk3568的485节点为例):
/dts-v1/;
/plugin/;
//方法一:使用&后{}中为节点绝对路径
&{/rk-485-ctl}{
overlay_node{
status="okay"
};
};
//方法二:使用&后带节点别名
&rk-485-ctl{
overlay_node{
status="okay"
};
};
//方法三:为方法一编译为dtbo后反编译得到
/{
frament@1{
target-path="/rk-485-ctl";
__overlay__{
overlay_node{
status="okay"
};
};
};
};
//方法四:为方法二编译为dtbo后反编译得到
/{
frament@1{
target=<&rk-485-ctl>;
__overlay__{
overlay_node{
status="okay"
};
};
};
};
五、编译与使用
1. 编译
编译的方式和编译设备树是一样的
sdk/kernel/scripts/dtc/dtc -I dts -O dtb overlay.dts -o overlay.dtbo
反编译为
sdk/kernel/scripts/dtc/dtc -I dtb -O dts overlay.dtbo -o overlay.dts
2. 使用
通过第三节加载相关驱动后,进入设备树插件相关configfs中
cd /sys/kernel/config/device-tree/overlays
创建一个内核对象
mkdir test
使用命令将dtbo写入到内核对象
cat /overlay.dtbo > /test/dtbo
使能dtbo
echo 1>/test/status
通过以下方式将能看到加载的节点
ls /proc/device-tree/rk-485-ctl/overlay_node/