一、Linux-移植设备树插件
分析
前面分析中我们知道如何使用设备树插件,本文将探讨如何移植设备树 插件到 RK3568 开发板上。移植设备树插件主要包括以下几个步骤
挂载 configfs 虚拟文件系统
首先我们打开 Linux 内核源码,输入以下命令打开 menuconfig 配置界面。
界面打开之后,将下图
勾选之后保存退出,然后输入以下命令
cp .config arch/arm64/configs/rockchip_linux_defconfig cd ../
./build.sh kernel
将编译之后的内核镜像烧写到开发板上,接着使用 mount 命令检查 configfs 虚拟文件系统是否挂载成功。挂载成功如下图
如果系统没有自动挂载 configfs 虚拟文件系统,需要输入以下命令挂载:
mount -t configfs none /sys/kernel/config
配置内核支持设备树插件
首先我们打开 Linux 内核源码,输入以下命令打开 menuconfig 配置界面。界面打开之后,将下图中的选项勾选。
勾选之后保存退出,然后输入以下命令
cp .config arch/arm64/configs/rockchip_linux_defconfig cd ../
./build.sh kernel
内核编译成功之后,接下来我们开始移植设备树插件驱动。
移植驱动
/*********************************************************************************
*
* Copyright (C) 2016-2021 Ichiro Kawazome
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
********************************************************************************/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/configfs.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/limits.h>
#include <linux/file.h>
#include <linux/version.h>
/**
* Device Tree Overlay Item Structure
*/
struct dtbocfg_overlay_item {
struct config_item item;
#if (LINUX_VERSION_CODE < 0x041100)
struct device_node* node;
#endif
int id;
void* dtbo;
int dtbo_size;
};
/**
* dtbocfg_overlay_create() - Create Device Tree Overlay
* @overlay: Pointer to Device Tree Overlay Item
* return Success(0) or Error Status.
*/
static int dtbocfg_overlay_item_create(struct dtbocfg_overlay_item *overlay)
{
int ret_val;
#if (LINUX_VERSION_CODE >= 0x041100)
{
int ovcs_id = 0;
ret_val = of_overlay_fdt_apply(overlay->dtbo,overlay->dtbo_size, &ovcs_id);
if (ret_val != 0) {
pr_err("%s: Failed to apply overlay (ret_val=%d)
", __func__, ret_val);
goto failed;
}
overlay->id = ovcs_id;
pr_debug("%s: apply OK(id=%d)
", __func__, ovcs_id);
}
#else
#if (LINUX_VERSION_CODE >= 0x040700)
of_fdt_unflatten_tree(overlay->dtbo, NULL, &overlay->node);
#else
of_fdt_unflatten_tree(overlay->dtbo, &overlay->node);
#endif
if (overlay->node == NULL) {
pr_err("%s: failed to unflatten tree
", __func__);
ret_val = -EINVAL;
goto failed;
}
pr_debug("%s: unflattened OK
", __func__);
#if (LINUX_VERSION_CODE >= 0x040F00)
{
int ovcs_id = 0;
ret_val = of_overlay_apply(overlay->node, &ovcs_id);
if (ret_val != 0) {
pr_err("%s: Failed to apply overlay (ret_val=%d)
", __func__, ret_val);
goto failed;
}
overlay->id = ovcs_id;
pr_debug("%s: apply OK(id=%d)
", __func__, ovcs_id);
}
#else
{
of_node_set_flag(overlay->node, OF_DETACHED);
ret_val = of_resolve_phandles(overlay->node);
if (ret_val != 0) {
pr_err("%s: Failed to resolve tree
", __func__);
goto failed;
}
pr_debug("%s: resolved OK
", __func__);
ret_val = of_overlay_create(overlay->node);
if (ret_val < 0) {
pr_err("%s: Failed to create overlay (ret_val=%d)
", __func__, ret_val);
goto failed;
}
overlay->id = ret_val;
}
#endif
#endif
pr_debug("%s: create OK
", __func__);
return 0;
failed:
return ret_val;
}
/**
* dtbocfg_overlay_item_release() - Relase Device Tree Overlay
* @overlay: Pointer to Device Tree Overlay Item
* return none
*/
static void dtbocfg_overlay_item_release(struct dtbocfg_overlay_item *overlay)
{
if (overlay->id >= 0) {
#if (LINUX_VERSION_CODE >= 0x040F00)
of_overlay_remove(&overlay->id);
#else
of_overlay_destroy(overlay->id);
#endif
overlay->id = -1;
}
}
/**
* container_of_dtbocfg_overlay_item() - Get Device Tree Overlay Item Pointer from Configuration Item
* @item: Pointer to Configuration Item
* return Pointer to Device Tree Overlay Item
*/
static inline struct dtbocfg_overlay_item* container_of_dtbocfg_overlay_item(struct config_item *item)
{
return item ? container_of(item, struct dtbocfg_overlay_item, item) : NULL;
}
/**
* dtbocfg_overlay_item_status_store() - Set Status Attibute
* @item: Pointer to Configuration Item
* @page: Pointer to Value Buffer
* @count: Size of Value Buffer Size
* return Stored Size or Error Status.
*/
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_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) {
dtbocfg_overlay_item_release(overlay);
}
} else {
if (overlay->id < 0) {
dtbocfg_overlay_item_create(overlay);
}
}
return count;
failed:
return -EPERM;
}
/**
* dtbocfg_overlay_item_status_show() - Show Status Attibute
* @item : Pointer to Configuration Item
* @page : Pointer to Value for Store
* return String Size or Error Status.
*/
static ssize_t dtbocfg_overlay_item_status_show(struct config_item *item, char *page)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
return sprintf(page, "%d
", overlay->id >= 0 ? 1 : 0);
}
/**
* dtbocfg_overlay_item_dtbo_write() - Write Device Tree Blob to Configuration Item
* @item : Pointer to Configuration Item
* @page : Pointer to Value Buffer
* @count: Size of Value Buffer
* return Stored Size or Error Status.
*/
static ssize_t dtbocfg_overlay_item_dtbo_write(struct config_item *item, const void *buf, size_t count)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
if (overlay->dtbo_size > 0) {
if (overlay->id >= 0) {
return -EPERM;
}
kfree(overlay->dtbo);
overlay->dtbo = NULL;
overlay->dtbo_size = 0;
}
overlay->dtbo = kmemdup(buf, count, GFP_KERNEL);
if (overlay->dtbo == NULL) {
overlay->dtbo_size = 0;
return -ENOMEM;
} else {
overlay->dtbo_size = count;
return count;
}
}
/**
* dtbocfg_overlay_item_dtbo_read() - Read Device Tree Blob from Configuration Item
* @item : Pointer to Configuration Item
* @page : Pointer to Value for Store, or NULL to query the buffer size
* @size : Size of the supplied buffer
* return Read Size
*/
static ssize_t dtbocfg_overlay_item_dtbo_read(struct config_item *item, void *buf, size_t size)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
if (overlay->dtbo == NULL)
return 0;
if (buf != NULL)
memcpy(buf, overlay->dtbo, overlay->dtbo_size);
return overlay->dtbo_size;
}
/**
* Device Tree Blob Overlay Attribute Structure
*/
CONFIGFS_BIN_ATTR(dtbocfg_overlay_item_, dtbo, NULL, 1024 * 1024); // 1MiB should be way more than enough
CONFIGFS_ATTR(dtbocfg_overlay_item_, status);
static struct configfs_attribute *dtbocfg_overlay_attrs[] = {
&dtbocfg_overlay_item_attr_status,
NULL,
};
static struct configfs_bin_attribute *dtbocfg_overlay_bin_attrs[] = {
&dtbocfg_overlay_item_attr_dtbo,
NULL,
};
/**
* dtbocfg_overlay_release() - Release Device Tree Overlay Item
* @item : Pointer to Configuration Item
* Return None
*/
static void dtbocfg_overlay_release(struct config_item *item)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
pr_debug("%s
", __func__);
dtbocfg_overlay_item_release(overlay);
if (overlay->dtbo) {
kfree(overlay->dtbo);
overlay->dtbo = NULL;
overlay->dtbo_size = 0;
}
kfree(overlay);
}
/**
* Device Tree Blob Overlay Item Structure
*/
static struct configfs_item_operations dtbocfg_overlay_item_ops = {
.release = dtbocfg_overlay_release,
};
static struct config_item_type dtbocfg_overlay_item_type = {
.ct_item_ops = &dtbocfg_overlay_item_ops,
.ct_attrs = dtbocfg_overlay_attrs,
.ct_bin_attrs = dtbocfg_overlay_bin_attrs,
.ct_owner = THIS_MODULE,
};
/**
* dtbocfg_overlay_group_make_item() - Make Device Tree Overlay Group Item
* @group: Pointer to Configuration Group
* @name : Pointer to Group Name
* Return Pointer to Device Tree Overlay Group Item
*/
static struct config_item *dtbocfg_overlay_group_make_item(struct config_group *group, const char *name)
{
struct dtbocfg_overlay_item *overlay;
pr_debug("%s
", __func__);
overlay = kzalloc(sizeof(*overlay), GFP_KERNEL);
if (!overlay)
return ERR_PTR(-ENOMEM);
overlay->id = -1;
overlay->dtbo = NULL;
overlay->dtbo_size = 0;
config_item_init_type_name(&overlay->item, name, &dtbocfg_overlay_item_type);
return &overlay->item;
}
/**
* dtbocfg_overlay_group_drop_item() - Drop Device Tree Overlay Group Item
* @group: Pointer to Configuration Group
* @item : Pointer to Device Tree Overlay Group Item
*/
static void dtbocfg_overlay_group_drop_item(struct config_group *group, struct config_item *item)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
pr_debug("%s
", __func__);
config_item_put(&overlay->item);
}
/**
* Device Tree Blob Overlay Sub Group Structures
*/
static struct configfs_group_operations dtbocfg_overlays_ops = {
.make_item = dtbocfg_overlay_group_make_item,
.drop_item = dtbocfg_overlay_group_drop_item,
};
static struct config_item_type dtbocfg_overlays_type = {
.ct_group_ops = &dtbocfg_overlays_ops,
.ct_owner = THIS_MODULE,
};
static struct config_group dtbocfg_overlay_group;
/**
* Device Tree Blob Overlay Root Sub System Structures
*/
static struct configfs_group_operations dtbocfg_root_ops = {
/* empty - we don't allow anything to be created */
};
static struct config_item_type dtbocfg_root_type = {
.ct_group_ops = &dtbocfg_root_ops,
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem dtbocfg_root_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "device-tree",
.ci_type = &dtbocfg_root_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(dtbocfg_root_subsys.su_mutex),
};
/**
* dtbocfg_module_init()
*/
static int __init dtbocfg_module_init(void)
{
int retval = 0;
pr_info("%s
", __func__);
config_group_init(&dtbocfg_root_subsys.su_group);
config_group_init_type_name(&dtbocfg_overlay_group, "overlays", &dtbocfg_overlays_type);
retval = configfs_register_subsystem(&dtbocfg_root_subsys);
if (retval != 0) {
pr_err( "%s: couldn't register subsys
", __func__);
goto register_subsystem_failed;
}
retval = configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group);
if (retval != 0) {
pr_err( "%s: couldn't register group
", __func__);
goto register_group_failed;
}
pr_info("%s: OK
", __func__);
return 0;
register_group_failed:
configfs_unregister_subsystem(&dtbocfg_root_subsys);
register_subsystem_failed:
return retval;
}
/**
* dtbocfg_module_exit()
*/
static void __exit dtbocfg_module_exit(void)
{
configfs_unregister_group(&dtbocfg_overlay_group);
configfs_unregister_subsystem(&dtbocfg_root_subsys);
}
module_init(dtbocfg_module_init);
module_exit(dtbocfg_module_exit);
MODULE_AUTHOR("ikwzm");
MODULE_DESCRIPTION("Device Tree Overlay Configuration File System");
MODULE_LICENSE("Dual BSD/GPL");
dtbocfg.c 是设备树插件驱动,我们只要将此驱动编译成驱动模块或者编译进内核即可。
二、Linux设备模型
什么是设备模型
字符设备驱动通常适用于相对简单的设备。但对于电源管理、热插拔事件管理等功能而言,字符设备框架可能不够灵活和高效。为此,Linux 内核提供了设备模型,它允许开发人员以更高级的方式描述硬件设备及其关系,并提供了一组通用的 API 和机制来处理设备注册、热插拔事件和电源管理等任务。
使用设备模型,驱动开发人员可以将更多的底层功能交给内核处理,而不必重复实现这些基础功能。这使得驱动编写更加高级和模块化,减少了重复工作和出错的可能性。例如,对于常见的硬件设备如 USB、I2C 和平台设备,内核已经提供了相应的设备模型和相关驱动,开发人员可以基于这些模型快速实现特定设备的功能,并借助内核的电源管理和热插拔事件管理功能。
总之,使用设备模型可以简化驱动开发过程,并提供更高级的功能和灵活性,使开发人员能够更好地应对复杂硬件设备的需求。
设备模型的好处
设备模型在内核驱动中扮演着重要的角色,它提供了一种统一的方式来描述硬件设备及其之间的关系。以下是设备模型在内核驱动中的一些重要方面:
1. 代码复用:设备模型允许多个设备复用同一个驱动。通过在设备树或总线上定义不同的设备节点,这些设备可以使用相同的驱动进行初始化和管理。这样可以减少代码冗余,提高驱动的复用性和维护性。
2. 资源的动态申请和释放:设备模型提供了一种机制来动态申请和释放设备所需的资源,如内存和中断等。驱动可以使用这些机制来管理设备所需的资源,确保在设备初始化和关闭时进行正确的资源分配和释放。
3. 简化驱动编写:设备模型提供了一组通用 API 和机制,使得驱动编写更加简化和模块化。开发人员可以使用这些 API 来注册设备、处理设备事件、进行设备的读写操作等,而不必重复实现这些通用功能。
4. 热插拔机制:设备模型支持热插拔机制,能够在运行时动态添加或移除设备。当设备插入或拔出时,内核会生成相应的热插拔事件,驱动可以通过监听这些事件来执行相应的操作,如设备的初始化或释放。
5. 面向对象的思想:设备模型的设计借鉴了面向对象编程(OOP)的思想。每个设备都被看作是一个对象,具有自己的属性和方法,并且可以通过设备模型的机制进行继承和扩展。这种设计使得驱动的编写更加模块化和可扩展,可以更好地应对不同类型的设备和功能需求。
总之,设备模型在内核驱动中扮演着关键的角色,通过提供统一的设备描述和管理机制,简化了驱动的编写和维护过程,提高了代码的复用性和可维护性,并支持热插拔和动态资源管理等重要功能。
kobject 和 kset
基本概念
kobject 和 kset 是 Linux 内核中用于管理内核对象的基本概念。object(内核对象)是内核中抽象出来的通用对象模型,用于表示内核中的各种实体。kobject 是一个结构体,其中包含了一些描述该对象的属性和方法。它提供了一种统一的接口和机制, 用于管理和操作内核对象。kobject 结构体在内核源码 kernel/include/linux/kobject.h 文件中,如 下所示:
kobject
/*
* 内核对象(kobject)基础结构体
* 它是Linux设备驱动模型、sysfs文件系统和内核对象管理的核心数据结构。
* 通常不单独使用,而是通过“嵌入”的方式,作为其他更大结构体的一个成员,
* 从而为上层结构体提供对象生命周期、sysfs接口和事件通知等基础服务。
*/
struct kobject {
const char *name; // 对象名称,在sysfs中显示为目录名
struct list_head entry; // 链表节点,用于将本对象链接到所属kset的链表中
struct kobject *parent; // 指向父kobject的指针,用于在sysfs中建立层级结构
struct kset *kset; // 指向所属kset(对象集合)的指针
struct kobj_type *ktype; // 指向对象类型描述符,包含释放函数、属性操作等
struct kernfs_node *sd; // 对应sysfs目录的节点指针
// 注释:sysfs目录条目
struct kref kref; // 引用计数器,用于对象的生命周期管理
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release; // 用于调试模式下的延迟释放工作项
#endif
// 以下为状态标志位域(每个标志占1位):
unsigned int state_initialized:1; // 1表示对象已初始化完毕
unsigned int state_in_sysfs:1; // 1表示对象已在sysfs中成功创建目录
unsigned int state_add_uevent_sent:1; // 1表示已发送“添加”事件到用户空间
unsigned int state_remove_uevent_sent:1;// 1表示已发送“移除”事件到用户空间
unsigned int uevent_suppress:1; // 1表示抑制uevent事件的上报
// Android内核ABI兼容性保留字段,用于保证不同版本内核模块间的二进制兼容性
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
该结构体还包含了一些与特定配置相关的保留字段。这些字段共同构成了 kobject 的基本
属性和关系,用于在内核中表示和管理不同类型的内核对象。通过这些字段,可以建立层次化 的关系,进行资源管理和操作。每一个 kobject 都会对应系统/sys/下的一个目录,如下图所示。

进入/sys/bus 目录下,bus 目录下的文件都
是和总线相关的目录,比如 amba 总线,CPU 总线,platform 总线。如下图所示

kobject 表示系统/sys 下的一个目录,而目录又是有多个层次所以对应 kobject 的树状关系如下图所示:

在 kobject 结构体中,parent 指针用于表示父 kobject,从而建立了 kobject 之间的层次关系,类似于目录结构中的父目录和子目录的关系。一个 kobject 可以有一 个父 kobject 和多个子 kobject,通过 parent 指针可以将它们连接起来形成一个层次化的结构, 类似于目录结构中,一个目录可以有一个父目录和多个子目录,通过目录的路径可以表示目录 之间的层次关系。这种层次化的关系可以方便地进行遍历,查找和管理,使得内核对象能够按 照层次关系进行组织和管理。这种设计使得 kobject 的树状结构在内核中具有很高的灵活性和 可扩展性。
kset
kset(内核对象集合)是一种用于组织和管理一组相关 kobject 的容器。kset 是 kobject 的一种扩展,它提供了一种层次化的组织结构,可以将一组相关的 kobject 组织在一起。kset 在内 核里面用 struct kset 结构体来表示,定义在 include/linux/kobject.h 头文件中
/*
* 内核对象集合(kset)结构体
* 它是一组具有相同类型的kobject的集合,用于在sysfs中创建目录并管理一组相关对象。
* kset本身也是一个kobject,因此可以拥有自己的属性和sysfs目录。
*/
struct kset {
struct list_head list; // 本kset中所有kobject的链表头
spinlock_t list_lock; // 保护链表访问的自旋锁
struct kobject kobj; // 内嵌的kobject,使kset本身也是一个kobject
const struct kset_uevent_ops *uevent_ops; // 用户空间事件通知操作集
#ifdef CONFIG_SYSFS
/*
* 早期内核版本中可能有此字段,用于支持热插拔
* 但注意:较新内核版本中此字段可能已移除或重命名
*/
struct kset_hotplug_ops *hotplug_ops;
#endif
};
该结构体还包含了一些与特定配置相关的保留字段。kset 通过包含一个 kobject 作为其成 员,将 kset 本身表示为一个 kobject,并使用 kobj 来管理和操作 kset。通过 list 字段,kset 可以 链接到全局 kset 链表中,以便进行全局的遍历和管理。同时,list_lock 字段用于保护对 kset 链 表的并发访问。kset 还可以定义自己的 uevent 操作,用于处理与 kset 相关的 uevent 事件,例 如在添加或删除 kobject 时发送相应的 uevent 通知
这些字段共同构成了 kset 的基本属性和关系,用于在内核中组织和管理一组相关的 kobject。kset 提供了一种层次化的组织结构,并与 sysfs 目录相对应,方便对 kobject 进行管理 和操作。
kset 和 kobject 的关系
在 Linux 内核中,kset 和 kobject 是相关联的两个概念,它们之间存在一种层次化的关系如下图所示

1. kset 是 kobject 的一种扩展:kset 可以被视为 kobject 的一种特殊形式,它扩展了 kobject 并提供了额外的功能。kset 可以包含多个 kobject,形成一个层次化的组织结构。
2. kobject 属于一个 kset:每个 kobject 都属于一个 kset。kobject 结构体中的 字段指向所属的 kset。这种关联关系表示了 kobject 所在的集合或组织。
struct kset *kset
通过 kset 和 kobject 之间的关系,可以实现对内核对象的层次化管理和操作。kset 提供了对 kobject 的集合管理接口,可以通过 kset 进行迭代、查找、添加或删除 kobject。同时,kset 也提供了特定于集合的功能,例如在集合级别处理 事件。
uevent
总结:kset 和 kobject 之间的关系可以概括为:一个 kset 可以包含多个 kobject,而一个 kobject 只能属于一个 kset。kset 提供了对 kobject 的集合管理和操作接口,用于组织和管理具有相似特性和关系的 kobject。这种关系使得内核能够以一种统一的方式管理和操作不同类型的内核对象。
例子
kobject
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
// 定义了三个kobject指针变量:mykobject01、mykobject02、mykobject03
struct kobject *mykobject01;
struct kobject *mykobject02;
struct kobject *mykobject03;
// 定义了一个kobj_type结构体变量mytype,用于描述kobject的类型。
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建kobject的第一种方法
// 创建并添加了名为"mykobject01"的kobject对象,父kobject为NULL
mykobject01 = kobject_create_and_add("mykobject01", NULL);
// 创建并添加了名为"mykobject02"的kobject对象,父kobject为mykobject01。
mykobject02 = kobject_create_and_add("mykobject02", mykobject01);
// 创建kobject的第二种方法
// 1 使用kzalloc函数分配了一个kobject对象的内存
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2 初始化并添加到内核中,名为"mykobject03"。
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "%s", "mykobject03");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放了之前创建的kobject对象
kobject_put(mykobject01);
kobject_put(mykobject02);
kobject_put(mykobject03);
}
module_init(mykobj_init); // 指定模块的初始化函数
module_exit(mykobj_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("xsx"); // 模块的作者
开发板启动之后,使用以下命令进行驱动模块的加载,如下图

驱动加载之后,我们进入/sys/目录下,如下图所示:

如上图所示我们发现kobject01,kobject03 创建在系统根目录/sys 目录下,kobject02 的父节点是 kobject01,所以被创建在mykobject02 目录下。现在我们成功验证了创建 kobject 就 是在系统根目录/sys 目录下创建一个文件夹,他们是一一对应的关系。最后可以使用以下命令进行驱动的卸载,如下图

kobject
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
// 定义kobject结构体指针,用于表示第一个自定义内核对象
struct kobject *mykobject01;
// 定义kobject结构体指针,用于表示第二个自定义内核对象
struct kobject *mykobject02;
// 定义kset结构体指针,用于表示自定义内核对象的集合
struct kset *mykset;
// 定义kobj_type结构体,用于定义自定义内核对象的类型
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建并添加kset,名称为"mykset",父kobject为NULL,属性为NULL
mykset = kset_create_and_add("mykset", NULL, NULL);
// 为mykobject01分配内存空间,大小为struct kobject的大小,标志为GFP_KERNEL
mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将mykset设置为mykobject01的kset属性
mykobject01->kset = mykset;
// 初始化并添加mykobject01,类型为mytype,父kobject为NULL,格式化字符串为"mykobject01"
ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");
// 为mykobject02分配内存空间,大小为struct kobject的大小,标志为GFP_KERNEL
mykobject02 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将mykset设置为mykobject02的kset属性
mykobject02->kset = mykset;
// 初始化并添加mykobject02,类型为mytype,父kobject为NULL,格式化字符串为"mykobject02"
ret = kobject_init_and_add(mykobject02, &mytype, NULL, "%s", "mykobject02");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放mykobject01的引用计数
kobject_put(mykobject01);
// 释放mykobject02的引用计数
kobject_put(mykobject02);
}
module_init(mykobj_init); // 指定模块的初始化函数
module_exit(mykobj_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("xsx"); // 模块的作者
开发板启动之后,使用以下命令进行驱动模块的加载,如下图

驱动加载之后,我们进入/sys/目录下,可以看到创建生成的 kset,如下图所示,我们进到
mykset 目录下,可以看到创建的 kobject。

最后可以使用以下命令进行驱动的卸载,如下图

三、创建kobject与kset
创建kobject
本章节是关于创建 kobject 的实践,通过使用 kobject 的 API 函数来创建系统根目录下的目 录。本章介绍了两种创建 kobject 的方法,一种是使用 kobject_create_and_add 函数,另一种是 使用 kzalloc 和 kobject_init_and_add 函数,还介绍了如何释放创建的 kobject
例子
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
// 定义了三个kobject指针变量:mykobject01、mykobject02、mykobject03
struct kobject *mykobject01;
struct kobject *mykobject02;
struct kobject *mykobject03;
// 定义了一个kobj_type结构体变量mytype,用于描述kobject的类型。
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建kobject的第一种方法
// 创建并添加了名为"mykobject01"的kobject对象,父kobject为NULL
mykobject01 = kobject_create_and_add("mykobject01", NULL);
// 创建并添加了名为"mykobject02"的kobject对象,父kobject为mykobject01。
mykobject02 = kobject_create_and_add("mykobject02", mykobject01);
// 创建kobject的第二种方法
// 1 使用kzalloc函数分配了一个kobject对象的内存
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2 初始化并添加到内核中,名为"mykobject03"。
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "%s", "mykobject03");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放了之前创建的kobject对象
kobject_put(mykobject01);
kobject_put(mykobject02);
kobject_put(mykobject03);
}
module_init(mykobj_init); // 指定模块的初始化函数
module_exit(mykobj_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
运行测试
开发板启动之后,使用以下命令进行驱动模块的加载,如下图

驱动加载之后,我们进入/sys/目录下,如下图所示

如上图所示,我们发现 kobject01,kobject03 创建在系统根目录/sys 目录下,kobject02 的父节点是 kobject01,所以被创建在 mykobject02 目录下。现在我们成功验证了创建 kobject 就 是在系统根目录/sys 目录下创建一个文件夹,他们是一一对应的关系。最后可以使用以下命令进行驱动的卸载,如下图

创建kset实验
实现 Linux 上创建 kset 的实验。在实验中介绍了如何使用代码创建 kset,并将 多个 kobject 与 kset 关联起来。通过演示实验现象,讲解了 kset 是一组 kobject 的集合,并解 释了 kobject 在 sys 目录下生成的原因。
例子
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
// 定义kobject结构体指针,用于表示第一个自定义内核对象
struct kobject *mykobject01;
// 定义kobject结构体指针,用于表示第二个自定义内核对象
struct kobject *mykobject02;
// 定义kset结构体指针,用于表示自定义内核对象的集合
struct kset *mykset;
// 定义kobj_type结构体,用于定义自定义内核对象的类型
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建并添加kset,名称为"mykset",父kobject为NULL,属性为NULL
mykset = kset_create_and_add("mykset", NULL, NULL);
// 为mykobject01分配内存空间,大小为struct kobject的大小,标志为GFP_KERNEL
mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将mykset设置为mykobject01的kset属性
mykobject01->kset = mykset;
// 初始化并添加mykobject01,类型为mytype,父kobject为NULL,格式化字符串为"mykobject01"
ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");
// 为mykobject02分配内存空间,大小为struct kobject的大小,标志为GFP_KERNEL
mykobject02 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将mykset设置为mykobject02的kset属性
mykobject02->kset = mykset;
// 初始化并添加mykobject02,类型为mytype,父kobject为NULL,格式化字符串为"mykobject02"
ret = kobject_init_and_add(mykobject02, &mytype, NULL, "%s", "mykobject02");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放mykobject01的引用计数
kobject_put(mykobject01);
// 释放mykobject02的引用计数
kobject_put(mykobject02);
}
module_init(mykobj_init); // 指定模块的初始化函数
module_exit(mykobj_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
运行测试
开发板启动之后,使用以下命令进行驱动模块的加载,如下图

驱动加载之后,我们进入/sys/目录下,可以看到创建生成的 kset,如下图所示,我们进到mykset 目录下,可以看到创建的kobject。

最后可以使用以下命令进行驱动的卸载,如下图

四、为什么要引入设备模型
问题
设备模型在内核驱动中扮演着关键角色,通过提供统一的设备描述和管理机制,简化了驱动的编写和维护过程,提高了代码的复用性和可维护性,并支持热插拔和动态资源管理等重要功能。设备模型包含以下四个核心概念:
1. 总线(Bus):总线是设备模型中的基础组件,用于连接和传输数据。总线可以是物理总线(如 PCI、USB)或虚拟总线(如虚拟设备总线),提供了设备之间通信和数据传输的基本机制。
2. 设备(Device):设备是指计算机系统中的硬件组件,例如网卡、显示器、键盘等。每个设备都有一个唯一的标识符,用于在系统中进行识别和管理。设备模型通过设备描述符来描述设备的属性和特性。
3. 驱动(Driver):驱动是设备模型中的软件组件,用于控制和管理设备的操作。每个设备都需要相应的驱动程序与操作系统进行交互和通信。驱动程序负责向设备发送命令、接收设备事件、进行设备配置等操作。
4. 类(Class):类是设备模型中的逻辑组织单元,用于对具有相似功能和特性的设备进行分类和管理。类定义了一组共享相同属性和行为的设备的集合。通过设备类,可以对设备进行分组、识别和访问。
设备模型的设计目的是为了提供一种统一的方式来管理和操作系统中的各种硬件设备。通 过将设备、驱动和总线等概念进行抽象和标准化,设备模型可以提供一致的接口和数据结构, 简化驱动开发和设备管理,并实现设备的兼容性和可移植性。
相关结构体
在 Linux 设备模型中,虚构了一条名的总线,用来连接一些直接与 CPU 相 连的设备控制器。这种设备控制器通常不符合常见的总线标准,比如 PCI 总线和 USB 总线,所 以 Linux 使用 platform 总线来管理这些设备。Platform 总线允许设备控制器与设备驱动程序进 行通信和交互。设备控制器在设备树中定义,并通过设备树与对应的设备驱动程序匹配。在设 备模型中,Platform 总线提供了一种统一的接口和机制来注册和管理这些设备控制器。设备驱 动程序可以通过注册到 Platform 总线的方式,与相应的设备控制器进行绑定和通信。设备驱 动程序可以访问设备控制器的寄存器、配置设备、处理中断等操作,如下图所示:
platform

当作为嵌入式/驱动开发人员时,了解设备,驱动程序,总线和类这几个结构体的概念和 关系非常重要。尽管在芯片原厂提供的 BSP 中已经实现了设备模型,但是了解这些概念可以帮 助您更好地理解设备的工作原理,驱动程序的编写和设备的管理。
struct bus_type
bus_type 结构体是 Linux 内核中用于描述总线的数据结构,定义在include/linux/device.h 头文件中。以下是对 bus_type 结构体的一般定义。
/**
* struct bus_type - Linux 内核中的总线类型描述符
*
* 此结构体定义了总线的接口和行为,是Linux设备模型的核心组件之一。
* 每个总线类型(如PCI、USB、I2C等)都需要定义并注册一个bus_type实例。
*/
struct bus_type {
/**
* @name: 总线名称
*
* 总线的唯一标识符,用于sysfs目录名和识别总线类型。
* 例如: "pci", "usb", "i2c", "spi", "platform" 等。
* 对应sysfs路径: /sys/bus/<name>/
*/
const char *name;
/**
* @dev_name: 默认设备名称前缀
*
* 当创建设备时,如果没有指定设备名称,内核会使用此前缀生成默认名称。
* 例如: "usb"总线使用"usb"作为前缀。
*/
const char *dev_name;
/**
* @dev_root: 总线根设备
*
* 指向代表总线本身的设备结构体。这个设备是总线设备层次结构的根。
* 用于在sysfs中创建总线目录: /sys/bus/<name>/devices/
*/
struct device *dev_root;
/**
* @bus_groups: 总线属性组数组
*
* 定义总线自身的sysfs属性组。这些属性出现在 /sys/bus/<name>/ 目录下。
* 用于导出总线的配置、状态等信息的可读写接口。
* 以NULL指针结尾的数组。
*/
const struct attribute_group **bus_groups;
/**
* @dev_groups: 设备默认属性组数组
*
* 定义连接到该总线的所有设备共有的默认sysfs属性。
* 这些属性会自动添加到该总线上的每个设备中。
* 出现在 /sys/bus/<name>/devices/<device>/ 目录下。
* 以NULL指针结尾的数组。
*/
const struct attribute_group **dev_groups;
/**
* @drv_groups: 驱动默认属性组数组
*
* 定义注册到该总线的所有驱动程序共有的默认sysfs属性。
* 这些属性会自动添加到该总线的每个驱动中。
* 出现在 /sys/bus/<name>/drivers/<driver>/ 目录下。
* 以NULL指针结尾的数组。
*/
const struct attribute_group **drv_groups;
/**
* @match: 设备-驱动匹配回调函数
*
* 当新设备添加到总线或新驱动注册到总线时调用此函数,
* 判断特定设备是否可由特定驱动程序驱动。
* 返回: 0 - 不匹配, 非0 - 匹配
*
* 参数:
* @dev: 要匹配的设备
* @drv: 要匹配的驱动
*/
int (*match)(struct device *dev, struct device_driver *drv);
/**
* @uevent: 热插拔事件回调函数
*
* 在生成设备热插拔事件(如设备添加、移除)前调用,
* 用于向用户空间发送事件通知(通过netlink)。
* 可以添加环境变量到uevent中。
*
* 参数:
* @dev: 触发事件的设备
* @env: 存储环境变量的结构
*/
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
/**
* @probe: 设备探测回调函数
*
* 当设备与驱动成功匹配后调用,用于初始化和配置设备。
* 通常由总线驱动实现,但也可以由设备驱动实现。
*
* 参数:
* @dev: 要探测的设备
*/
int (*probe)(struct device *dev);
/**
* @remove: 设备移除回调函数
*
* 当设备从总线移除或驱动卸载时调用,执行清理操作。
* 与probe函数相对应。
*
* 参数:
* @dev: 要移除的设备
*/
int (*remove)(struct device *dev);
/**
* @shutdown: 系统关机回调函数
*
* 在系统关机时调用,用于优雅地关闭设备。
* 设备应进入安全状态,以便可以安全断电。
*
* 参数:
* @dev: 要关闭的设备
*/
void (*shutdown)(struct device *dev);
/**
* @online: 设备上线回调函数
*
* 当设备从离线状态转为在线状态时调用。
* 用于热插拔和动态配置场景。
*
* 参数:
* @dev: 要上线的设备
*/
int (*online)(struct device *dev);
/**
* @offline: 设备离线回调函数
*
* 当设备从在线状态转为离线状态时调用。
* 用于热插拔和动态配置场景。
*
* 参数:
* @dev: 要离线的设备
*/
int (*offline)(struct device *dev);
/**
* @suspend: 设备挂起回调函数
*
* 在系统进入睡眠状态前调用,使设备进入低功耗状态。
* 保存设备状态,以便后续恢复。
*
* 参数:
* @dev: 要挂起的设备
* @state: 电源管理状态
*/
int (*suspend)(struct device *dev, pm_message_t state);
/**
* @resume: 设备恢复回调函数
*
* 在系统从睡眠状态恢复后调用,恢复设备到正常工作状态。
* 恢复suspend时保存的设备状态。
*
* 参数:
* @dev: 要恢复的设备
*/
int (*resume)(struct device *dev);
/**
* @num_vf: 虚拟功能数量回调函数
*
* 返回设备的虚拟功能(Virtual Function)数量。
* 主要用于SR-IOV(单根I/O虚拟化)设备。
*
* 参数:
* @dev: 要查询的设备
*/
int (*num_vf)(struct device *dev);
/**
* @dma_configure: DMA配置回调函数
*
* 配置设备的DMA参数,如DMA掩码、IOMMU映射等。
* 在设备探测期间调用。
*
* 参数:
* @dev: 要配置的设备
*/
int (*dma_configure)(struct device *dev);
/**
* @dma_get_required_mask: 获取所需DMA掩码回调函数
*
* 返回设备执行DMA操作所需的地址掩码。
* 用于确定设备支持的DMA地址范围。
*
* 参数:
* @dev: 要查询的设备
* 返回: 所需的DMA地址掩码
*/
u64 (*dma_get_required_mask)(struct device *dev);
/**
* @iommu_ops: IOMMU操作结构指针
*
* 指向IOMMU(输入输出内存管理单元)操作结构。
* 用于管理设备的IOMMU映射和DMA重映射。
* 如果为NULL,表示总线不支持IOMMU。
*/
const struct iommu_ops *iommu_ops;
/**
* @p: 总线私有数据指针
*
* 指向subsys_private结构体,包含总线的内部管理数据:
* - 设备和驱动链表
* - 互斥锁
* - kobject引用计数
* - 总线属性
* 由内核自动分配和管理,总线驱动不应直接访问。
*/
struct subsys_private *p;
/**
* @lock_key: 锁类键
*
* 用于锁验证的锁类键,帮助内核锁调试器(lockdep)检测锁的使用规则。
* 每个总线类型应有唯一的锁类键。
*/
struct lock_class_key lock_key;
/**
* 注意:实际内核中可能包含更多字段,具体取决于内核版本和配置。
* 此结构体在内核头文件include/linux/device/bus.h中定义。
*/
};
struct device
device 结构体是 Linux 内核中用于描述设备的数据结构,定义在 include/linux/device.h 头 文件中。以下是 device 结构体的一般定义:
/**
* struct device - 内核设备模型中的核心设备结构
*
* 表示连接到系统中的物理或逻辑设备,是Linux设备模型的基石。
* 每个设备在内核中都有一个对应的device结构实例。
*/
struct device {
/**
* @parent: 指向父设备的指针
*
* 设备层次结构中的父设备。例如:
* - USB设备的父设备是USB控制器
* - PCI设备的父设备是PCI桥
* - platform设备的父设备通常是平台总线
* 如果为NULL,表示这是顶级设备(如平台总线本身)。
*/
struct device *parent;
/**
* @p: 指向设备私有数据的指针
*
* 包含设备模型内部管理数据,如:
* - 设备链表
* - 驱动绑定信息
* - kobject状态
* 由内核自动分配和管理,驱动程序不应直接访问。
*/
struct device_private *p;
/**
* @kobj: 内嵌的kobject结构
*
* 提供设备在内核对象模型中的基本表示。
* 处理引用计数、sysfs表示、热插拔事件等。
* 设备是kobject的扩展,通过container_of可以获取device指针。
*/
struct kobject kobj;
/**
* @init_name: 设备的初始名称
*
* 设备的可选初始名称,用于在注册时设置设备名称。
* 如果指定,在注册时内核会使用此名称。
* 如果为NULL,则需要通过dev_set_name()设置名称。
*/
const char *init_name;
/**
* @type: 指向设备类型的指针
*
* 描述此类设备的通用类型信息。
* 包含设备类通用的回调函数和属性。
* 例如:所有字符设备共享相同的设备类型。
*/
const struct device_type *type;
/**
* @mutex: 设备互斥锁
*
* 保护设备结构体字段的互斥锁。
* 用于同步对设备的并发访问,特别是在设备添加/移除时。
*/
struct mutex mutex;
/**
* @bus: 指向设备所属总线类型的指针
*
* 描述设备连接的总线类型(如PCI、USB、I2C等)。
* 总线负责设备的探测、匹配和电源管理。
* 如果为NULL,表示设备不通过标准总线连接(如platform设备)。
*/
struct bus_type *bus;
/**
* @driver: 指向绑定到该设备的驱动的指针
*
* 当前控制该设备的驱动程序。
* 在成功匹配和绑定后设置,在解除绑定时清除。
* 驱动程序通过其probe()函数初始化设备。
*/
struct device_driver *driver;
/**
* @platform_data: 平台特定数据指针
*
* 指向设备特定的平台数据,通常由板级代码设置。
* 包含配置信息、资源或其他设备特定的数据。
* 所有权:驱动程序负责管理此内存。
*/
void *platform_data;
/**
* @driver_data: 驱动私有数据指针
*
* 指向驱动程序特定的私有数据。
* 驱动程序可以在probe()期间设置此字段,用于存储设备状态。
* 所有权:驱动程序负责管理此内存。
*/
void *driver_data;
/**
* @power: 电源管理相关数据
*
* 包含设备的电源管理状态、唤醒源、延迟等信息。
* 由内核电源管理框架使用。
*/
struct dev_pm_info power;
/**
* @pm_domain: 指向电源管理域的指针
*
* 设备的电源管理域,处理设备的电源状态转换。
* 用于复杂的电源管理场景,如时钟门控、电源门控。
*/
struct dev_pm_domain *pm_domain;
/**
* @numa_node: NUMA节点ID
*
* 设备所在的NUMA(非一致性内存访问)节点。
* 用于优化内存分配,使设备DMA缓冲区位于相同NUMA节点。
* 如果为-1,表示未知或与NUMA无关。
*/
int numa_node;
/**
* @dma_mask: DMA地址掩码
*
* 设备支持的DMA地址掩码,表示设备可以访问的物理地址范围。
* 例如:32位设备设置为0xFFFFFFFF,64位设备设置为~0ULL。
* 用于确保DMA缓冲区位于设备可寻址范围内。
*/
u64 *dma_mask;
/**
* @coherent_dma_mask: 一致性DMA掩码
*
* 设备支持的一致性DMA地址掩码。
* 用于分配可通过CPU和设备一致访问的DMA缓冲区。
* 通常与dma_mask相同,但有些设备对一致性访问有限制。
*/
u64 coherent_dma_mask;
/**
* @dma_parms: DMA参数
*
* 包含DMA相关参数,如最大段大小、段边界等。
* 用于优化DMA映射和分散/聚集操作。
*/
struct device_dma_parameters *dma_parms;
/**
* @dma_pools: DMA池链表头
*
* 设备的小型DMA缓冲区池链表。
* 用于频繁分配小型DMA缓冲区,提高性能。
*/
struct list_head dma_pools;
/**
* @archdata: 架构特定数据
*
* 包含特定于硬件架构的设备信息。
* 由各架构的代码使用,通用代码不应访问。
*/
struct dev_archdata archdata;
/**
* @of_node: 指向设备树节点的指针
*
* 指向与设备对应的设备树(Device Tree)节点。
* 用于从设备树获取设备的配置和资源信息。
* 如果设备不是从设备树实例化的,则为NULL。
*/
struct fwnode_handle *fwnode;
/**
* @devt: 设备号
*
* 字符设备和块设备的主/次设备号。
* 格式:主设备号在高12位,次设备号在低20位。
* 由dev_t帮助函数操作:MAJOR(devt), MINOR(devt)。
*/
dev_t devt;
/**
* @id: 设备ID
*
* 设备的唯一标识符,具体含义取决于总线类型。
* 例如:PCI设备使用vendor/device ID,USB设备使用product ID。
*/
u32 id;
/**
* @class: 指向设备类的指针
*
* 设备所属的设备类(如输入设备、网络设备、块设备等)。
* 设备类在sysfs中创建目录:/sys/class/<class_name>/
* 包含同类设备的通用属性和操作。
*/
struct class *class;
/**
* @groups: 设备属性组数组
*
* 设备的默认属性组,在sysfs中创建属性文件。
* 出现在 /sys/devices/.../<device>/ 目录下。
* 以NULL指针结尾的数组。
*/
const struct attribute_group **groups;
/**
* @release: 设备释放回调函数
*
* 当设备的引用计数归零时调用,执行清理操作。
* 必须被所有设备实现,用于释放设备结构体。
*
* 参数:
* @dev: 要释放的设备
*/
void (*release)(struct device *dev);
/**
* @iommu_group: IOMMU组指针
*
* 设备所属的IOMMU组,组内设备共享IOMMU页表。
* 用于DMA隔离和安全,防止设备访问未授权内存。
*/
struct iommu_group *iommu_group;
/**
* @dma_coherent: 一致性DMA标志
*
* 指示设备是否支持硬件强制执行的一致性缓存。
* 如果为true,内核可以跳过某些缓存维护操作。
*/
bool dma_coherent:1;
/**
* @dma_ops_bypass: DMA操作绕过标志
*
* 指示设备是否绕过IOMMU进行DMA操作。
* 某些设备有内置IOMMU或可以直接访问物理内存。
*/
bool dma_ops_bypass:1;
/**
* @removable: 设备可移除性标志
*
* 指示设备是否可以在系统运行时物理移除。
* 影响电源管理和热插拔处理。
*/
unsigned removable:2;
/**
* @state_synced: 设备状态同步标志
*
* 指示设备的运行时电源管理状态是否与硬件同步。
* 用于管理设备的状态转换。
*/
bool state_synced:1;
/**
* @can_match: 设备可匹配标志
*
* 指示设备是否参与驱动匹配过程。
* 某些设备(如父设备)可能不需要绑定驱动程序。
*/
bool can_match:1;
/**
* @offline_disabled: 离线禁用标志
*
* 如果为true,禁止设备进入离线状态。
* 用于关键设备,必须始终保持在线。
*/
bool offline_disabled:1;
/**
* @offline: 设备离线状态标志
*
* 如果为true,设备当前处于离线状态。
* 离线设备不响应操作,但仍在系统中注册。
*/
bool offline:1;
/**
* @of_node_reused: 设备树节点重用标志
*
* 如果为true,表示设备树节点已被其他设备使用。
* 用于处理设备树节点的重复使用情况。
*/
bool of_node_reused:1;
/**
* @state_initialized: 设备状态初始化标志
*
* 如果为true,表示设备已完全初始化。
* 在设备注册过程中设置,确保正确的初始化顺序。
*/
bool state_initialized:1;
/**
* @links: 设备链接
*
* 管理设备之间的依赖关系,如电源、时钟、DMA等。
* 确保依赖设备在消费者之前初始化,在提供者之后清理。
*/
struct dev_links_info links;
/**
* @msi: MSI/MSI-X中断数据
*
* 设备的消息信号中断(Message Signaled Interrupts)数据。
* 用于管理MSI/MSI-X中断分配和配置。
*/
struct msi_device_data *msi;
/**
* @dma_ops: DMA操作结构指针
*
* 指向设备特定的DMA操作结构。
* 包含DMA映射、取消映射、同步等回调函数。
* 如果为NULL,使用架构默认的DMA操作。
*/
const struct dma_map_ops *dma_ops;
/**
* @dma_mem: 设备预留的DMA内存
*
* 指向设备专用的DMA内存区域。
* 用于需要特殊DMA内存的设备(如旧ISA设备)。
*/
void *dma_mem;
/**
* @cma_area: 连续内存分配区域
*
* 指向设备的连续内存分配区域。
* 用于分配物理上连续的大块内存,用于DMA操作。
*/
struct cma *cma_area;
/**
* @dma_io_tlb_mem: I/O TLB内存
*
* 设备的I/O翻译后备缓冲器内存。
* 用于软件I/O TLB实现,处理DMA地址转换。
*/
struct io_tlb_mem *dma_io_tlb_mem;
/**
* @dma_coherent_hint: 一致性DMA提示
*
* 提示设备DMA缓冲区的一致性需求。
* 帮助DMA层优化缓存维护操作。
*/
enum dev_dma_attr dma_coherent_hint;
/**
* @dma_pfn_offset: DMA页帧号偏移
*
* 设备的DMA地址空间与物理地址空间之间的偏移。
* 用于有偏移DMA地址的设备(如某些ARM SoC)。
*/
unsigned long dma_pfn_offset;
/**
* @dma_ops_bypass: DMA操作绕过标志
*
* 指示设备是否绕过IOMMU进行DMA操作。
* 某些设备有内置IOMMU或可以直接访问物理内存。
*/
bool dma_ops_bypass:1;
/**
* 注意:
* 1. 实际内核中可能包含更多字段,具体取决于内核版本、配置和架构。
* 2. 此结构体在内核头文件include/linux/device.h中定义。
* 3. 设备驱动通常不直接分配device结构,而是嵌入在更大的设备特定结构中。
*/
};
sysfs 文件系统
在前面中,我们介绍了设备模型四个重要的组成部分:总线,设备,驱动,类。然而,要更深入地理解设备模型,我们需要进一步探究其代码层面的实现,在前面创建 kobj 的时候,父节点 为 NULL,会在系统根目录/sys 目录下创建?
概念
sysfs 文件系统是 Linux 内核提供的一种虚拟文件系统,用于向用户空间提供内核中设备,驱动程序和其他内核对象的信息。它以一种层次结构的方式组织数据,并将这些数据表示为文件和目录,使得用户空间可以通过文件系统接口访问和操作内核对象的属性。sysfs 提供了一种统一的接口,用于浏览和管理内核中的设备、总线、驱动程序和其他内 核对象。它在 /sys 目录下挂载,用户可以通过查看和修改 /sys 目录下的文件和目录来获取 和配置内核对象的信息。
设备模型的基本框架
为什么说 kobject 和 kset 是设备模型的基本框架呢?
当使用 kobject 时,通常不会单独使用它,而是将其嵌入到一个数据结构中。这样做的目 的是将高级对象接入到设备模型中。比如 cdev 结构体和 platform_device 结构体,如下所示:cdev 结构体如下所示,其成员kobject.
struct cdev{
struct kobject kobj;// 内嵌到cdev 中的kobject
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count ;
}
platform_device 结构体如下所示,其成员有 device 结构体,在 device 结构体中包含了
结构体。
struct platform_device{
const char *name ;
int id;
bool id_auto;
struct device dev;
u32 num_resource;
struct resource *resource;
const struct platform_device_id *id_entry;
struct mfd_cell * mfd_cell;
struct pdev_archdata arcdata;
}
device 结构体如下
struct device {
struct device *parent;//设备的父设备
struct device_prrivate *p;// 私有指针
struct kobject kobj;// 对应kobj
const char *init_name; //设备初始化名字
const struct device_type *type ;//设备类型
struct bus_type *bus;// 设备所属总线
struct device_driver *driver;
struct class *class;//设备所属的类
struct struct attribute_group **groups;// 设备的属性组
}
所以我们也可以把总线,设备,驱动看作是 kobject 的派生类。因为他们都是设备模型中 的实体,通过继承或扩展 kobject 来实现与设备模型的集成。
在 Linux 内核中,kobject 是一个通用的基础结构,用于构建设备模型。每个 kobject 实例 对应于 sys 目录下的一个目录,这个目录包含了该 kobject 相关的属性,操作和状态信息。如下图所示:

因此,可以说 kobject 是设备模型的基石,通过创建对应的目录结构和属性文件, 它提供了一个统一的接口和框架,用于管理和操作设备模型中的各个实体。
代码层面分析
在系统启动的时候会在/sys 目录下创建以下目录,如下图所示:

让我们从代码层面一层层解释一下为什么当使用 kobject_create_and_add()函数创建
kobject 时,父节点为 NULL 会在系统根目录/sys 下创建。 逐步追踪路径如下所示:
kobject_create_and_add->kobject_add->kobject_add_varg->kobject_add_internal->create_dir- >sysfs_create_dir_ns(fs/sysfs/dir.c)
接下来我们看一下 kobject_create_and_add 函数实现,如下所示:
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
struct kobject *kobj;
int retval;
kobj = kobject_create();
if (!kobj)
return NULL;
retval = kobject_add(kobj, parent, "%s", name);
if (retval) {
pr_warn("%s: kobject_add error: %d
", __func__, retval);
kobject_put(kobj);
kobj = NULL;
}
return kobj;
}
EXPORT_SYMBOL_GPL(kobject_create_and_add);
在上述代码中,首先调用了 函数来创建一个新的
kobject_create() 对象。该函数负责分配一个新的
kobject 结构体,并对其进行初始化,包括将
kobject 的
kobject 字段设置为传入的
name 参数,将
name 的
kobject 字段设置为传入的
parent 参数。
parent
接下来,代码调用了 函数将新创建的
kobject_add() 添加到设备模型中。为了进一步了解
kobject 的实现,下面我们来看一下其实现细节:
kobject_add()
/**
* kobject_add - 将kobject添加到sysfs层次结构中
* @kobj: 要添加的kobject指针
* @parent: 父kobject指针(可以为NULL)
* @fmt: 用于生成kobject名称的格式化字符串
* @...: 格式化字符串的可变参数
*
* 此函数将kobject添加到sysfs文件系统中,使其在用户空间可见。
* 函数内部使用kobject_add_varg处理可变参数。
*
* 返回值: 成功返回0,失败返回负的错误码
*/
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
{
va_list args; // 可变参数列表
int retval; // 返回值
// 检查kobj是否为NULL
if (!kobj)
return -EINVAL; // 无效参数错误
// 检查kobj是否已初始化
if (!kobj->state_initialized) {
pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.
",
kobject_name(kobj), kobj);
dump_stack(); // 打印调用栈以帮助调试
return -EINVAL; // 返回无效参数错误
}
// 开始处理可变参数
va_start(args, fmt);
// 调用kobject_add_varg进行实际添加操作
retval = kobject_add_varg(kobj, parent, fmt, args);
// 结束可变参数处理
va_end(args);
// 返回操作结果
return retval;
}
EXPORT_SYMBOL(kobject_add); // 导出函数供内核其他模块使用
在上述函数中,函数调用 kobject_add_varg(),该函数会根据传入的参数将 kobj 添加到设备 模型中。kobject_add_varg()函数的实现可能会根据具体情况进行一些额外的处理,例如创建对 应的目录并设置父节点等。我们进一步探究下 kobject_add_varg()函数的实现,如下所示:
/**
* kobject_add_varg - 将kobject添加到sysfs层次结构中(可变参数版本)
* @kobj: 要添加的kobject指针
* @parent: 父kobject指针
* @fmt: 用于生成kobject名称的格式化字符串
* @vargs: 格式化字符串的可变参数列表
*
* 此函数是kobject_add的内部实现,处理可变参数列表。
* 它首先设置kobject的名称,然后将其添加到sysfs层次结构中。
*
* 返回值: 成功返回0,失败返回负的错误码
*/
static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval; // 存储函数返回值
// 步骤1: 设置kobject的名称
retval = kobject_set_name_vargs(kobj, fmt, vargs);
// 如果设置名称失败,打印错误信息并返回错误码
if (retval) {
pr_err("kobject: can not set name properly!
");
return retval;
}
// 步骤2: 设置kobject的父对象
kobj->parent = parent;
// 步骤3: 调用内部函数实际添加kobject到sysfs
return kobject_add_internal(kobj);
}
在上述函数中,kobject_add_varg()函数用于将指定的 kobject 添加到设备模型中。它通过调 用 kobject_set_name_vargs()设置 kobject 的名称,并将父节点赋值给 kobject 的 parent 字段。然 后,它调用 kobject_add_internal()函数执行实际的添加操作。接下来我们进一步探究下 kobject_add_internal()函数的实现,如下所示:
/**
* kobject_add_internal - 将kobject添加到sysfs层次结构中的内部实现
* @kobj: 要添加的kobject指针
*
* 此函数是kobject添加操作的核心实现,负责:
* 1. 参数验证
* 2. 父对象引用管理
* 3. kset关联处理
* 4. sysfs目录创建
*
* 返回值: 成功返回0,失败返回负的错误码
*/
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0; // 错误码,初始化为0
struct kobject *parent; // 父kobject指针
// 步骤1: 检查kobj是否为空
if (!kobj)
return -ENOENT; // 没有这样的文件或目录
// 步骤2: 检查kobj名称是否有效
if (!kobj->name || !kobj->name[0]) {
// 使用WARN宏打印警告信息,包含调试信息
WARN(1,
"kobject: (%p): attempted to be registered with empty name!
",
kobj);
return -EINVAL; // 无效参数
}
// 步骤3: 获取父对象的引用,增加其引用计数
parent = kobject_get(kobj->parent);
/*
* 步骤4: 处理kset关联
* 如果kobj属于某个kset,则加入该kset,
* 并且如果还没有父对象,则使用kset作为父对象
*/
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj); // 将kobj加入到kset中
kobj->parent = parent; // 更新父指针
}
/*
* 步骤5: 打印调试信息
* 显示kobject、父对象和kset的名称,便于调试
*/
pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'
",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
// 步骤6: 在sysfs中创建对应的目录
error = create_dir(kobj);
// 步骤7: 处理创建目录失败的情况
if (error) {
kobj_kset_leave(kobj); // 从kset中移除kobj
kobject_put(parent); // 减少父对象的引用计数
kobj->parent = NULL; // 清空父指针
/* 对于特定的错误码,打印更详细的错误信息 */
if (error == -EEXIST)
pr_err("%s failed for %s with -EEXIST, "
"don't try to register things with the same name "
"in the same directory.
",
__func__, kobject_name(kobj));
}
// 步骤8: 返回操作结果
return error;
}
kobject_add_internal()函数用于在设备模型中添加指定的 kobject。它会检查 kobject 的有效 性和名称是否为空,并处理 kobject 所属的 kset 相关的操作。然后,它会创建 kobject 在 sysfs 中的目录,并处理创建失败的情况。最后,它会设置 kobject 的相关状态,并返回相应的错误。 我们继续追究creat_dir()函数的实现,如下所示:
static int struct kobject *kobj)
{
const struct kobj_ns_type_operations *ops;
int error;
error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
if (error) return error;
error = populate_dir(kobj);
if (error) {
sysfs_remove_dir(kobj);
return error;
}
/*
* may be deleted by an ancestor going away. * extra so that it stays until @kobj is gone.
*/
sysfs_get(kobj->sd);
ops = kobj_child_ns_ops(kobj);
if (ops)
{
BUG_ON(ops->type <=KOBJ_NS_TYPE_NONE);
BUG_ON(ops->type >=KOBJ_NS_TYPES);
BUG_ON(!kobj_ns_type_registered(op->type));
sysfs_enable_ns(kobj->sd);
}
return 0;
}
create_dir()函数用于创建与给定 kobject 相关联的目录,并填充该目录。它还处理了引用计、命名空间操作等相关的逻辑.如果创建目录和填充目录的是发生错误函数会进行相应的函数处理以及返回错误的代码。函数调用sysfs_creat_dir_ns()函数来创建于kobj相关的目录kobj实现如下所示:
/**
* sysfs_create_dir_ns - 在sysfs中为kobject创建目录(命名空间感知)
* @kobj: 要创建目录的kobject指针
* @ns: 可选的命名空间指针
*
* 此函数在sysfs文件系统中为指定的kobject创建一个目录。
* 它是sysfs层次结构构建的核心函数之一。
*
* 返回值: 成功返回0,失败返回负的错误码
*/
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
struct kernfs_node *parent, *kn; // 父节点和新建节点指针
kuid_t uid; // 用户ID
kgid_t gid; // 组ID
// 检查kobj是否有效,如果为NULL则触发内核BUG
BUG_ON(!kobj);
// 确定父目录节点
if (kobj->parent)
parent = kobj->parent->sd; // 使用kobj父对象的sysfs目录节点
else
parent = sysfs_root_kn; // 否则使用sysfs根节点
// 如果父节点不存在,返回错误
if (!parent)
return -ENOENT; // 没有这样的文件或目录
// 获取kobject的所有权信息(用户ID和组ID)
kobject_get_ownership(kobj, &uid, &gid);
// 在父目录下创建新的目录节点
kn = kernfs_create_dir_ns(parent, // 父节点
kobject_name(kobj), // kobject名称
S_IRWXU | S_IRUGO | S_IXUGO, // 权限:所有者RWX,组R,其他RX
uid, gid, // 用户ID和组ID
kobj, // 关联的kobject
ns); // 命名空间
// 检查创建是否成功
if (IS_ERR(kn)) {
// 如果是目录已存在的错误,发出警告
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(parent, kobject_name(kobj));
// 返回错误码
return PTR_ERR(kn);
}
// 将创建的kernfs节点关联到kobject
kobj->sd = kn;
// 成功返回
return 0;
}
在上面的函数中,当没有父节点的时候,父节点被赋值成了 sysfs_root_kn,即/sys 目录根 目录的节点。如果有 parent,则它的父节点为 kobj->parent->sd,然后调用 kernfs_create_dir_ns 创建目录。那么 sysfs_root_kn 是在什么时候创建的呢?我们找到 fs/sysfs/mount.c 文件,如下 所示:
int __init sysfs_init (void)
{
int err;
sysfs_root = kernfs_create_root(NULL, KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK, NULL);
if (IS_ERR(sysfs_root) return PTR_ERR(sysfs_root);
sysfs_root_kn = sysfs_root->kn;
err=register_filesystem(&sysfs_fs_type);
if (err) {
kernfs_destory_root(sysfs_root);
return err;
}
return 0;
}
通过上述对 API 函数的分析,我们可以总结出创建目录的规律,如下所示:
1、无父目录、无 kset,则将在 sysfs 的根目录(即/sys/)下创建目录。
2、无父目录、有 kset,则将在 kset 下创建目录,并将 kobj 加入 kset.list。
3、有父目录、无 kset,则将在 parent 下创建目录。
4、有父目录、有 kset,则将在 parent 下创建目录,并将 kobj 加入 kset.list。





