如何在驱动中添加kobject以及在其下添加单个或多个文件(sysfs file)
How to add a kojbect and sysfs file in Linux driver
驱动程序常常需要和
用户有一定的交互,
或者说用户常常要去通过某些接口去查看当前设备的某些信息,或者对驱动做一些配置,
分别对应着程序里面的
read和write操作。
Linux
内核提供了很多方式,除了常见的proc文件
系统之外,还有个sysfs。
其中每个sysfs中的文件夹都对应着内核中的一个kobject。
每个sysfs文件下面,可能会有单个或多个文件,供用户read或write,
其一般通过cat sysfs_file_name和echo XXX > sysfs_file_name实现。
下面将要介绍的就是,向一个驱动中添加一个kobject和在其下添加单个或多个sysfs file的大概套路。
由于水平有限,难免有误,看到问题的还请指教:green-waste(At)163.com
1.在Probe函数中添加函数去添加kobject
- static int easypoint_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
- {
- ...
- err = kobject_init_and_add?&epdata->kobj, &ktype_easypoint, &idev->dev.kobj,
- "easypoint");
- ...
- }
复制代码 说明:
(1)easypoint_probe是我的某个驱动的probe函数。
(2)kobject_init_and_add的原型为:
- int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
- struct kobject *parent, const char *fmt, ...);
复制代码 此处只需要调用一次kobject_init_and_add,
即可实现添加kobj以及创建对应的sysfs中的文件夹
及其由传入的ktype中的default_attrs决定的那些属性文件(sysfs file)。
(3)传入的&idev->dev.kobj是表示parent object,如果此处没有,那么也可以直接写成NULL,我这里是有parent的obj的,
即前面已经有的一个input device的obj。
2.实现对应的ktype和ktype中的操作接口sysfs_ops下面接着说关于参数中的ktype对应的变量以及其他相关信息:
- static ssize_t show(struct kobject *kobj, struct attribute *attr ,char *buf)
- {
- struct easypoint_data *epdata = to_epdata(kobj);
- struct easypoint_attr *ep_attr = to_attr(attr);
- ssize_t ret = -EINVAL;
- if (ep_attr->show)
- ret = ep_attr->show(epdata, buf);
- else
- ret = -EIO;
- return ret;
- }
- static ssize_t store(struct kobject *kobj, struct attribute *attr,
- const char *buf, size_t count)
- {
- struct easypoint_data *epdata = to_epdata(kobj);
- struct easypoint_attr *ep_attr = to_attr(attr);
- ssize_t ret = -EINVAL;
- if (ep_attr->store)
- ret = ep_attr->store(epdata, buf, count);
- else
- ret = -EIO;
- return ret;
- }
- static struct attribute *ep_default_attrs[] = {
- &easypoint_calibrate.attr,
- NULL
- };
- static void easypoint_sysfs_release(struct kobject *kobj)
- {
- }
- static struct sysfs_ops ep_sysfs_ops = {
- .show = show,
- .store = store,
- };
- static struct kobj_type ktype_easypoint = {
- .sysfs_ops = &ep_sysfs_ops,
- .default_attrs = ep_default_attrs,
- .release = easypoint_sysfs_release,
- };
复制代码 说明:
(1)
- struct kobj_type {
- void (*release)(struct kobject *kobj);
- struct sysfs_ops *sysfs_ops;
- struct attribute **default_attrs;
- };
复制代码 中的release函数,此处好像不需要做什么事情,所以留空。
(2)sysfs_ops的定义:
- struct sysfs_ops {
- ssize_t (*show)(struct kobject *, struct attribute *,char *);
- ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
- };
复制代码 所以,上面定义了一个ep_sysfs_ops,然后挂载了一个show和store,其函数意思很明显,
就是相当于对此文件操作的read和write时候,分别调用show和store函数。
(3)其中的show和store函数中,也没什么特殊的,只是调用对应的attr的show和store函数。
(4)其中show和store函数中的to_epdata和to_attr分别是:
- #define to_epdata(k) container_of(k,struct easypoint_data, kobj)
- #define to_attr(a) container_of(a,struct easypoint_attr, attr)
复制代码 即利用常用的container_of,来实现通过结构体成员kobj来得到我们自己的
数据结构指针。
3.定义对应的单个或多个sysfs file对应的attr下面说说关于上面的那个default_attrs对应的ep_default_attrs:
- static struct attribute *ep_default_attrs[] = {
- &easypoint_calibrate.attr,
- NULL
- };
复制代码 其实很简单,就是一个attr的指针数组,一个个挂到default_attr中。
如果要实现多个sysfs的file,即多个attr,即类似于
&easypoint_calibrate.attr,
一样,去加上别的即可,型如:
&easypoint_calibrate.attr,
&XXXXXX.attr,
其中easypoint_calibrate相关内容,见如下定义的:
- struct easypoint_attr {
- struct attribute attr;
- ssize_t (*show)(struct easypoint_data *, char *);
- ssize_t (*store)(struct easypoint_data *, const char *, size_t count);
- };
- #define define_one_rw(_name) \
- static struct easypoint_attr _name = \
- __ATTR(_name, 0644, show_##_name, store_##_name)
- define_one_rw(easypoint_calibrate);
复制代码 去用define_one_rw定义了一个读写的attr,其中__ATTR是sysfs.h中提供的一个宏方便我们初始化attr的变量:
- /**
- * Use these macros to make defining attributes easier. See include/linux/device.h
- * for examples..
- */
- #define __ATTR(_name,_mode,_show,_store) { \
- .attr = {.name = __stringify(_name), .mode = _mode }, \
- .show = _show, \
- .store = _store, \
- }
复制代码 说明:
(1)上面的
- #define define_one_rw(_name) \
- static struct easypoint_attr _name = \
- __ATTR(_name, 0644, show_##_name, store_##_name)
- define_one_rw(easypoint_calibrate);
复制代码 可以用
- static struct easypoint_attr easypoint_calibrate = \
- __ATTR(_name, 0644, show_easypoint_calibrate, store_easypoint_calibrate)
复制代码 来代替,其中##可以用于字符串连接。
(2)__ATTR中__stringify,用于将输入内容变成字符串,否则单独的宏定义,比如你传入的easypoint_calibrate,会无法被识别的。
据我的理解,如果想要定义一个attr,即一个sysfs中一个file,其套路一般是:
先定义一个自己的attr相关的结构体变量,比如上面的struct easypoint_attr,
其中的show和store中的第一个参数(此处的struct easypoint_data *)都是自己的那个数据结构体指针,后面的参数都是一样的。
然后再去定义一个这样的变量,此处的是static struct easypoint_attr _name,这样,
就定义了对应的一个attr,然后再去实现上面的show和store函数,此处即为show_easypoint_calibrate和store_easypoint_calibrate。
4.实现对应的那些attr的show和store函数,实现你所需要的功能下面针对上面说明,给出我此处的实现例子:
- /**
- * show_easypoint_calibrate - show calibrate info
- */
- static ssize_t show_easypoint_calibrate(struct easypoint_data *epdata,
- char *buf)
- {
- int ret = 0;
- ret = sprintf(buf, "Calibrated:\t%3s\n", epdata->calibrated ? "Yes" : "No");
- ret += sprintf(buf + ret, "Calibrating:\t%3s\n", epdata->calibrating ? "Yes" : "No");
- ret += sprintf(buf + ret, "xn:%2d,xp:%2d,yn:%2d,yp:%2d\n",
- epdata->xn_max_allow,
- epdata->xp_max_allow,
- epdata->yn_max_allow,
- epdata->yp_max_allow);
- return ret;
- }
复制代码 说明:
show函数中的,一般都是打印一些信息,注意第二行的
- ret += sprintf(buf + ret, "Calibrating:\t%3s\n", epdata->calibrating ? "Yes" : "No");
复制代码 中的buf + ret表示接着上面一行接着输出。
其中sprintf返回值为已经格式化打印输出的字符数。
- /**
- * store_easypoint_calibrate - process the calibrate command and do action
- */
- static ssize_t store_easypoint_calibrate(struct easypoint_data *epdata,
- const char *buf, size_t count)
- {
- unsigned int ret = -EINVAL;
- char str_cmd[10];
- int x_sum, y_sum;
- char x_offset, y_offset;
- int retry_time, i;
- ret = sscanf (buf, "%10s", str_cmd);
- if (ret != 1)
- return -EINVAL;
- if (0 == strnicmp(str_cmd, "start", 10)) {
- /* start calibrate */
- ...
- }
- else if (0 == strnicmp(str_cmd, "stop", 10)) {
- /* stop calibrate */
- ...
- }
- else
- return -EINVAL;
- return count;
- fail:
- return ret;
- }
复制代码 说明:
(1)store函数一般都是接受外界输入,然后对判断输入的内容去执行对应的操作。
比如此处的这里运行的输入为start和stop,分别去做对应的事情。
(2)另外要说明的是,对于sysfs中的文件,如果要其显示内容,即执行show函数,要用cat
命令,比如此处的:
- # cat /sys/class/input/input2/easypoint/easypoint_calibrate
- Calibrated: No
- Calibrating: No
- xn:26,xp:32,yn:29,yp:30
复制代码 而对于输入内容,让store函数执行,要用到echo,而不是cat,比如:
- # cd /sys/class/input/input2/easypoint/
- # ls
- easypoint_calibrate
- # echo start > easypoint_calibrate
- ....
- # echo stop > easypoint_calibrate
- ...
复制代码