好文:http://www.ibm.com/developerworks/cn/linux/l-cn-sysfs/
先来看下怎么用sysfs,再去分析原理。以2.6.30内核中led的使用为例,因为我常用的3.2.0感觉不如这个版本清楚
涉及到文件 class.c led-class.c leds-s3c24xx.c
先来看用户空间怎么控制Led灯
点灯:echo 1 > /sys/class/leds/led0/brightness
灭灯:echo 0 > /sys/class/leds/led0/brightness
下面我们就来看看sysfs是如何一步步产生了这些操作结点的
/sys目录我们先略过,这肯定是sysfs核心产生的。我们分析sys里边的内容如何产生,
过程中凡是涉及到sysfs核心的东西都先略过,后期再分析。
先来看一下sys目录:# ls /sys -F 加了-F选项会显示出文件类型
block/ class/ devices/ fs/ module/
bus/ dev/ firmware/ kernel/ power/
对于各个目录的作用,在《深入理解linux内核》设备驱动模型一节和上面网址中都有介绍。
现在按照上面的例子,先来看看class目录的产生。
1.在…/driver/base/class.c中。
classes_init(void)
class_kset = kset_create_and_add("class", NULL, NULL);
看一下kset_create_and_add的原型:
static struct kset *kset_create(const char *name, struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj)
第一个是名字,第二个略过不说,第三个是父类容器
从形参看,第三个参数是NULL,所以class是没有父类容器的,所以class目录会出现在/sys的底层目录
好了,/sys/class出现了,我们不再往下深入,只是理清大体流程。
2.那么如何在class目下下产生其它节点内?
熟悉Linux分层设计思想的肯定一下就能想到,既然是在class目录下产生节点,肯定是由class核心来统一管理了。
没错,就是这样,class提供了一个注册函数__class_create供外部使用,这个函数又被device.h重新封装为class_create
class_create这个函数相信只要写过字符设备的人都很熟悉,在调用device_create之前通常会先调用class_create产生类。
好了,接下来我们就看看内核自带的Led驱动是怎么做的。
leds_init(void)
leds_class = class_create(THIS_MODULE, "leds"); //这个调用,就在/sys/class下产生了leds目录
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
这里要多提一点,如果我们平时写个led驱动会怎么做呢?通常大部分人会直接在同一个文件中调用
class_create和device_create,或者注册成为misc设备。这样并没有什么不妥,但是内核的做法更
符合软件分层设计思想。把led共性抽出来,注册为一个leds类,然后由led_class.c统一管理。
然后所有的平台相关的设备再通过led_class.c提供的接口把具体的设备注册到leds目录里边。
是不是一层层的非常清楚明了。这就是内核魅力所在。到这里,就产生了/sys/class/leds目录。
led-class.c就是整个led子系统的核心了,下面我们就从led_classdev_register来分析,
详细看下led类是如何利用sysfs实现相应功能的。
led_classdev_register(struct device *parent, struct led_classdev *led_cdev) //...drivers/leds/led-class.c
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name);
device_create_file(led_cdev->dev, &dev_attr_brightness);
device_create_file(led_cdev->dev, &dev_attr_max_brightness);
device_create_file(led_cdev->dev, &dev_attr_trigger);
形参有两个,一个是parent,一个是led_classdev结构体指针
parent通常指向platform平台设备的私有数据,这个可以先不用管,后边用处也不大
第二个参数就是Led核心提供的led设备抽象,可以参考下文来了解
http://blog.csdn.net/yuanlulu/article/details/6438841
然后就是我们熟悉的device_create函数了,都知道是在/dev目录下生成相应的设备结点。
而且会让led_cdev->dev和led核心维护的leds_class建立联系,为后边的device_create_file做准备。
接下来就是最重要的三个device_create_file调用,这个函数是sysfs核心提供的,通过调用它,
就可以生成设备的属性,也就是在用户空间操作的属性。
比如上面的device_create_file(led_cdev->dev, &dev_attr_brightness)调用
就会在/sys/class/leds/led0目录下产生brightness属性,而我们用
echo 1 > /sys/class/leds/led0/brightness命令即可实现点灯
cat /sys/class/leds/led0/brightness就可以查看灯的状态
sysfs具体做了什么暂时不分析,但是大体能够猜到,调用device_create_file函数就会导致
sysfs核心为这个属性建立相应的目录,并且绑定相应的读写方法。
那么对于led的操作方法是什么呢?在led-class.c中搜dev_attr_brightness你会发现是搜不到的,
这是因为它被定义成了一个宏函数DEVICE_ATTR,这也是sysfs核心提供的。代码中有这么一句:
static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store);
把这个宏展开就是:
static struct device_attribute dev_attr_brightness = { \
.attr = {.brightness = __stringify(brightness), .mode = 0644 }, \
.show = led_brightness_show, \
.store = led_brightness_store, \
}
dev_attr_brightness就出现了,而show和store方法也被绑定了。
当调用cat …led0/brightness命令时候,就会调用led_brightness_show函数
当调用echo 1 > …led0/brightness命令时候,就会调用led_brightness_store函数
我们看一下led_brightness_store的实现
led_brightness_store(const char *buf, size_t size)
long state = simple_strtoul(buf, &after, 10); //把buf的字符串转换为long型
if (state == LED_OFF)
led_set_brightness(led_cdev, state);
led_cdev->brightness_set(led_cdev, value);
函数原型略去了两个形参,buf就是echo的内容了,sysfs已经帮我们从用户空间拷贝到了内核空间,
所以可以直接使用,首先把字符串转为数字,然后判断是灭灯还是亮灯,最后调用的是led_cdev->brightness_set
熟悉驱动框架的都知道,这个肯定就是调用到具体的平台相关led驱动中的函数了,即通过led-class.c
提供的led_classdev_register接口注册进来的驱动提供的具体函数了。以2240为例就是
s3c24xx_led_probe
cdev.brightness_set = s3c24xx_led_set;
led_classdev_register(&dev->dev, &led->cdev);
那么我们应该怎么给驱动添加sysfs属性的支持呢?下面就以蜂鸣器操作为例展示一下:
这是linux3.2.0中关于AM335X平台内核提供的蜂鸣器驱动源码–am335x_buzzer.c
/*
am335x_buzzer.c gpio driver ,misc device
PINS:
GPIO1_28
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#define DEV_NAME "buzzer"
#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
#define GPIO_BUZZER GPIO_TO_PIN(1, 28)
#define BEEP_ON 0
#define BEEP_OFF 1
static int gpio_open(struct inode *inode, struct file *file)
{
return 0;
}
static int gpio_release(struct inode *inode, struct file *file)
{
return 0;
}
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case BEEP_OFF:
gpio_set_value(GPIO_BUZZER, BEEP_OFF);
break;
case BEEP_ON:
gpio_set_value(GPIO_BUZZER, BEEP_ON);
break;
default:
return -ENOTTY;
}
return 0;
}
static struct file_operations dev_ope = {
.owner= THIS_MODULE,
.unlocked_ioctl = gpio_ioctl,
.open = gpio_open,
.release = gpio_release,
};
static struct miscdevice gpio_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &dev_ope,
};
static int __devinit gpio_probe(struct platform_device *pdev)
{
int ret;
int result;
ret = misc_register(&gpio_misc);
if (ret)
{
printk(KERN_ERR"misc gpio failed\n");
return ret;
}
result = gpio_request(GPIO_BUZZER, "gpio_buzzer");
if (result != 0)
{
printk("buzzer_request pin failed!\n");
}
gpio_direction_output(GPIO_BUZZER, BEEP_OFF);
printk(KERN_INFO"am335x buzzer driver\n");
return 0;
}
static int __devexit gpio_remove(struct platform_device *pdev)
{
misc_deregister(&gpio_misc);
gpio_free(GPIO_BUZZER);
printk(KERN_INFO "unregistered gpio_buzzer device\n ");
return 0;
}
static struct platform_driver gpio_device_driver = {
.probe = gpio_probe,
.remove = gpio_remove,
.driver = {
.name = DEV_NAME,
.owner = THIS_MODULE,
}
};
static int __init gpio_init(void)
{
return platform_driver_register(&gpio_device_driver);
}
static void __exit gpio_exit(void)
{
platform_driver_unregister(&gpio_device_driver);
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_DESCRIPTION("GPIO_BUZZER Drivers");
MODULE_LICENSE("GPL");
它是以platform总线实现的,在probe函数中又调用了misc_register把它注册为misc类设备
所以此时加载驱动会在/sys/class/misc中看到buzzer节点,但是ls一下只有下面几个属性
$ls /sys/class/misc/buzzer
dev power subsystem uevent
这四个属性是所有设备共有的属性,由sysfs核心产生,此时我们想提供一个state属性,
可用通过cat …/buzzer/state查看蜂鸣器状态,通过echo 0 > …/buzzer/state和
echo 0 > …/buzzer/state实现蜂鸣器响和不响操作。
我们需要做的有下面几件事:
1.调用 DEVICE_ATTR 宏声明state属性名称、读写权限及操作函数
2.实现操作函数buzzer_state_show和buzzer_state_set
3.调用device_create_file接口增加属性
所以代码就改为(只列出增加部分和修改部分,另增加了一个example属性):
static ssize_t buzzer_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%u\n", gpio_get_value(GPIO_BUZZER));
}
static ssize_t buzzer_state_set(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
char *after;
unsigned long state = simple_strtoul(buf, &after, 1);
switch(state)
{
case BEEP_OFF:
gpio_set_value(GPIO_BUZZER, BEEP_OFF);
break;
case BEEP_ON:
gpio_set_value(GPIO_BUZZER, BEEP_ON);
break;
default:
return -ENOTTY;
}
return size;
}
static DEVICE_ATTR(state, 0644, buzzer_state_show, buzzer_state_set);
static ssize_t buzzer_example_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "\n%s\n", "hello I am sysfs example");
}
static ssize_t buzzer_example_set(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
printk("%s\n", buf);
return size;
}
static DEVICE_ATTR(example, 0644, buzzer_example_show, buzzer_example_set);
static int __devinit gpio_probe(struct platform_device *pdev)
{
int ret;
int result;
ret = misc_register(&gpio_misc);
if (ret)
{
printk(KERN_ERR"misc gpio failed\n");
return ret;
}
result = gpio_request(GPIO_BUZZER, "gpio_buzzer");
if (result != 0)
{
printk("buzzer_request pin failed!\n");
}
gpio_direction_output(GPIO_BUZZER, BEEP_OFF);
ret = device_create_file(gpio_misc.this_device, &dev_attr_state);
if(ret)
goto err_out;
ret = device_create_file(gpio_misc.this_device, &dev_attr_example);
if(ret)
goto err_example;
printk(KERN_INFO"am335x buzzer driver\n");
return 0;
err_example:
device_remove_file(gpio_misc.this_device, &dev_attr_state);
err_out:
gpio_free(GPIO_BUZZER);
misc_deregister(&gpio_misc);
return ret;
}
做了修改后加载驱动,再查看buzzer的属性时候就可以看到增加了state属性
$ ls /sys/class/misc/buzzer
dev example power state subsystem uevent
然后进行测试:
$ cat example
hello I am sysfs example
$ echo 123456 > example
[ 566.408172] 123456 //驱动中把用户输入简单的通过printk打印出来了
$ cat state
1 //即蜂鸣器的管脚是高电平
echo 0 > state 就会导致蜂鸣器鸣叫
至此,就把sysfs应用简单分析了一下,并简单修改了buzzer的驱动验证了一下。
至于sysfs的核心实现,后边有时间再写吧。最后为整个子系统分下层
用户空间:
使用shell命令操作sys下节点属性可以直接控制硬件。
比如echo 1 > /sys/class/leds/led0/brightness
内核空间:
sysfs核心:产生/sys目录
class.c:产生/sys/class目录
led-class.c:产生/sys/class/leds目录及相关属性
leds-s3c24xx.c:提供属性操作具体方法