Linux驱动开发(速记版)--设备树插件

第六十八章 设备树插件介绍

Linux 4.4之后引入了动态设备树,其中的设备树插件(Device Tree Overlay)是一种扩展机制,允许在运行时动态添加、修改或删除设备节点和属性。

设备树插件机制通过DTS(设备树源文件)定义,提供了一种灵活配置硬件设备的方式,无需重新编译整个设备树,也无需重启系统即可进行硬件配置更改。

在 linux 源码中 linux_sdk/kernel/Documentation/filesystems/configfs 目录下的 configfs.txt。是设备树插件的帮助文档。

第六十九章 设备树插件语法和编译实验

69.1 设备树插件语法

设备树插件的语法格式基于设备树源文件的语法,但是有一些特定的语法和指令用于描述插件的行为。

我们新建 overlay.dts,

1 首先添加插件头部声明,它指定了插件的名称和版本等信息,并指定了要修改的设备树的路径,

cpp 复制代码
/dts-v1/;  
/plugin/;  
// 插件头部声明(示例,实际内容根据需求填写)  
/plugin/name: "my_overlay";  
/plugin/version: "1.0";  
/plugin/target-path: "/"; // 目标设备树路径

2 插件节点名称用于指定要操作的设备节点及其属性。

假设需要编写插件的设备树节点如下:

cpp 复制代码
rk_485_ctl:rk-485-ctl{
    compatible = "topeet,rs485_ctl";
    gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>;
    pinctrl-names = "default";
    pinctrl-0 = <&rk_485_gpio>;
}

设备树插件有以下四种写法。 了解即可。

cpp 复制代码
/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,输入以下命令:

cpp 复制代码
/home/topeet/Linux/linux_sdk/kernel/scripts/dtc/dtc
 -I dts          //输入文件的格式
 -O dtb          //输出文件的格式
 overlay.dts     //输入的源文件
 -o overlay.dtbo //输出文件的名称和路径

反编译设备树,输入以下命令:

cpp 复制代码
/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 查看当前内核支持的文件系统类型的列表。
cat /proc/filesystems 当前内核支持的文件系统类型列表

在Linux系统中,/proc/filesystems文件是一个虚拟文件,它提供了当前内核支持的文件系统类型的列表。这个文件是由内核动态生成的,并且只存在于内存中,不占用磁盘空间。

输出中的每一行都代表一个文件系统类型,前面的 nodev表示该文件系统类型不支持在磁盘上作为设备挂载(即它不是基于块设备的文件系统)。没有nodev前缀的文件系统类型则通常可以挂载在磁盘设备或其他类型的文件系统之上。

70.2 设备树插件的使用

在上一章节中,我们编写了 overlay.dts。

在 overlay.dts 中,rk-485-ctl 节点下添加新的节点overlay_node 节点,如下所示:

cpp 复制代码
/dts-v1/;
/plugin/;

&{/rk-485-ctl}{          
    overlay_node{
        status = "okay";
    };
};

使用 dtc 编译器编译得到 dtbo 文件,并将 dtbo 拷贝到开发板上。

cpp 复制代码
/home/topeet/Linux/linux_sdk/kernel/scripts/dtc/dtc 
-I dts 
-O dtb 
overlay.dts 
-o overlay.dtbo

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

在这个目录下使用以下命令创建一个内核对象,

cpp 复制代码
mkdir test

使用命令 cd test 进到 test 文件夹,在文件夹下创建 status和 dtbo两个文件

cpp 复制代码
cd test

使用 cat /overlay.dtbo > dtbo 将 插件.dtbo文件 写入 dtbo文件。
在状态文件status中写入 1,来使能插件。echo 1 > status。

此时查看 proc文件系统的devicetree可以看到加载的 插件节点。

cpp 复制代码
ls /proc/device-tree/rk-485-ctl/overlay_node/

/proc/device-tree/节点/插件节点

