Linux驱动开发(速记版)--设备树插件
第六十八章 设备树插件介绍
Linux 4.4之后引入了动态设备树,其中的设备树插件(Device Tree Overlay)是一种扩展机制,允许在运行时动态添加、修改或删除设备节点和属性。
设备树插件机制通过DTS(设备树源文件)定义,提供了一种灵活配置硬件设备的方式,无需重新编译整个设备树,也无需重启系统即可进行硬件配置更改。
在 linux 源码中 linux_sdk/kernel/Documentation/filesystems/configfs 目录下的 configfs.txt。是设备树插件的帮助文档。
第六十九章 设备树插件语法和编译实验
69.1 设备树插件语法
设备树插件的语法格式基于设备树源文件的语法,但是有一些特定的语法和指令用于描述插件的行为。
我们新建 overlay.dts,
1 首先添加插件头部声明,它指定了插件的名称和版本等信息,并指定了要修改的设备树的路径,
/dts-v1/;
/plugin/;
// 插件头部声明(示例,实际内容根据需求填写)
/plugin/name: "my_overlay";
/plugin/version: "1.0";
/plugin/target-path: "/"; // 目标设备树路径
2 插件节点名称用于指定要操作的设备节点及其属性。
假设需要编写插件的设备树节点如下:
rk_485_ctl:rk-485-ctl{compatible = "topeet,rs485_ctl";gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>;pinctrl-names = "default";pinctrl-0 = <&rk_485_gpio>;
}
设备树插件有以下四种写法。 了解即可。
/dts-v1/;
/plugin/;&{/rk-485-ctl}{ 或者 &rk_485_ctl{overlay_node{status = "okay";};
};/{fragment@0{target-path = "rk-485-cl"; 或者 target= <rk-485-cl>;__overlay__{overlay_node{status = "okay";}}}
}
69.2 设备树插件编译
使用方法一,然后编译设备树插件 overlay.dts,输入以下命令:
/home/topeet/Linux/linux_sdk/kernel/scripts/dtc/dtc-I dts //输入文件的格式-O dtb //输出文件的格式overlay.dts //输入的源文件-o overlay.dtbo //输出文件的名称和路径
反编译设备树,输入以下命令:
/home/topeet/Linux/linux_sdk/kernel/scripts/dtc/dtc -I dtb -O dts overlay.dtbo -o 1.dts
第七十章 设备树插件使用
70.1 准备实验环境
要求内核镜像支持设备树插件,一般要求内核版本4.4以上。
使用“insmod xxx.ko”命令加载设备树插件驱动。
使用 cat /proc/filesystems 查看当前内核支持的文件系统类型的列表。
在Linux系统中,/proc/filesystems文件是一个虚拟文件,它提供了当前内核支持的文件系统类型的列表。这个文件是由内核动态生成的,并且只存在于内存中,不占用磁盘空间。
输出中的每一行都代表一个文件系统类型,前面的 nodev表示该文件系统类型不支持在磁盘上作为设备挂载(即它不是基于块设备的文件系统)。没有nodev前缀的文件系统类型则通常可以挂载在磁盘设备或其他类型的文件系统之上。
70.2 设备树插件的使用
在上一章节中,我们编写了 overlay.dts。
在 overlay.dts 中,rk-485-ctl 节点下添加新的节点overlay_node 节点,如下所示:
/dts-v1/;
/plugin/;&{/rk-485-ctl}{ overlay_node{status = "okay";};
};
使用 dtc 编译器编译得到 dtbo 文件,并将 dtbo 拷贝到开发板上。
/home/topeet/Linux/linux_sdk/kernel/scripts/dtc/dtc
-I dts
-O dtb
overlay.dts
-o overlay.dtbo

我们进到系统 /sys/kernel/config/device-tree/overlays/(这个目录需要加载设备树插件才会生成)目录下。

在这个目录下使用以下命令创建一个内核对象,
mkdir test
使用命令 cd test 进到 test 文件夹,在文件夹下创建 status和 dtbo两个文件
cd test

使用 cat /overlay.dtbo > dtbo 将 插件.dtbo文件 写入 dtbo文件。
在状态文件status中写入 1,来使能插件。echo 1 > status。
此时查看 proc文件系统的devicetree可以看到加载的 插件节点。
ls /proc/device-tree/rk-485-ctl/overlay_node/
要删掉插件节点,直接把 /sys/kernel/config/device-tree/overlays/ 下的 test文件删掉就可。
要加载多个插件,多写几个文件夹,把 dtc编译后的插件内容用 cat xxx > bbb 写到 dtbo 文件就行。
第七十一章 虚拟文件系统 ConfigFS
虚拟文件系统 ConfigFS 是一个特殊的文件系统,旨在提供一种动态配置 Linux 内核和设备的机制。
71.1 常用的虚拟文件系统
在Linux内核中,虚拟文件系统为应用程序提供了统一的文件访问接口,简化了开发、维护,提高了可移植性和灵活性。以下是几个常用的虚拟文件系统及其功能简述:
Procfs: 主要用于进程、设备、驱动等系统信息,表示运行时状态。
Sysfs: 主要用于设备、驱动等内核对象的属性和状态。
Configfs:主要用于动态管理内核对象。允许运行时添加、修改和删除内核对象
Procfs:
功能:访问内核运行时状态,包括进程、设备、驱动程序等系统信息。
路径:通常在 /proc 目录下。
# 访问CPU信息
/proc/cpuinfo
Sysfs:
功能:表示系统中的设备、驱动程序等内核对象,为这些对象的属性和状态提供统一的访问接口。
路径:通常在 /sys 目录下。
# 查看设备信息
/sys/class/tty/ttyS0/device/idVendor
Configfs:
功能:动态配置和管理内核对象,允许在运行时添加、修改和删除内核对象。
路径:通常在 /sys/kernel/config 目录下。
# 动态配置内核对象
/sys/kernel/config
设备树插件选择 ConfigFS 原因在于,
configfs允许用户空间动态配置内核对象,无需改内核代码,适合设备树插件技术。

第七十二章 ConfigFS 核心数据结构
72.1 关键数据结构
ConfigFS核心数据结构:
configfs_subsystem:顶层结构,代表整个ConfigFS系统,含根配置项组和系统状态。
config_group:配置项组,可含多个相关配置项,成层次结构,含父子配置项指针。
config_item:基本配置项,表示内核对象,含类型、名称、属性和状态、父子关系指针。
这些结构形成树形,subsystem为根,group为组,item为项,通过指针连接成父子关系。
72.2 子系统、配置组和 配置项
configfs_subsystem 结构体:成员有config_group 结构体: 和互斥锁,
config_group 结构体:成员有config_item结构体,和多个链表头,
config_item结构体,成员有 config_item_type结构体和引用计数等。
config_item_type结构体有
configfs_item_operations配置项操作集、
configfs_group_operations配置组操作集。
configfs_attribute属性文件操作集。
config_item_operations操作集有release方法。
config_group_operations操作集make_item、drop_item、make_group等方法。
configfs_attribute属性文件操作集有 myread_show和 mywrite_store方法,在 只读属性文件、只写属性文件分别被 读出、写入时执行。
configfs_subsystem 结构体:
/*configfs子系统结构体*/
struct configfs_subsystem {struct config_group su_group;//配置项组结构体struct mutex su_mutex; //互斥锁
};
config_group 结构体:
/*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; //双向链表头,链接配置组所属子系统的条目
};
config_item 结构体:
/*config_item配置项*/
struct config_item {char *ci_name; //配置项名称指针char ci_namebuf[CONFIGFS_ITEM_NAME_LEN]; //配置项名称缓冲区.一个配置项就是一个目录struct kref ci_kref; //配置项引用计数 struct list_head ci_entry; //配置项的链表条目struct config_item *ci_parent; //父配置项struct config_group *ci_group; //配置项组const struct config_item_type *ci_type; //目录下属性文件和属性操作struct dentry *ci_dentry; //指向与配置项关联的目录项(dentry)的指针。
};//每个配置项都对应一个文件系统目录项,
//config_item_type用于描述配置项类型和操作,包括用于读写目录项属性的回调函数//在ConfigFS中,每个配置项都对应一个文件系统目录项,
//该目录项用于与用户空间进行交互
//(如通过mount命令挂载ConfigFS后,可以通过文件操作来访问这些配置项)。
接下来我们分析一下设备树插件驱动代码,
设备树插件驱动代码定义了ConfigFS子系统和配置项组。
dtbocfg_root_subsys是子系统实例,其根配置项组由 config_group表示,命名为"device-tree",并指定了自定义类型 dtbocfg_root_type。子系统使用互斥锁保护操作。
首先,在这里,该结构体的.cg_item 字段表示根配置项组的基本配置项。
/*创建configfs子系统实例的定义和初始化*/
static struct configfs_subsystem dtbocfg_root_subsys = { //dtbocfg_root_subsys.su_group 是一个 config_group 结构体,它表示子系统的根配置项组。//.cg_item 字段表示根配置项组的基本配置项.su_group.cg_item.ci_namebuf = "device-tree", /*根配置项名称*///注册子文件系统时,会在sys/kernel/configs目录下,用根配置项名称创建子文件系统//配置项的类型=一个自定义config_item_type.su_group.cg_item.ci_type = &dtbocfg_root_type,//初始化互斥锁.su_mutex = __MUTEX_INITIALIZER(dtbocfg_root_subsys.su_mutex),
};
注意,子文件系统有 配置项组成员,配置项组成员有根配置项,注册子文件系统时,用的是根配置项的名字。
注册 配置项组的时候,用的是注册函数给的名字参数
通过这段代码,创建了一个名为"device-tree"的子系统,它的根配置项组为空。
可在该子系统下添加更多配置项和配置项组,用于动态配置和管理设备树相关的内核对象。
Linux 系统下创建了 device-tree 这个子系统,
接下来是设备树插件驱动代码中注册配置项组的部分,
/*创建配置项组实例*/
static struct config_group dtbocfg_overlay_group = { .cg_item.ci_type = &dtbocfg_overlays_type,
}; /**/
static int dtbocfg_module_init(void) { int retval; /*初始化Configfs子系统的根配置项组*/config_group_init(&dtbocfg_root_subsys.su_group); /*初始化Confifs子系统的名为 overlays的配置项组*/ config_group_init_type_name(&dtbocfg_overlay_group,"overlays", &dtbocfg_overlays_type);/*注册子系统*/retval = configfs_register_subsystem(&dtbocfg_root_subsys); if (retval) goto register_subsystem_failed; /*注册配置项组*/retval = configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group); if (retval) goto register_group_failed; pr_info("OK\n"); return 0; register_group_failed: /*注销子系统*/configfs_unregister_subsystem(&dtbocfg_root_subsys);
register_subsystem_failed: return retval;
}
初始化函数 dtbocfg_module_init()执行以下步骤:
初始化子系统的根配置项组和名为"overlays"的配置项组,指定类型dtbocfg_overlays_type。
configfs_register_subsystem()注册子系统。
将"overlays"配置项组添加到子系统的根配置项组下。
configfs_register_group()在子系统中注册配置项组。
如注册失败,注销子系统并返回错误码;成功则打印"OK"。
结果是在/sys/kernel/config下创建了名为"device-tree"的子系统,并在其下创建了名为"overlays"的容器。
72.3 属性和方法
我们要在容器下放目录或属性文件,所以我们看一下 config_item 结构体,
struct config_item {char *ci_name;char ci_namebuf[CONFIGFS_ITEM_NAME_LEN]; //目录的名字struct kref ci_kref;struct list_head ci_entry;struct config_item *ci_parent;struct config_group *ci_group;const struct config_item_type *ci_type; //目录下属性文件和属性操作struct dentry *ci_dentry;
};
config_item 结构体中包含了 config_item_type 结构体,
/*配置项的属性和操作*/
struct config_item_type { struct module *ct_owner; //所属模块struct configfs_item_operations *ct_item_ops; //item(目录)的操作方法struct configfs_group_operations *ct_group_ops; //group(容器)的操作方法struct configfs_attribute **ct_attrs; //属性文件的操作方法struct configfs_bin_attribute **ct_bin_attrs; //bin 属性文件的操作方法
};
config_item_type 结构体中包含了 struct configfs_item_operations 结构体,
/*配置项组下,配置项删除和链接的创建、删除的钩子函数*/
struct configfs_item_operations {//删除 item 方法,在 group 下面使用 rmdir 命令会调用这个方法void (*release)(struct config_item *);//参数是指向被删除的配置项的指针/*在尝试创建一个从 src 到 target 的链接之前,这个函数会被调用。在 ConfigFS 中,配置项可以通过创建链接(link)来组织成树状结构或形成关联。*/int (*allow_link)(struct config_item *src, struct config_item *target);/*当从 src 到 target 的链接被删除时,这个函数会被调用。*/void (*drop_link)(struct config_item *src, struct config_item *target);
};
config_item_type 结构体中包含了 struct configfs_group_operations 结构体,
/*配置项与配置项组关联的相关钩子函数*/
struct configfs_group_operations {//当在配置组下使用 mkdir 命令创建一个新的子配置项时,这个函数会被调用struct config_item *(*make_item)(struct config_group *group, const char *name);/*其实就是目录下新建文件的时候,该函数调用*///当在配置组下使用 mkdir 命令创建一个新的子配置组时,这个函数会被调用。struct config_group *(*make_group)(struct config_group *group, const char *name);/*其实就是目录下新建文件的时候,该函数调用.相较于make_item优先级更高*///在配置项被完全设置或修改后,这个函数会被调用以确认或提交这些更改。int (*commit_item)(struct config_item *item);//配置项与其父配置组之间的关联被断开时(例如,当配置项被删除时),这个函数会被调用。void (*disconnect_notify)(struct config_group *group, struct config_item *item);//当配置项从配置组中被删除时,这个函数会被调用。void (*drop_item)(struct config_group *group, struct config_item *item);
};
config_item_type 结构体中包含了 struct configfs_attribute 结构体,
struct configfs_attribute {const 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; /* 二进制数据的最大大小 */ ssize_t (*read)(struct config_item *, void *, size_t); /* 读取回调函数 */ ssize_t (*write)(struct config_item *, const void *, size_t); /* 写入回调函数 */
};
第七十三章 注册 configfs 子系统实验
我们编写驱动代码创建一个名为“myconfigfs”的 configfs 子系统,并将其注册到内核中。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>//定义名为"myconfig_item_type"的配置项类型结构体
static const struct config_item_type myconfig_item_type ={.ct_owner = THIS_MODULE, .ct_item_ops = NULL, .ct_group_ops = NULL, .ct_attrs = NULL, };//定义一个 configfs_subsystem 结构体实例"myconfigfs_subsystem"
static struct configfs_subsystem myconfigfs_subsystem ={.su_group = {.cg_item = {.ci_namebuf = "myconfigfs", //配置项的名称.ci_type = &myconfig_item_type, //配置项的操作},},
};//模块的初始化函数
static int myconfigfs_init(void)
{//初始化配置组config_group_init(&myconfigfs_subsystem.su_group);//注册子系统configfs_register_subsystem(&myconfigfs_subsystem);return 0;
}// 模块退出函数
static void myconfigfs_exit(void)
{/*注销子系统*/configfs_unregister_subsystem(&myconfigfs_subsystem);
}
module_init(myconfigfs_init); // 指定模块的初始化函数
module_exit(myconfigfs_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("topeet"); // 模块的作者
驱动加载之后,进入/sys/kernel/config 目录下,可以看到注册生成的 myconfigfs 子系统,
第七十四章 注册 configs 配置项实验
由于 注册 config_group配置项组的过程和 注册 configfs子系统的过程相似,所以略过,
而注册配置项的过程包含了注册配置项组的过程,且涉及config_item_type的配置过程,有着较多的钩子函数需要配置。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
// 定义一个名为"mygroup"的 config_group 结构体
static struct config_group mygroup;
// 自定义的配置项结构体
struct myitem
{struct config_item item;
};// 配置项释放函数
void myitem_release(struct config_item *item)
{struct myitem *myitem = container_of(item, struct myitem, item);kfree(myitem);printk("%s\n", __func__);
}// 配置项操作结构体
struct configfs_item_operations myitem_ops = {.release = myitem_release,
};// 配置项类型结构体
static struct config_item_type mygroup_item_type = {.ct_owner = THIS_MODULE, .ct_item_ops = &myitem_ops,
};// 新建配置项函数
struct config_item *mygroup_make_item(struct config_group *group, const char *name)
{struct myitem *myconfig_item;printk("%s\n", __func__);myconfig_item = kzalloc(sizeof(*myconfig_item), GFP_KERNEL);//创建一个配置项,绑定 名称 和 属性及操作config_item_init_type_name(&myconfig_item->item, name, &mygroup_item_type);return &myconfig_item->item;
}// 配置组操作结构体
struct configfs_group_operations mygroup_ops = {/*绑定配置项的新建函数*/.make_item = mygroup_make_item,
};//config_item_type 结构体,用于描述配置项类型(但其实是传给配置项组的成员)。
static const struct config_item_type mygroup_config_item_type = {.ct_owner = THIS_MODULE, .ct_group_ops = &mygroup_ops,
};// 定义名为"myconfig_item_type"的配置项类型结构体
static const struct config_item_type myconfig_item_type = {.ct_owner = THIS_MODULE, .ct_group_ops = NULL, //省略了配置项与配置项关联的相关钩子函数
};// 定义一个 configfs_subsystem 结构体实例"myconfigfs_subsystem"
static struct configfs_subsystem myconfigfs_subsystem = {//填充配置项组成员.su_group = {//填充配置项成员.cg_item = {.ci_namebuf = "myconfigfs", //填充配置项名称.ci_type = &myconfig_item_type, //填充配置项操作}, },
};// 模块的初始化函数
static int myconfig_group_init(void)
{// 初始化配置组config_group_init(&myconfigfs_subsystem.su_group);// 注册子系统configfs_register_subsystem(&myconfigfs_subsystem);// 初始化配置组"mygroup" config_group_init_type_name(&mygroup, //配置项组实例"mygroup", //配置项组名字&mygroup_config_item_type); //配置项组操作// 在子系统中注册配置组"mygroup" configfs_register_group(&myconfigfs_subsystem.su_group, &mygroup);return 0;
}// 模块退出函数
static void myconfig_group_exit(void)
{// 注销子系统configfs_unregister_subsystem(&myconfigfs_subsystem);
}
module_init(myconfig_group_init); // 指定模块的初始化函数
module_exit(myconfig_group_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可
加载模块后,进入 sys/kernel/config 目录,发现注册的文件系统目录已经生成,配置组目录也已经生成,
然后输入“mkdir test”命令创建 config_item,如下图所示,创建成功之后,打印
“mygroup_make_item”,说明驱动程序中 mygroup_make_item 函数成功执行。
第七十五章 drop 和 release 函数
.release 是在 struct config_item_type 结构体中定义的回调函数指针,用于在配置项被删除时执行资源释放操作。
每个配置项实例都有一个与之关联的引用计数。
这个引用计数用于跟踪有多少实体(如内核模块、进程或其他内核组件)正在使用该配置项。当你创建一个新的配置项实例时,通常会初始化其引用计数为 1,表示该配置项已经被创建者所引用。随着配置项被不同的实体所使用,其引用计数会增加;相应地,当这些实体不再需要该配置项时,它们会通过调用 config_item_put 来减少其引用计数。当引用计数减少到 0就会调用.release函数执行清理工作。
.drop_item 是在 struct configfs_group_operations 结构体中定义的回调函数指针,用于在配置组被删除时执行相关的清理操作。
// 定义一个名为"mygroup"的 config_group 结构体
static struct config_group mygroup;// 自定义的配置项结构体
struct myitem
{struct config_item item;
};// 配置项释放函数,配置项的引用计数为0时执行
void myitem_release(struct config_item *item)
{struct myitem *myitem = container_of(item, struct myitem, item);kfree(myitem);printk("%s\n", __func__);
}/* 配置项操作结构体,填充配置项操作的release函数 */
struct configfs_item_operations myitem_ops = {.release = myitem_release,
};// 配置项类型结构体
static struct config_item_type mygroup_item_type = {.ct_owner = THIS_MODULE, .ct_item_ops = &myitem_ops, //填充配置项操作
};// 创建配置项函数
struct config_item *mygroup_make_item(struct config_group *group, const char *name)
{struct myitem *myconfig_item;printk("%s\n", __func__);myconfig_item = kzalloc(sizeof(*myconfig_item), GFP_KERNEL);config_item_init_type_name(&myconfig_item->item, name, &mygroup_item_type);return &myconfig_item->item;
}// 删除配置项函数
void mygroup_delete_item(struct config_group *group, struct config_item *item)
{struct myitem *myitem = container_of(item, struct myitem, item);config_item_put(&myitem->item); //减少传入 config_item 实例的引用计数printk("%s\n", __func__);
}// 配置组操作结构体
struct configfs_group_operations mygroup_ops = {.make_item = mygroup_make_item, //配置组创建配置项的钩子函数.drop_item = mygroup_delete_item,//配置组删除配置项的钩子函数
};// config_item_type 配置项类型结构体,用于描述配置项类型。用于将配置组注册进子系统时初始化
static const struct config_item_type mygroup_config_item_type = {.ct_owner = THIS_MODULE, .ct_group_ops = &mygroup_ops, //填充配置组类型的钩子函数
};// 定义名为"myconfig_item_type"的配置项类型结构体,用于定义子系统时初始化
static const struct config_item_type myconfig_item_type = {.ct_owner = THIS_MODULE, .ct_group_ops = NULL,
};// 定义一个 configfs_subsystem 结构体实例
static struct configfs_subsystem myconfigfs_subsystem = {.su_group = {.cg_item = {.ci_namebuf = "myconfigfs", .ci_type = &myconfig_item_type, }, },
};// 模块的初始化函数
static int myconfig_group_init(void)
{// 初始化配置组config_group_init(&myconfigfs_subsystem.su_group);// 注册子系统configfs_register_subsystem(&myconfigfs_subsystem);// 初始化配置组"mygroup" config_group_init_type_name(&mygroup, "mygroup", &mygroup_config_item_type);// 在子系统中注册配置组"mygroup" configfs_register_group(&myconfigfs_subsystem.su_group, &mygroup);return 0;
}//...省略模块出口函数
在配置组目录下,使用 mkdir创建配置项,会执行 make_item函数,
使用 rmdir删除配置项,会先执行 drop_item函数,然后执行release函数。
第七十六章 注册 attribute 实验
// 使用CONFIGFS_ATTR_RO和CONFIGFS_ATTR_WO宏定义只读和只写属性
CONFIGFS_ATTR_RO(my, read); // 定义一个名为read的只读属性,关联到myread_show函数
/*生成一个 myattr_read只读属性*/ CONFIGFS_ATTR_WO(my, write); // 定义一个名为write的只写属性,关联到mywrite_store函数
/*生成一个 myattr_write只写属性*//*属性文件和关联的读写方法是通过属性名和函数名关联的*/// 定义属性数组,以NULL结尾,用于注册到config_item_type中
struct configfs_attribute *my_attrs[] = { &myattr_read, &myattr_write, NULL,
};
config_item_type 结构体中包含了 struct configfs_attribute 结构体,
/*config_item_type结构体下,包含了configfs_attribute结构体*/
struct configfs_attribute {const 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);
};
// 定义一个结构体myitem,它包含一个config_item成员和其他自定义成员
struct myitem { struct config_item item; // 内嵌的config_item,用于与配置文件系统交互 int size; // 用于存储数据的大小 void *addr; // 指向数据的指针
}; // 释放myitem资源的函数,当config_item被释放时调用
void myitem_release(struct config_item *item) { struct myitem *myitem = container_of(item, struct myitem, item); // 将config_item指针转换回myitem指针 kfree(myitem); // 释放myitem占用的内存
} // 读取myitem数据的回调函数,用于configfs的只读属性
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); // 将数据复制到page中 return myitem->size; // 返回复制的字节数
} // 写入数据到myitem的回调函数,用于configfs的只写属性
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; // 更新数据大小 return size; // 返回写入的字节数
} // 使用CONFIGFS_ATTR_RO和CONFIGFS_ATTR_WO宏定义只读和只写属性
CONFIGFS_ATTR_RO(my, read); // 定义一个名为myattr_read的只读属性,关联到myread_show函数
CONFIGFS_ATTR_WO(my, write); // 定义一个名为myattr_write的只写属性,关联到mywrite_store函数 // 定义属性数组,以NULL结尾,用于注册到config_item_type中
struct configfs_attribute *my_attrs[] = { &myattr_read, &myattr_write, NULL,
}; // 定义myitem的操作集,包括释放函数
struct configfs_item_operations myitem_ops = { .release = myitem_release,
}; // 定义mygroup的config_item_type,包括所有者、操作集和属性数组
struct config_item_type mygroup_item_type = { .ct_owner = THIS_MODULE, // 模块所有者 .ct_item_ops = &myitem_ops, // 操作集 .ct_attrs = my_attrs, // 属性数组
}; // 初始化函数,用于注册mygroup到配置文件系统
static int myconfig_group_init(void) { // 假设myconfigfs_subsystem已定义并初始化 configfs_register_subsystem(&myconfigfs_subsystem); // 注册子系统 config_group_init_type_name(&mygroup, "mygroup", &mygroup_item_type); // 初始化组并设置名称和类型 configfs_register_group(&myconfigfs_subsystem.su_group, &mygroup); // 注册组到子系统中 return 0;
} // 清理函数,用于注销mygroup和子系统
static void myconfig_group_exit(void) { configfs_unregister_subsystem(&myconfigfs_subsystem); // 注销子系统
}
在使用CONFIGFS_ATTR_RO和CONFIGFS_ATTR_WO宏时,
它们会生成名为 myattr_read和 myattr_write的 configfs_attribute结构体实例。
/*生成只读属性 名为[_name]attr_read的configfs_attribute实例*/
#define CONFIGFS_ATTR_RO(_name, _show) struct configfs_attribute configfs_attr_##_name = { .ca_owner = THIS_MODULE,.ca_name = __stringify(_name), .ca_mode = S_IRUGO,.show = _show,} /*生成只写属性 名为[_name]attr_write的configfs_attribute实例*/
#define CONFIGFS_ATTR_WO(_name, _store) struct configfs_attribute configfs_attr_##_name = { .ca_owner = THIS_MODULE, .ca_name = __stringify(_name), .ca_mode = S_IWUSR,.store = _store,}
模块编译、加载后,在名为 myconfigfs的子系统目录下,有 配置组 mygroup目录,使用mkdir创建配置项 test,可以看到配置项目录 test下有生成的 属性文件 read 和 write。
可以使用 以下命令对属性文件进行操作:
cat read //读echo 1 > write //写入
分别对属性文件 read 和 属性文件 write 进行读写操作后,
会分别执行 myread_show 和 mywrite_store 函数。
第七十七章 实现多级目录
完成了 configfs_group_operations 结构体下的,.make_group函数,
.make_group函数优先级比 .make_item优先级高,因此编译好驱动装载好,使用mkdir创建目录的时候会调用 .make_group视为新建了 配置组。
// 创建配置组函数
struct config_group *mygroup_make_group(struct config_group *group, const char *name)
{struct mygroup *mygroup;printk("%s\n", __func__);mygroup = kzalloc(sizeof(*mygroup), GFP_KERNEL);config_group_init_type_name(&mygroup->group, name, &mygroup_type);return &mygroup->group;
};// 配置组操作结构体
struct configfs_group_operations mygroup_ops = {.make_item = mygroup_make_item, //新建配置项函数.drop_item = mygroup_delete_item, //删除配置项函数.make_group = mygroup_make_group, //新建配置组函数
};
第七十八章 移植设备树插件驱动
移植设备树插件到 iTOP-RK3568 开发板上。移植设备树插件主要包括以下几个步骤
1 配置内核支持挂载 configfs 虚拟文件系统。
2 配置内核支持设备树插件(kernel 4.4及以上)
3 移植设备树插件驱动
78.1 挂载 configfs 虚拟文件系统
内核源码的menuconfig页面,选择支持configfs文件系统。
将编译之后的内核镜像烧写到开发板上,接着使用 mount 命令检查 configfs 虚拟文件系是否挂载成功。

