如何将文件操作附加到平台驱动程序中的 sysfs 属性?

How to attach file operations to sysfs attribute in platform driver?

我为我们开发的外围设备编写了一个平台驱动程序,并希望向 sysfs 公开一些配置选项。我已经设法在探测函数中使用属性结构(见下文)和 sysfs_create_file 创建了适当的文件,但我无法弄清楚如何将 show/store 函数附加到平台驱动程序中的结构.

我在网上找到的大多数资源都使用 device_attribute 结构或类似的东西来创建它们的文件,这在这里也合适吗?对于平台驱动程序,还有其他方法可以做到这一点吗?

我的属性结构如下所示:

struct attribute subkey_attr = {
    .name = "subkeys",
    .mode = S_IWUGO | S_IRUGO,
};

然后我使用此调用注册文件:

riddler_kobject = &pdev->dev.kobj;
ret_val = sysfs_create_file(riddler_kobject, &subkey_attr);

归结为下一个:

  • 重新使用来自 struct device(来自您的 struct platform_device)的现有 kobject 用于 sysfs_create_group()(而不是创建您自己的 kobject
  • 使用 DEVICE_ATTR() 声明 struct device_attribute 而不是常规的 __ATTR(),这会创建 struct kobj_attribute.

下面是我如何为我的平台驱动程序创建 sysfs 属性。

  1. 为您的 sysfs 属性(文件)创建您将在 show() / store() 操作中用作私有数据的结构。例如:

    struct mydrv {
        struct device *dev;
        long myparam;
    };
    
  2. 在你的驱动程序中分配这个结构 probe():

    static int mydrv_probe(struct platform_device *pdev)
    {
        struct mydrv *mydrv;
    
        mydrv = devm_kzalloc(&pdev->dev, sizeof(*mydrv), GFP_KERNEL);
        mydrv->dev = &pdev->dev;
        platform_set_drvdata(pdev, mydrv);
    
        ...
    }
    
  3. 创建 show() / store() 函数:

    static ssize_t mydrv_myparam_show(struct device *dev,
            struct device_attribute *attr, char *buf)
    {
        struct mydrv *mydrv = dev_get_drvdata(dev);
        int len;
    
        len = sprintf(buf, "%d\n", mydrv->myparam);
        if (len <= 0)
            dev_err(dev, "mydrv: Invalid sprintf len: %d\n", len);
    
        return len;
    }
    
    static ssize_t mydrv_myparam_store(struct device *dev,
            struct device_attribute *attr, const char *buf, size_t count)
    {
        struct mydrv *mydrv = dev_get_drvdata(dev);
    
        kstrtol(buf, 10, &mydrv->myparam);
        return count;
    }
    
  4. 为这些函数创建设备属性(在这些函数之后):

    static DEVICE_ATTR(myparam, S_IRUGO | S_IWUSR, mydrv_myparam_show,
                       mydrv_myparam_store);
    
  5. 声明属性table(实际上为您的驱动程序列出 sysfs 文件):

    static struct attribute *mydrv_attrs[] = {
        &dev_attr_myparam.attr,
        NULL
    };
    
  6. 声明属性组(实际上为您的驱动程序指定 sysfs 目录):

    static struct attribute_group mydrv_group = {
        .name = "mydrv",
        .attrs = mydrv_attrs,
    };
    
    static struct attribute_group *mydrv_groups[] = {
        &mydrv_group,
        NULL
    }
    

    实际上可以用一行代替:

    ATTRIBUTE_GROUPS(mydrv);
    
  7. 在驱动程序的 probe() 函数中创建 sysfs 目录和文件:

    static int mydrv_probe(struct platform_device *pdev)
    {
        int ret;
    
        ...
    
        ret = sysfs_create_group(&pdev->dev.kobj, &mydrv_group);
        if (ret) {
            dev_err(&pdev->dev, "sysfs creation failed\n");
            return ret;
        }
    
        ...
    }
    
  8. 在驱动程序的 remove() 函数中删除您的 sysfs 文件:

    static int mydrv_remove(struct platform_device *pdev)
    {
        sysfs_remove_group(&pdev->dev.kobj, &mydrv_group);
        ...
    }
    

竞争条件注释

正如@FranzForstmayr 正确指出的那样,在 mydrv_probe() 中添加具有 sysfs_create_group() 的 sysfs 文件时,可能存在 竞争条件 。这是因为 user-space 可以在调用 mydrv_probe() 之前就已经知道这些文件存在(这些文件实际上是由 sysfs_create_group() 函数创建的)。 Greg Kroah-Hartman 的 "How to Create a sysfs File Correctly" 文章详细介绍了这个问题。

因此在我们的 platform_device 案例中,您可以使用 默认属性组 [=133=,而不是调用 sysfs_create_group()(及其对应的 sysfs_remove_group()) ].为此,您需要将 struct device 的相应 .groups 字段分配给属性组变量:

static int mydrv_probe(struct platform_device *pdev)
{
    ...

    pdev->dev.groups = mydrv_groups;

    ...
}

免责声明:我没有测试这段代码,虽然它应该可以工作,因为 this 代码。

请参阅 [1,2,3] 链接以获取有关上述竞争条件的更多见解。

更多示例,运行内核源目录中的下一个命令:

$ git grep -l --all-match -e platform_device -e attribute -e '\.groups =' -- drivers/

您也可以在提交消息中通过 "default attribute" 搜索:

$ git log --no-merges --oneline --grep="default attribute" -- drivers/

我通过这种方式找到的一些提交:[4,5,6,7]。

参考资料

[1]

[2] PATCH: sysfs: add devm_sysfs_create_group() and friends

[3] [GIT PATCH] Driver core patches for 3.11-rc2

[4]commit 1

[5]commit 2

[6]commit 3

[7]commit 4

没有足够的声誉 post 发表评论,但我只想评论接受的答案中的 默认属性组 注释。 我的理解是,这不应该添加到探测函数中,如示例中所示,而是应该在设备结构中设置(或 device_driver、class 或总线,具体取决于您的驱动程序) 通常在文件末尾定义。 例如:

static struct device iio_evgen_dev = {
    .bus = &iio_bus_type,
    .groups = iio_evgen_groups,
    .release = &iio_evgen_release,
};

来自 this 示例

奇怪的是,根据 this 使用 DEVICE_INT_ATTR 创建属性时无法正常工作,所以不确定这是怎么回事。

此外,我不是 100% 确定,但我认为 this 是在加载驱动程序时调用的,而不是在探测设备时调用的。

这是对 Sam Protsenko 和 Anthony 的回答的补充

如果您通过 DEVICE_ATTR 宏创建 设备 属性,则必须在 .dev_groups 中注册属性组 (mydrv_groups)而不是 .groups 字段。

static struct device iio_evgen_dev = {
    .bus = &iio_bus_type,
    .dev_groups = iio_evgen_groups, // .dev_groups for DEVICE_ATTR
    .groups = another_attr_group,   // .groups for DRIVER_ATTR
    .release = &iio_evgen_release,
};

然后属性会自动正确注册,而无需在 probe/remove 函数中设置任何内容,如 Greg Kroah-Hartman 的文章所述。

假设模块已经加载到内核中并且驱动程序注册在

/sys/bus/platform/drivers/mydrv

每个 设备 实例都将是驱动程序文件夹的子目录,如

/sys/bus/platform/drivers/mydrv/mydrv1
/sys/bus/platform/drivers/mydrv/mydrv2

.groups 字段中注册属性会在 driver 文件夹中创建属性。 在 .dev_groups 字段中注册属性会在 设备 的实例文件夹中创建属性。

mydrv
├── driver_attr1
├── driver_attr2
└── mydrv1
    ├── device_attr1
    └── device_attr2

.groups 字段中属性的 show/store 函数无法通过 platform_set_drvdata(pdev, mydrv) 访问驱动程序数据集。 至少不是通过 dev_get_drvdata(dev) 访问它。 通过 dev_get_drvdata(dev) returns NULL 访问驱动程序数据并取消引用它会导致内核 oops。

反过来,.dev_groups 字段中属性的 show/store 函数可以通过

访问驱动程序数据
struct mydrv *mydrv = dev_get_drvdata(dev);