要删掉插件节点,直接把 /sys/kernel/config/device-tree/overlays/ 下的 test文件删掉就可。

要加载多个插件,多写几个文件夹,把 dtc编译后的插件内容用 cat xxx > bbb 写到 dtbo 文件就行。

第七十一章 虚拟文件系统 ConfigFS

虚拟文件系统 ConfigFS 是一个特殊的文件系统,旨在提供一种动态配置 Linux 内核和设备的机制。

71.1 常用的虚拟文件系统

在Linux内核中,虚拟文件系统为应用程序提供了统一的文件访问接口,简化了开发、维护,提高了可移植性和灵活性。以下是几个常用的虚拟文件系统及其功能简述:

Procfs : 主要用于进程、设备、驱动等系统信息,表示运行时状态。

Sysfs: 主要用于设备、驱动等内核对象的属性和状态

Configfs:主要用于动态管理内核对象。允许运行时添加、修改和删除内核对象
Procfs

功能:访问内核运行时状态,包括进程、设备、驱动程序等系统信息

路径:通常在 /proc 目录下。

bash 复制代码
# 访问CPU信息
/proc/cpuinfo 

Sysfs

功能:表示系统中的设备、驱动程序等内核对象,为这些对象的属性和状态提供统一的访问接口。

路径:通常在 /sys 目录下。

bash 复制代码
# 查看设备信息
/sys/class/tty/ttyS0/device/idVendor  

Configfs

功能:动态配置和管理内核对象,允许在运行时添加、修改和删除内核对象。

路径:通常在 /sys/kernel/config 目录下。

bash 复制代码
# 动态配置内核对象
/sys/kernel/config  

设备树插件选择 ConfigFS 原因在于,

configfs允许用户空间动态配置内核对象,无需改内核代码,适合设备树插件技术。

第七十二章 ConfigFS 核心数据结构

72.1 关键数据结构

ConfigFS核心数据结构:

configfs_subsystem:顶层结构,代表整个ConfigFS系统,含根配置项组和系统状态。

config_group:配置项组,可含多个相关配置项,成层次结构,含父子配置项指针。

config_item:基本配置项,表示内核对象,含类型、名称、属性和状态、父子关系指针。

这些结构形成树形,subsystem为根,group为组,item为项,通过指针连接成父子关系。
configfs核心数据结构

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 结构体:

cpp 复制代码
/*configfs子系统结构体*/
struct configfs_subsystem {
    struct config_group su_group;//配置项组结构体
    struct mutex su_mutex;       //互斥锁
};

config_group 结构体:

cpp 复制代码
/*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 结构体:

cpp 复制代码
/*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 字段表示根配置项组的基本配置项。

cpp 复制代码
/*创建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 这个子系统,
创建configfs_subsystem对象后sys/kernel/config目录下新建了 与[子系统名]同名的目录

接下来是设备树插件驱动代码中注册配置项组的部分,

cpp 复制代码
/*创建配置项组实例*/
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"的容器。
overlays配置项组注册成功,子系统目录下出现对应的配置项目录

72.3 属性和方法

我们要在容器下放目录或属性文件,所以我们看一下 config_item 结构体,

cpp 复制代码
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 结构体,

cpp 复制代码
/*配置项的属性和操作*/
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 结构体,

cpp 复制代码
/*配置项组下,配置项删除和链接的创建、删除的钩子函数*/
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 结构体,

cpp 复制代码
/*配置项与配置项组关联的相关钩子函数*/
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 结构体,

cpp 复制代码
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); 
};
cpp 复制代码
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 子系统,并将其注册到内核中。

cpp 复制代码
#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的配置过程,有着较多的钩子函数需要配置。

cpp 复制代码
#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 结构体中定义的回调函数指针,用于在配置组被删除时执行相关的清理操作。

cpp 复制代码
// 定义一个名为"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 实验