如果系统没有自动挂载 configfs 虚拟文件系统,需要输入以下命令挂载:
mount -t //挂载的类型
configfs //configfs虚拟文件系统
none //不需要具体的设备与挂载点关联
/sys/kernel/config //挂载的文件目录,挂载好后可通过该目录与configfs交互,来动态配置内核对象
78.2 配置内核支持设备树插件
源码,menuconfig页面,勾选支持 device tree overlays。
内核编译成功后,就可以开始移植设备树。
78.3 移植驱动
我们没有必要重复造轮子,github 上有大神编写好的设备树插件驱动。
假设 dtbocfg.c 是设备树插件驱动源码,我们将此驱动编译成驱动模块或编译进内核即可。
好了,设备树插件驱动移植完毕,设备树插件的使用参考前面的章节即可。
第七十九章 设备树插件驱动分析
dtbocfg.c 为设备树插件驱动文件。
在 dtbocfg_overlays_type 中实现了 ct_group_ops 下的 make_item 和 drop_item。
// 初始化dtbocfg模块的函数
static int __init dtbocfg_module_init(void)
{ int retval = 0; // 打印初始化开始信息,注意这里应该使用__func__来获取当前函数名 pr_info("%s: Initialization started\n", __func__); // 初始化configfs的group config_group_init(&dtbocfg_root_subsys.su_group); config_group_init_type_name(&dtbocfg_overlay_group, "overlays", &dtbocfg_overlays_type); // 注册子系统到configfs retval = configfs_register_subsystem(&dtbocfg_root_subsys); if (retval != 0) { pr_err("%s: Couldn't register subsystem\n", __func__); } // 注册group到已注册的子系统 retval = configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group); if (retval != 0) { pr_err("%s: Couldn't register group\n", __func__); } // 打印初始化成功信息 pr_info("%s: Initialization OK\n", __func__); return 0;
}
在配置组下,命令行输入 mkdir时,如果没有实现 make_group,则会去执行 make_item。
make_item负责开辟空间,并调用初始化函数去初始化 config_item。
config_item有 config_item_type成员,
config_item_type成员 有
config_item_operations结构体、config_group_operations结构体。
attribute结构体。bin_attribute结构体。
attribute结构体有 read_show,和 write_store函数可以绑定。
bin_attribute结构体有read 和write 可以绑定。
// 定义一个二进制属性,用于dtbo(设备树二进制对象)的覆盖
CONFIGFS_BIN_ATTR(dtbocfg_overlay_item, dtbo, NULL, 1024*1024); // 1MiB足够大 // 定义一个普通属性,用于显示或设置状态
CONFIGFS_ATTR(dtbocfg_overlay_item, status);
//定义一个status属性,该属性属于 dtbocfg_overlay_item配置项/* dtbocfg_overlay_item_attr_dtbo 和 dtbocfg_overlay_item_attr_status 是宏展开后生成的变量名。*/// 定义一个属性结构体数组,包含所有与dtbocfg_overlay_item相关的普通属性
static struct configfs_attribute *dtbocfg_overlay_attrs[] = { &dtbocfg_overlay_item_attr_status, //将属性,通过属性数组,填充给 配置项NULL, // 数组结束标志
}; // 定义一个结构体数组,包含所有与dtbocfg_overlay_item相关的二进制属性
static struct configfs_bin_attribute *dtbocfg_overlay_bin_attrs[] = { &dtbocfg_overlay_item_attr_dtbo, //将属性,通过属性数组,填充给 配置项NULL, // 数组结束标志
};
CONFIGFS_BIN_ATTR 用于定义二进制属性。
/*定义一个二进制属性*/
#define CONFIGFS_BIN_ATTR(_name, _mode, _read,_write,_size) //二进制属性可存储的最大字节数
CONFIGFS_ATTR 宏用于定义普通属性。
/*定义一个普通属性*/
#define CONFIGFS_ATTR(_name, //名称_mode, //权限_show, //读操作函数_store) //写操作函数
当 status 写入 1 的时,会执行 dtbocfg_overlay_item_create 函数。在这个函数中又去执行了 of_overlay_fdt_apply 函数。
完善dtbocfg_overlay_item_create 函数,可以执行我们需要的逻辑。
static ssize_t dtbocfg_overlay_item_status_store(struct config_item *item, const char *buf, size_t count)
{ struct dtbocfg_overlay_item *overlay = container_of(item, struct dtbocfg_overlay_item, item); ssize_t status; unsigned long value; // 尝试将字符串缓冲区转换为无符号长整型值 if (0 != (status = kstrtoul(buf, 10, &value))) { goto failed; // 转换失败,跳转到错误处理 } // 根据值决定是释放还是创建覆盖项 if (value == 0) { if (overlay->id >= 0) { // 如果id有效,则释放覆盖项 dtbocfg_overlay_item_release(overlay); } } else { if (overlay->id < 0) { dtbocfg_overlay_item_create(overlay); } } return count; // 假设成功写入全部数据
failed: return -EPERM; // 权限错误或转换失败
}
设备树插件(dtbo)里面的节点也要被转换成 device_node,有的 device_node 也要被转换成 platform_device。
不过在进行转换之前,of_overlay_fdt_apply 函数会先创建一个改变集。然后根据这个改变集去进行修改。
改变集是为了方便修改和复原设备树而设计的。
设备树是静态的,编译加载后不易直接更改。改变集记录了设备树的修改操作,如增删改节点,允许运行时动态调整,无需改动源文件。它提供了高层次抽象,简化描述变化,且可保存、传递、应用于不同设备树。同时,改变集支持撤销修改,恢复原始状态。
相关文章:
Linux驱动开发(速记版)--设备树插件
第六十八章 设备树插件介绍 Linux 4.4之后引入了动态设备树,其中的设备树插件(Device Tree Overlay)是一种扩展机制,允许在运行时动态添加、修改或删除设备节点和属性。 设备树插件机制通过DTS(设备树源文件࿰…...
代码报错后如何定位问题
文章目录 一、查看终端报错Exception二、百度三、问 一、查看终端报错Exception 代码报错时,终端一般都会有xxxException异常提示,或者exception、error…等字样提示,就顺着这些关键字提醒找到异常即可。 二、百度 不知道这个英文的异常是…...
Python数据可视化--Matplotlib--入门
我生性自由散漫,不喜欢拘束。我谁也不爱,谁也不恨。我没有欺骗这个,追求那个;没有把这个取笑,那个玩弄。我有自己的消遣。 -- 塞万提斯 《堂吉诃德》 Matplotlib介绍 1. Matplotlib 是 Python 中常用的 2D 绘图库&a…...
美国食品等级FDA认证测试介绍
美国FDA认证概览 美国食品和药物管理局(FDA)是负责监管食品、药品、医疗设备和化妆品等的联邦机构,以确保这些产品对公众健康和安全的影响。FDA认证在美国属于强制性认证,对产品的安全性和质量有着严格的要求。通过FDA认证&#…...
Vue2如何在网页实现文字的逐个显现
目录 Blue留言: 效果图: 实现思路: 代码: 1、空字符串与需渲染的字符串的定义 2、vue的插值表达式 3、函数 4、mounted()函数调用 结语: Blue留言: 在国庆前夕,突发奇想,我想…...
mybatisplus的查询,分页查询,自定义多表查询,修改的几种写法
使用mybatisplus的Db类简化写法 使用静态调用的方式,执行CRUD方法,避免Spring环境下Service循环注入、简洁代码,提升效率需要项目中已注入对应实体的BaseMapper完整使用方式见官方测试用例:官方测试用例地址对于参数为Wrapper的&…...
括号匹配判断
本题实现求表达式中括号是否匹配。只需判断表达式中括号(本题中只会出现三种括号,分别是小括号,中括号和大括号)是否匹配,表达式中可以有其他值也可没有。 函数接口定义: int match (char *exp); 其中 …...
数据结构(栈和队列的实现)
1. 栈(Stack) 1.1 栈的概念与结构 栈是一种特殊的线性表,其只允许固定的一段插入和删除操作;进行数据插入和删除的一段叫做栈顶,另一端叫栈底;栈中的元素符合后进先出LIFO(Last In First Out&…...
Python批量处理客户明细表格数据,挖掘更大价值
批量处理 .xls 数据并进行归类分析以挖掘内在价值,通常涉及以下步骤: 读取数据:使用 pandas 库读取 .xls 文件。数据清洗:处理缺失值、异常值、重复值等。数据转换:对数据进行必要的转换,如日期格式统一、…...
NAND Flash虚拟层索引表机制
NAND Flash虚拟层的索引表用于建立逻辑块与数据块、日志块之间的关系,用于NAND Flash虚拟层在运行过程中的读写、擦除操作;由于NAND Flash虚拟层采用集中索引的方式,因此在NAND Flash虚拟层启动时需要在NAND Flash存放索引表区域扫描并确定NAND Flash中存…...
Spring Boot框架:新闻推荐系统开发新趋势
3系统分析 3.1可行性分析 通过对本新闻推荐系统实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本新闻推荐系统采用JAVA作为开发语言,Spring Boot框…...
RK3568平台(opencv篇)opencv处理图像
一.颜色转换 cv2.cvtColor()函数功能: 将一幅图像从一个色彩空间转换到另一个色彩空间。 函数原型: cv2.cvtColor(src,code,dst=None,dstCn=None) 参数定义: src:要转换的源文件 code,转换的色彩空间,在 opencv 中有超过 150 种颜色空间转换方法,但是经常用的只有 B…...
【移动端】Viewport 视口
1. 什么是 Viewport(视口)? Viewport(视口)是指浏览器中用户可见的那部分网页内容的区域,简单来说,它是用户当前看到的网页的“窗口”区域。在不同的设备上,Viewport 的大小会有所不…...
PWM 模式
一、介绍 PWM(脉宽调制,Pulse-width modulation)是一种通过调节脉冲信号的宽度来控制电能输出的方法。PWM是一种方波信号,通常在电子和电气工程中用于调节功率输送,控制电机速度,调节LED亮度,以…...
模拟算法(3)_Z字形变换
个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 模拟算法(3)_Z字形变换 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌 目录 1. 题目链…...
Go语言实现长连接并发框架 - 任务执行流路由模块
文章目录 前言接口结构体接口实现项目地址最后 前言 你好,我是醉墨居士,上篇博客中我们实现了任务执行流上下文部分,接下来我们实现一下任务执行流的路由模块,基于该模块可以实现将消息转发到相应注册的任务执行流中进行处理 接…...
Windows 编译 FFmpeg 源码详细教程
FFmpeg FFmpeg 是一个开源的多媒体框架,它包括了一整套工具和库,可以用来处理(转码、转换、录制、流式传输等)音频和视频。FFmpeg 支持广泛的音视频格式,并且可以在多种操作系统上运行,包括 Windows、Linux 和 macOS。 FFmpeg 的主要组件包括: ffmpeg:这是一个命令行工…...
JavaCV 实现视频链接截取封面工具
引入必要依赖 <!--JavaCV--> <dependency><groupId>org.bytedeco</groupId><artifactId>javacv-platform</artifactId><version>1.5.7</version> </dependency> <dependency><groupId>cn.hutool</groupI…...
初识Linux · 进程替换
目录 前言: 1 直接看代码和现象 2 解释原理 3 将代码改成多进程版本 4 认识所有函数并使用 前言: 由前面的章节学习,我们已经了解了进程状态,进程终止以及进程等待,今天,我们学习进程替换。进程替换我…...
项目-坦克大战学习-人机ai
我们要知道,人机的移动和玩家的移动方式是一样的,所以我们可以将玩家移动代码以及检测碰撞代码移过来,唯一不同的就是人机检测到碰撞后会改变方向继续移动而不是停止 所以我们需要一个随机数使人机检测到碰撞后随机修改方向 Random rd new …...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...
CppCon 2015 学习:Reactive Stream Processing in Industrial IoT using DDS and Rx
“Reactive Stream Processing in Industrial IoT using DDS and Rx” 是指在工业物联网(IIoT)场景中,结合 DDS(Data Distribution Service) 和 Rx(Reactive Extensions) 技术,实现 …...
简约商务通用宣传年终总结12套PPT模版分享
IOS风格企业宣传PPT模版,年终工作总结PPT模版,简约精致扁平化商务通用动画PPT模版,素雅商务PPT模版 简约商务通用宣传年终总结12套PPT模版分享:商务通用年终总结类PPT模版https://pan.quark.cn/s/ece1e252d7df...
