概述
在 Linux 驱动开发中,device_create_file()
是用于为设备创建单个属性文件的函数。如果你需要为一个设备创建多个属性文件(即多个 sysfs
属性),可以通过多次调用 device_create_file()
来实现。
不过,为了提高代码的可读性和维护性,Linux 内核提供了一种更高效的方式——使用 sysfs_create_group()
机制来批量定义和注册多个属性文件。
设备树示例
包含兼容属性,pinctrl的名字和配置,gpio属性
eta: eta {
compatible = "ww,eta4056";
pinctrl-names = "default";
pinctrl-0 = <&eta_pmx_func>;
stat-gpio = <49>;
en-gpio = <50>;
status = "okay";
};
代码示例
包括对pinctrl的获取和设置;gpio的读取;gpio的输入输出配置;attr的配置
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/device.h>
#include <linux/sysfs.h>
static int status_gpio;
static int en_gpio;
ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int value = gpio_get_value(status_gpio);
return sprintf(buf, "%d\n", value);
}
ssize_t en_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
gpio_set_value(en_gpio, buf[0]-'0');
return count;
}
ssize_t volt_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "TODO\n");
}
ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "TODO\n");
}
static DEVICE_ATTR_RO(status);
static DEVICE_ATTR_WO(en);
static DEVICE_ATTR_RO(volt);
static DEVICE_ATTR_RO(temp);
static struct attribute *arry_attrs[] = {
&dev_attr_status.attr,
&dev_attr_en.attr,
&dev_attr_volt.attr,
&dev_attr_temp.attr,
NULL,
};
static struct attribute_group attr_group = {
.attrs = arry_attrs,
};
static int eta4056_probe(struct platform_device *pdev) {
struct pinctrl *pinctrl;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
printk("eta4056_init\n");
pinctrl = devm_pinctrl_get_select_default(dev);
if (IS_ERR(pinctrl))
dev_warn(dev, "pins are not configured\n");
of_property_read_u32(node, "stat-gpio", &status_gpio );
if (status_gpio < 0)
return -ENODEV;
gpio_request_one(status_gpio, GPIOF_DIR_IN, "eta_status");
of_property_read_u32(node, "en-gpio", &en_gpio );
if (en_gpio < 0)
return -ENODEV;
gpio_request_one(en_gpio, GPIOF_OUT_INIT_HIGH, "eta_en");
if (sysfs_create_group(&pdev->dev.kobj, &attr_group)) {
pr_err("Failed to create attribute group\n");
return -ENODEV;
}
return 0;
}
static int eta4056_exit(struct platform_device *pdev) {
printk("eta4056_exit\n");
return 0 ;
}
static const struct of_device_id eta_ids[] = {
{ .compatible = "ww,eta4056", .data = NULL },
{},
};
static struct platform_driver eta4056_driver = {
.probe = eta4056_probe,
.remove = eta4056_exit,
.driver = {
.name = "eta4056",
.of_match_table = eta_ids,
},
};
static int __init eta4056_init(void)
{
return platform_driver_probe(&eta4056_driver, eta4056_probe);
}
device_initcall_sync(eta4056_init);
MODULE_DESCRIPTION("eta4056 Driver");
MODULE_LICENSE("GPL v2");
最终呈现如下
代码解析
sysfs_create_group调用internal_create_group来完成多个属性文件的创建
int sysfs_create_group(struct kobject *kobj,
const struct attribute_group *grp)
{
return internal_create_group(kobj, 0, grp);
}
EXPORT_SYMBOL_GPL(sysfs_create_group);
internal_create_group 增加 kernfs_node
的引用计数,确保在后续操作中不会被意外释放,create_files()
是核心函数,负责根据 grp->attrs
和 grp->bin_attrs
创建对应的 sysfs
文件
static int internal_create_group(struct kobject *kobj, int update,
const struct attribute_group *grp)
{
struct kernfs_node *kn;
kuid_t uid;
kgid_t gid;
int error;
if (WARN_ON(!kobj || (!update && !kobj->sd)))
return -EINVAL;
/* Updates may happen before the object has been instantiated */
if (unlikely(update && !kobj->sd))
return -EINVAL;
if (!grp->attrs && !grp->bin_attrs) {
WARN(1, "sysfs: (bin_)attrs not set by subsystem for group: %s/%s\n",
kobj->name, grp->name ?: "");
return -EINVAL;
}
kobject_get_ownership(kobj, &uid, &gid);
if (grp->name) {
if (update) {
kn = kernfs_find_and_get(kobj->sd, grp->name);
if (!kn) {
pr_warn("Can't update unknown attr grp name: %s/%s\n",
kobj->name, grp->name);
return -EINVAL;
}
} else {
kn = kernfs_create_dir_ns(kobj->sd, grp->name,
S_IRWXU | S_IRUGO | S_IXUGO,
uid, gid, kobj, NULL);
if (IS_ERR(kn)) {
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(kobj->sd, grp->name);
return PTR_ERR(kn);
}
}
} else
kn = kobj->sd;
kernfs_get(kn);
error = create_files(kn, kobj, uid, gid, grp, update);
if (error) {
if (grp->name)
kernfs_remove(kn);
}
kernfs_put(kn);
if (grp->name && update)
kernfs_put(kn);
return error;
}