目录
🍅点击这里查看所有博文
随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。
想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。
很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。
同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。
既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。
本系列博客所述资料均来自互联网,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。
前置知识
介绍
configfs是 Linux 内核中的一种虚拟文件系统,用于动态配置内核对象的属性和行为。它提供了一种用户空间和内核空间之间交互的接口,允许用户空间通过创建和管理 configfs 目录来配置内核对象。
configfs 的设计目标是提供一种灵活、可扩展的配置机制,使用户能够在运行时动态地配置内核对象,而无需重新编译和加载内核模块。它可以与其他子系统(如网络子系统、文件系统等)进行集成,以实现更复杂的配置和管理功能。
支持列表
configfs可以与其他子系统(如网络子系统、文件系统、USB等)进行集成,以实现更复杂的配置和管理功能。通过与其他子系统的集成,configfs 可以提供更丰富的配置选项和更灵活的配置方式。被支持的设备都会以普通文件/文件夹的形式创建在/sys/kernel/config中。这点和sysfs很相似。
root@sdxlemur:/sys/kernel/config# ls stp-policy usb_gadget
读写操作
configfs支持属性和操作。属性可以是简单的键值对形式,用于表示内核对象的状态和配置信息。操作则是一组函数指针,用于执行内核对象的特定操作。用户空间可以通过读取和写入 configfs 文件来访问和修改内核对象的属性,还可以通过调用 configfs 文件来执行内核对象的操作。
以usb_gadget为例,示例中通过往UDC中写入none控制usb断开,往UDC中写入数据a600000.dwc3控制usb的连接。写入的数据是通过cat指令事先查询出来的。
root@sdxlemur:/sys/kernel/config/usb_gadget/g1# cat ./UDC a600000.dwc3 root@sdxlemur:/sys/kernel/config/usb_gadget/g1# echo none > ./UDC root@sdxlemur:/sys/kernel/config/usb_gadget/g1# dmesg -c [ 289.275213] msm-dwc3 a600000.ssusb: ep [gsi-epin1,29] already configured as msm endpoint [ 289.275252] msm-dwc3 a600000.ssusb: ep [gsi-epout1,30] already configured as msm endpoint [ 289.381579] android_work: sent uevent USB_STATE=DISCONNECTED [ 289.400634] systemd-udevd[1511]: android0: Process '/etc/udev/scripts/automtp.sh' failed with exit code 1. [ 289.480029] msm-dwc3 a600000.ssusb: Could not get usb psy [ 289.575063] msm-dwc3 a600000.ssusb: Could not get usb psy [ 289.575114] android_work: did not send uevent (0 0 00000000) root@sdxlemur:/sys/kernel/config/usb_gadget/g1# echo a600000.dwc3 > ./UDC root@sdxlemur:/sys/kernel/config/usb_gadget/g1# dmesg -c [ 297.674319] android_work: sent uevent USB_STATE=CONNECTED [ 297.697035] systemd-udevd[1511]: android0: Process '/etc/udev/scripts/automtp.sh' failed with exit code 1. [ 297.706357] configfs-gadget gadget: high-speed config #1: c [ 297.712878] msm-dwc3 a600000.ssusb: Could not get usb psy [ 297.712973] android_work: sent uevent USB_STATE=CONFIGURED [ 297.742445] ipa_usb_notify_cb: Set net_ready_trigger [ 297.751615] systemd-udevd[1514]: android0: Process '/etc/udev/scripts/automtp.sh' failed with exit code 1.
configfs除了可以修改配置中的数据,还可以通过创建和删除 configfs 目录来实现对内核对象的动态配置。同样以usb_gadget为例,该目录下的每一个文件夹都代表了USB的一个配置。我们可以通过mkdir创建一个新的配置g2。创建一个配置目录后,我们可以发现,创建的配置目录中已经存在了很多的配置项。配置目录在创建时实际是按照特定的模板去进行创建的,模板由驱动设置。
root@sdxlemur:/sys/kernel/config/usb_gadget# ls g1 root@sdxlemur:/sys/kernel/config/usb_gadget# mkdir g2 root@sdxlemur:/sys/kernel/config/usb_gadget# ls -l -rw-rw-r-- 1 root system 4096 Jan 1 02:55 UDC -rw-r--r-- 1 root root 4096 Jan 1 05:27 bDeviceClass -rw-r--r-- 1 root root 4096 Jan 1 05:27 bDeviceProtocol -rw-r--r-- 1 root root 4096 Jan 1 05:27 bDeviceSubClass -rw-r--r-- 1 root root 4096 Jan 1 05:27 bMaxPacketSize0 -rw-r--r-- 1 root root 4096 Jan 1 05:27 bcdDevice -rw-r--r-- 1 root root 4096 Jan 1 05:27 bcdUSB drwxr-xr-x 3 root root 0 Jan 1 02:51 configs -rw-r--r-- 1 root root 4096 Jan 1 05:27 driver_match_existing_only drwxr-xr-x 25 root root 0 Jan 1 02:51 functions -rw-r--r-- 1 root root 4096 Jan 1 02:51 idProduct -rw-r--r-- 1 root root 4096 Jan 1 02:51 idVendor -rw-r--r-- 1 root root 4096 Jan 1 05:27 max_speed drwxr-xr-x 2 root root 0 Jan 1 02:51 os_desc drwxr-xr-x 3 root root 0 Jan 1 02:51 strings root@sdxlemur:/sys/kernel/config/usb_gadget# rmdir ./g2 root@sdxlemur:/sys/kernel/config/usb_gadget# ls g1
需要注意的是,configfs 的具体实现和用法可能会因内核版本和具体的配置需求而有所不同。在使用时,可以参考相关文档和示例来了解具体的使用方法。
添加配置
针对上面示例usb_gadget模版中的文件,其对应的注册方法位于/kernel/msm-5.4/drivers/usb/gadget/configfs.c文件中。如果需要新增一个配置文件,也是按照该方法添加一个声明。最后在编写处理函数即可。
CONFIGFS_ATTR(gadget_dev_desc_, bDeviceClass); CONFIGFS_ATTR(gadget_dev_desc_, bDeviceSubClass); CONFIGFS_ATTR(gadget_dev_desc_, bDeviceProtocol); CONFIGFS_ATTR(gadget_dev_desc_, bMaxPacketSize0); CONFIGFS_ATTR(gadget_dev_desc_, idVendor); CONFIGFS_ATTR(gadget_dev_desc_, idProduct); CONFIGFS_ATTR(gadget_dev_desc_, bcdDevice); CONFIGFS_ATTR(gadget_dev_desc_, bcdUSB); CONFIGFS_ATTR(gadget_dev_desc_, UDC); CONFIGFS_ATTR(gadget_dev_desc_, max_speed); CONFIGFS_ATTR(gadget_, driver_match_existing_only);
关于处理函数,我们选择max_speed作为示例,在用户空间中通过configfs读取max_speed文件就等同于调用gadget_dev_desc_max_speed_show函数,修改max_speed文件等同于调用gadget_dev_desc_max_speed_store函数。
static ssize_t gadget_dev_desc_max_speed_show(struct config_item *item,char *page) { enum usb_device_speed speed = to_gadget_info(item)->composite.max_speed; return scnprintf(page, PAGE_SIZE, "%s\n", usb_speed_string(speed)); } static ssize_t gadget_dev_desc_max_speed_store(struct config_item *item,const char *page, size_t len) { struct gadget_info *gi = to_gadget_info(item); mutex_lock(&gi->lock); /* Prevent changing of max_speed after the driver is binded */ if (gi->composite.gadget_driver.udc_name) goto err; if (strncmp(page, "super-speed-plus", 16) == 0) gi->composite.max_speed = USB_SPEED_SUPER_PLUS; else if (strncmp(page, "super-speed", 11) == 0) gi->composite.max_speed = USB_SPEED_SUPER; else if (strncmp(page, "high-speed", 10) == 0) gi->composite.max_speed = USB_SPEED_HIGH; else if (strncmp(page, "full-speed", 10) == 0) gi->composite.max_speed = USB_SPEED_FULL; else if (strncmp(page, "low-speed", 9) == 0) gi->composite.max_speed = USB_SPEED_LOW; else goto err; gi->composite.gadget_driver.max_speed = gi->composite.max_speed; mutex_unlock(&gi->lock); return len; err: mutex_unlock(&gi->lock); return -EINVAL; }
文件夹则是通过config_group_init_type_name函数进行创建。
config_group_init_type_name(&gi->group, name, &gadget_root_type); config_group_init_type_name(&gi->group, name, &gadget_root_type); config_group_init_type_name(&gi->functions_group, "functions",&functions_type); configfs_add_default_group(&gi->functions_group, &gi->group); config_group_init_type_name(&gi->configs_group, "configs",&config_desc_type); configfs_add_default_group(&gi->configs_group, &gi->group); config_group_init_type_name(&gi->strings_group, "strings",&gadget_strings_strings_type); configfs_add_default_group(&gi->strings_group, &gi->group); config_group_init_type_name(&gi->os_desc_group, "os_desc",&os_desc_type); configfs_add_default_group(&gi->os_desc_group, &gi->group); xxxxx }
代码
文件和函数的关系
展开CONFIGFS_ATTR宏,可以看到内核就是通过宏定义了一个configfs_attribute 结构体,在结构体中分别通过.show和.store定义了对应的处理函数。
// kernel/msm-5.4/include/linux/configfs.h #define CONFIGFS_ATTR(_pfx, _name) \ static struct configfs_attribute _pfx##attr_##_name = { \ .ca_name = __stringify(_name), \ .ca_mode = S_IRUGO | S_IWUSR, \ .ca_owner = THIS_MODULE, \ .show = _pfx##_name##_show, \ .store = _pfx##_name##_store, \ }
同样以CONFIGFS_ATTR(gadget_dev_desc_, max_speed);为例,_pfx等于gadget_dev_desc_,_name等于max_speed。那么最后被定义的结构体_pfx##attr_##_name展开后就是gadget_dev_desc_attr_max_speed。 结构体中的.show成员 _pfx##_name##_show被展开后就是gadget_dev_desc_max_speed_show。
gadget_dev_desc_attr_max_speed结构体将configfs机制下的文件和其对应操作函数进行了绑定,通过层层封装,最终存放在gadget_root_type 中。gadget_root_type 将所有的configfs文件整合在一起作为一个模板。在创建配置时将被使用到。
static struct configfs_attribute *gadget_root_attrs[] = { xxxxxxxxxxx &gadget_dev_desc_attr_max_speed, xxxxxxxxxxxx NULL, }; static const struct config_item_type gadget_root_type = { .ct_item_ops = &gadget_root_item_ops, .ct_attrs = gadget_root_attrs, .ct_owner = THIS_MODULE, };
创建对象
在configfs的框架里使用configfs_group_operations 去定义mkdir和rmdir的行为。上面示例在用户空间中执行了mkdir g2等同于直接调用gadgets_make(NULL, "g2");的接口。
static struct configfs_group_operations gadgets_ops = { .make_group = &gadgets_make, .drop_item = &gadgets_drop, };
在gadgets_make函数中可以清晰地看到gadget_root_type模板被使用,用于在创建对象时,同步创建模板中记录的全部文件。随后便创建了配置中的文件夹。
static struct config_group *gadgets_make(struct config_group *group,const char *name) { struct gadget_info *gi; gi = kzalloc(sizeof(*gi), GFP_KERNEL); if (!gi) return ERR_PTR(-ENOMEM); config_group_init_type_name(&gi->group, name, &gadget_root_type); config_group_init_type_name(&gi->group, name, &gadget_root_type); config_group_init_type_name(&gi->functions_group, "functions",&functions_type); configfs_add_default_group(&gi->functions_group, &gi->group); config_group_init_type_name(&gi->configs_group, "configs",&config_desc_type); configfs_add_default_group(&gi->configs_group, &gi->group); config_group_init_type_name(&gi->strings_group, "strings",&gadget_strings_strings_type); configfs_add_default_group(&gi->strings_group, &gi->group); config_group_init_type_name(&gi->os_desc_group, "os_desc",&os_desc_type); configfs_add_default_group(&gi->os_desc_group, &gi->group); xxxxx }
小结
configfs是基于文件系统的内核对象管理器,在用户空间通过mkdir显式的创建,使用rmdir销毁。
在mkdir之后会出现对应的属性。用户程序可以通过 readdir、read 函数读取这些属性,也可以通过 write 函数修改某些属性。重点在于configfs中的内容是在用户空间里创建、销毁,用户空间控制着 configfs的生命周期。可以认为 configfs就是这些内核对象的管理器。
那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。
还没有评论,来说两句吧...