Skip to content

LV050-设备树插件驱动分析 ​

设备树插件也是用的 configfs,来了解一下?若笔记中有错误或者不合适的地方,欢迎批评指正 😃。

一、源码分析 ​

1. dtbocfg_module_init() ​

先来看模块的初始化函数 dtbocfg_module_init():

c
/**
 * dtbocfg_module_init()
 */
static int __init dtbocfg_module_init(void)
{
    int retval = 0;

    pr_info(DRIVER_NAME ": " DRIVER_VERSION "\n");

    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\n", __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\n", __func__);
        goto register_group_failed;
    }

    pr_info(DRIVER_NAME ": OK\n");
    return 0;

  register_group_failed:
    configfs_unregister_subsystem(&dtbocfg_root_subsys);
  register_subsystem_failed:
    return retval;
}

可以看到,这里和前面我们的 demo 一样,创建了一个子系统和一个配置项组。

1.1 子系统相关结构 ​

子系统 configfs_subsystem 相关的变量如下:

c
/**
 * 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),
};

configfs_register_subsystem(&dtbocfg_root_subsys);

1.2 配置项组相关结构 ​

我们看一下配置项组的相关结构:

c
/**
 * 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;

config_group_init(&dtbocfg_root_subsys.su_group);
config_group_init_type_name(&dtbocfg_overlay_group, "overlays", &dtbocfg_overlays_type);

configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group);

在 dtbocfg_overlays_type 中实现了 ct_group_ops 下的 make_item 和 drop_item。

2. make_item ​

我们在配置项组中执行 mkdir 创建一个配置项的时候,会调用 dtbocfg_overlay_group_make_item()函数:

c
/**
 * 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\n", __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_item,然后用 dtbocfg_overlay_item_type 去初始化这个 item。

2.1 dtbocfg_overlay_item_type ​

dtbocfg_overlay_item_type 定义如下:

c
/**
 * 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_item_type 中实现了 attrs 和 bin_attrs 和 ct_item_ops

2.2 attrs ​

这里定义了两个属性:

c
/**
 * 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,
};

每个属性都会有自己的操作函数。

2.2.1 dtbocfg_overlay_item_attr_dtbo ​
c
/**
 * 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;
}
2.2.2 dtbocfg_overlay_item_attr_status ​
c
/**
 * 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\n", overlay->id >= 0 ? 1 : 0);
}

3. status 属性的操作 ​

我们向 status 写 1 的时候就可以开启设备树插件,我们来看一下怎么实现的,主要是看下面的函数:

c
/**
 * 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;
}

当我们向 status 写入 1 的时候, 会执行 dtbocfg_overlay_item_create() 函数。 在这个函数中又去执行了 of_overlay_fdt_apply() 函数。 of_overlay_fdt_apply() 函数如下图

image-20250314153133971

设备树插件(dtbo) 里面的节点也要被转换成 device_node, 有的 device_node 也要被转换成 platform_device。 不过在进行转换之前, of_overlay_fdt_apply 函数会先创建一个改变集。 然后根据这个改变集去进行修改。

创建改变集的目的是为了方便对设备树进行修改和复原。 设备树是一种静态的数据结构,一旦被编译和加载到内核中, 就难以直接修改。 为了解决这个问题, 设备树覆盖功能引入了改变集的概念。

二、改变集简介 ​

改变集是一个描述设备树变化的数据结构, 它记录了对设备树的修改操作, 如添加、 删除或修改节点。 通过创建改变集, 我们可以在运行时对设备树进行动态修改, 而无需修改原始的设备树源文件。

通过创建改变集, 我们可以方便地定义需要进行的修改操作, 而不必直接操作设备树的底层结构。 这提供了一种高层次的抽象, 使我们能够以更简洁和可读的方式描述设备树的变化。同时, 改变集也可以被保存、 传递和应用到其他设备树上, 方便在不同系统或环境中进行设备树的配置和定制。

此外, 改变集还可以用于设备树的复原。 在某些情况下, 我们可能需要在运行时撤销对设备树的修改并恢复到原始状态。 通过应用反向的改变集, 我们可以还原设备树, 使其回到修改之前的状态, 实现修改的复原。

因此, 创建改变集提供了一种方便、 可控和可复原的方式来修改设备树, 使设备树的管理和配置更加灵活和可靠。