cpp 复制代码
// 使用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 结构体,

cpp 复制代码
/*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); 
};
cpp 复制代码
// 定义一个结构体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结构体实例。

cpp 复制代码
/*生成只读属性 名为[_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。
注册 attribute实验,生成属性文件

可以使用 以下命令对属性文件进行操作:

cpp 复制代码
cat read        //读
 
echo 1 > write  //写入

分别对属性文件 read 和 属性文件 write 进行读写操作后,

会分别执行 myread_show 和 mywrite_store 函数。

第七十七章 实现多级目录

完成了 configfs_group_operations 结构体下的,.make_group函数,

.make_group函数优先级比 .make_item优先级高,因此编译好驱动装载好,使用mkdir创建目录的时候会调用 .make_group视为新建了 配置组。

cpp 复制代码
// 创建配置组函数
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文件系统。
内核源码menuconfig页面,选择支持 configfis文件系统

将编译之后的内核镜像烧写到开发板上,接着使用 mount 命令检查 configfs 虚拟文件系是否挂载成功。

如果系统没有自动挂载 configfs 虚拟文件系统,需要输入以下命令挂载:

cpp 复制代码
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。

cpp 复制代码
// 初始化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;  
}  

make_item和drop_item的实现

在配置组下,命令行输入 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 可以绑定。

cpp 复制代码
// 定义一个二进制属性,用于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 用于定义二进制属性。

cpp 复制代码
/*定义一个二进制属性*/
#define CONFIGFS_BIN_ATTR(_name, 
                          _mode, 
                          _read,
                         _write,
                          _size) //二进制属性可存储的最大字节数

CONFIGFS_ATTR 宏用于定义普通属性。

cpp 复制代码
/*定义一个普通属性*/
#define CONFIGFS_ATTR(_name, //名称
                      _mode, //权限
                      _show, //读操作函数
                     _store) //写操作函数

当 status 写入 1 的时,会执行 dtbocfg_overlay_item_create 函数。在这个函数中又去执行了 of_overlay_fdt_apply 函数。

完善dtbocfg_overlay_item_create 函数,可以执行我们需要的逻辑。

cpp 复制代码
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 函数会先创建一个改变集。然后根据这个改变集去进行修改。

改变集是为了方便修改和复原设备树而设计的。

设备树是静态的,编译加载后不易直接更改。改变集记录了设备树的修改操作,如增删改节点,允许运行时动态调整,无需改动源文件。它提供了高层次抽象,简化描述变化,且可保存、传递、应用于不同设备树。同时,改变集支持撤销修改,恢复原始状态。

相关推荐
007php0075 小时前
linux服务器上CentOS的yum和Ubuntu包管理工具apt区别与使用实战
linux·运维·服务器·ubuntu·centos·php·ai编程
djykkkkkk5 小时前
ubuntu编译遇到的问题
linux·运维·ubuntu
qq_429856575 小时前
linux 查看服务是否开机自启动
linux·运维·服务器
7yewh7 小时前
Linux驱动开发 IIC I2C驱动 编写APP访问EEPROM AT24C02
linux·arm开发·驱动开发·嵌入式硬件·嵌入式
dessler7 小时前
Docker-Dockerfile讲解(三)
linux·运维·docker
KevinRay_7 小时前
命令行之巅:Linux Shell编程的至高艺术(中)
linux·运维·服务器·重定向·shell编程
程序员JerrySUN7 小时前
Yocto 项目 - 共享状态缓存 (Shared State Cache) 机制
linux·嵌入式硬件·物联网·缓存·系统架构
林农9 小时前
C05S16-MySQL高可用
linux·mysql·云计算
码中小白鼠9 小时前
Ubuntu系统部署Mysql8.0后设置不区分大小写
linux·mysql·ubuntu·adb
gz94569 小时前
Virtualbox安装ubuntu20虚拟机无法打开终端
java·linux·开发语言