(1)类class、总线bus、设备device、驱动driver
(2)kobject和对象生命周期
(3)sysfs
(4)udev
(1)早期内核(2.4之前)没有统一的设备驱动模型,但照样可以用
(2)2.6版本中正式引入设备驱动模型,目的是在设备越来越多,功耗要求等新特性要求的情况下让驱动体系更易用、更优秀。
(3)设备驱动模型负责统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施
(4)设备驱动模型目的是简化驱动程序编写,但是客观上设备驱动模型本身设计和实现很复杂。
1.3、驱动开发的2个点
(1)驱动源码本身编写、调试。重点在于对硬件的了解。
(2)驱动什么时候被安装、驱动中的函数什么时候被调用。跟硬件无关,完全和设备驱动模型有关。
树形结构中每一个目录与一个kobject对象相对应,其包含了目录的组织结构和名字等信息。在Linux系统中, kobject结构体是组成设备驱动模型的基本结构。
(1)kobject提供了最基本的设备对象管理能力,每一个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录,而不是文件。
(2)各种对象最基本单元,提供一些公用型服务如:对象引用计数、维护对象链表、对象上锁、对用户空间的表示
(3)设备驱动模型中的各种对象其内部都会包含一个kobject
(4)地位相当于面向对象体系架构中的总基类
struct kobject {
const char *name;//kobject的名字,且作为一个目录的名字
struct list_head entry;//连接下一个kobject结构
struct kobject *parent;//指向父亲kobject结构体,如果父设备存在
struct kset *kset; //指向kset集合
struct kobj_type *ktype; //指向kobject的属性描述符
struct sysfs_dirent *sd; //对应sysfs的文件目录
struct kref kref; //kobject的引用计数
unsigned int state_initialized:1; //表示该kobject是否初始化
unsigned int state_in_sysfs:1; //表示是否加入sysfs中
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
使用该kobject设备的共同属性
(1)很多书中简称为ktype,每一个kobject都需要绑定一个ktype来提供相应功能
(2)关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store)
(2)关键点2:attribute,提供在sysfs中以文件形式存在的属性,其实就是应用接口
struct kobj_type {
void (*release)(struct kobject *kobj);//释放kobject和其占用的函数
const struct sysfs_ops *sysfs_ops; //操作一个属性数组的方法
struct attribute **default_attrs; //属性数组的方法
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
属性数组:
struct attribute {
const char *name; //属性的名字
struct module *owner;//指向用于该属性的模块,已经不常使用
mode_t mode; //属性读写权限
};
name:属性的名字,对应某个目录下的文件的名字
操作属性数组的方法:
struct sysfs_ops
{
ssize t (*show) (struct kobject *, struct attribute *, char *);/*读属性操作函数*/
ssize t (*store) (struct kobject *,struct attribute *,const char *buf, size_t count);/*写属性操作函数*/
}
show()函数用于读取一个属性到用户空间。函数的第1个参数是要读取的kobect的指针,它对应要读的目录;第2个参数是要读的属性;第3个参数是存放读到的属性的缓存区。当函数调用成功后,会返回实际读取的数据长度,这个长度不能超过PAGESIZE个字节大小。
store()函数将属性写入内核中。函数的第1个参数是与写相关的kobject的指针,它对应要写的目录:第2个参数是要写的属性;第3个参数是要写入的数据;第4个参数是要写入的参数长度。这个长度不能超过PAGE-SIZE个字节大小。只有当拥有属性有写权限时,才能调用store0函数。
kset是具有相同类型的kobject的集合
kset包含了一个kobject,其实它相当于一个链表节点,虽然Kobject包含了kset元素
(1)kset的主要作用是做顶层kobject的容器类
(2)kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构
(3)可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起
struct kset {
struct list_head list; //连接链表
spinlock_t list_lock; //链表的自旋锁
struct kobject kobj; //内嵌的kobject结构体,说明kset本身也是一个目录
const struct kset_uevent_ops *uevent_ops; //热插拔事件
};
热插拔事件:
内核将在什么时候产生热插拔事件呢?当驱动程序将kobject注册到设备驱动模型时, 会产生这些事件。也就是当内核调用kobject_add()和kobject_del()丽数时,会产生热插拔事件。热插拔事件产生时,内核会根据kobject的kset指针找到所属的kset结构体,执行kset结构体中uevent_ops包含的热插拔函数。这些函数的定义如下:
struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);//决定是否向内核发送事件
const char *(* const name)(struct kset *kset, struct kobject *kobj);//得到子程序的名字
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
kset 和 kobject 的关系
不管是平台总线还是IIC总线都都有这样的调用路线:
当系统发现了新设备或者新驱动就会掉用相应总线的Match()进行匹配,当找到后就会掉用相对应的总线的Probe函数,最后Probe函数再调用驱动自己的Probe函数
虽然平台总线和IIC总线的实现有些不同,但是大体使一样的
如下:
//platform 总线
int platform_driver_register(struct platform_driver *drv)
{
if (drv->probe)
drv->driver.probe = platform_drv_probe;
return driver_register(&drv->driver);
}
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
// IIC总线
static int i2c_device_probe(struct device *dev)
{
status = driver->probe(client, i2c_match_id(driver->id_table, client));
}
(1)物理上的真实总线及其作用(英文bus)
(2)驱动框架中的总线式设计
(3)bus_type结构体,关键是match函数和uevent函数
struct bus_type {
const char *name; //总线类型名
struct bus_attribute *bus_attrs; //总线属性和导出到sysfs中的方法
struct device_attribute *dev_attrs; //设备属性和导出到sysfs中的方法
struct driver_attribute *drv_attrs; //驱动程序属性和导出到sysfs中的方法
//匹配函数,检验参数2中的驱动是否支持参数1中的设备
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev); //探测设备
int (*remove)(struct device *dev); //移除设备
void (*shutdown)(struct device *dev); //关闭函数
int (*suspend)(struct device *dev, pm_message_t state);//改变设备供电状态,使其节能
int (*resume)(struct device *dev); //恢复供电状态,使其正常工作
const struct dev_pm_ops *pm; //关于电源管理的操作符
struct bus_type_private *p; //总线的私有数据
};
struct bus type private {
struct kset subsys;/*代表该bus子系统,里面的kobj是该bus的主kobj,也就是最顶层*/
struct kset *drivers kset;/*挂接到该总线上的所有驱动集合*/
struct kset *devices kset;/*挂接到该总线上的所有设备集合*/
struct klist klist devices;/*所有设备的列表,与devices kset中的1ist相同*/
struct klist klist drivers;/*所有驱动程序的列表,与drivers_kset中的1ist相同*/
struct blocking notifier head bus notifier;/**/
unsigned int drivers autoprobe:1;/*设置是否在驱动注册时, 自动探测(probe)设备*/
struct bus type *bus;/*回指包含自己的总线*/
}
struct bus_attribute {
struct attribute attr;//总线属性
ssize_t (*show)(struct bus_type *bus, char *buf); //属性读函数
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);//属性写函数
};
在Linux设备驱动模型中,每一个设备都由一个device结构体来描述。device结构体包含了设备所具有的一些通用信息。对于驱动开发人员来说,当遇到新设备时,需要定义一个新的设备结构体,将device作为新结构体的成员。这样就可以在新结构体中定义新设备的一些信息,而设备通用的信息就使用device结构体来表示。使用device结构体的另一个好处是,可以通过device轻松地将新设备加入设备驱动模型的管理中。
device中的大多数函数被内核使用,驱动开发人员不用关注
(1)struct device是硬件设备在内核驱动框架中的抽象
(2)device_register用于向内核驱动框架注册一个设备
(3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device
struct device {
struct klist_klist children;/*连接子设备的链表*/
struct device *parent;/*指向父设备的指针*/
struct kobject kobj;/*内嵌的kobject结构体*/
char bus_id[BUS ID SIZE];/*连接到总线上的位置*/
unsigned uevent suppress:1;/*是否支持热插拔事件*/
const char init_name;/*设备的初始化名字*/
struct device_type *type;/*设备相关的特殊处理函数*/
struct bus_type *bus;/*指向连接的总线指针*/
struct device_driver *driver;/*指向该设备的驱动程序*/
void *driver data;/*指向驱动程序私有数据的指针*/
struct dev_pm info power;/*电源管理信息*/
dev t deyt;/*设备号*/
struct class *class;/*指向设备所属类*/
struct attribute_group **groups;/*设备的组属性*/
void (*release) (struct device *dev);/*释放设备描述符的回调函数*/
}
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
int device_create_file(struct device *dev, //创建
const struct device_attribute *attr)
void device_remove_file(struct device *dev, //删除
const struct device_attribute *attr)
(1)struct device_driver是驱动程序在内核驱动框架中的抽象
(2)关键元素1:name,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
(3)关键元素2:probe,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理
struct device_driver {
const char *name;//设备驱动程序的名字
struct bus_type *bus;//指向驱动属于的总线
struct module *owner;//设备驱动自身模块
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
#if defined(CONFIG_OF)
const struct of_device_id *of_match_table;
#endif
int (*probe) (struct device *dev);//探测设备的方法,并检测设备驱动可以控制哪些设备
int (*remove) (struct device *dev);//移除设备调用的方法
void (*shutdown) (struct device *dev);//关闭设备的方法
int (*suspend) (struct device *dev, pm_message_t state);//设备处于低功耗的方法
int (*resume) (struct device *dev);//恢复正常的方法
const struct attribute_group **groups;//属性组
const struct dev_pm_ops *pm;//电源管理
struct driver_private *p;//设备驱动私有数据
};
(1)相关结构体:struct class 和 struct class_device
(2)udev的使用离不开class
(3)class的真正意义在于作为同属于一个class的多个设备的容器。也就是说,class是一种人造概念,目的就是为了对各种设备进行分类管理。当然,class在分类的同时还对每个类贴上了一些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
struct device_attribute *dev_attrs;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct class_private *p;
};
(1)模型思想很重要,其实就是面向对象的思想
(2)全是结构体套结构体,对基本功(语言功底和大脑复杂度)要求很高
platform平台总线概述
Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为
platform_driver。
系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下
platform总线的bus_type实例platform_bus_type
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
static int platform_match(struct device *dev, struct device_driver *dr
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
有4种可能性,
一是基于设备树风格的匹配;
二是基于ACPI风格的匹配;
三是匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内);
第四种是匹配platform_device设备名和驱动的名字。
对于Linux 2.6ARM平台而言,对platform_device的定义通常在BSP的板文件中实现。
在板文件中,将所有 platform_device 设备归纳为一个数组,最终通过platform_add_devices()函数统一注册。
Linux 3.x之后, ARM Linux不太喜欢人们以编码的形式去填写platform_device和注册,而倾向于根据设备树中的内容自动展开platform_device。
由以上分析可知,在设备驱动中引入platform的概念至少有如下好处。
1)使得设备被挂接在一个总线上,符合Linux 2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能。
2)隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
3)让一个驱动支持多个设备实例。譬如DM9000的驱动只有一份,但是我们可以在板级添加多份DM9000的platform_device,它们都可以与唯一的驱动匹配
4.1、何为平台总线
(1)相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的。
(2)回顾裸机中讲的,CPU与外部通信的2种方式:地址总线式连接和专用接口式连接。平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设。
(3)思考:为什么要有平台总线?进一步思考:为什么要有总线的概念?
4.2、平台总线下管理的2员大将
(1)platform工作体系都定义在drivers/base/platform.c中
(2)两个结构体:platform_device和platform_driver
(3)两个接口函数:platform_device_register和platform_driver_register
struct platform_device {
const char * name; // 平台总线下设备的名字
int id; //设备名加ID名就得到了设备文件文件名
struct device dev; // 所有设备通用的属性部分
u32 num_resources; // 设备使用到的resource的个数
struct resource * resource; // 设备使用到的资源数组的首地址
const struct platform_device_id *id_entry; // 设备ID表
/* arch specific additions */
struct pdev_archdata archdata; // 自留地,用来提供扩展性的
};
struct platform_driver {
int (*probe)(struct platform_device *); // 驱动探测函数
int (*remove)(struct platform_device *); // 去掉一个设备
void (*shutdown)(struct platform_device *); // 关闭一个设备
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; // 所有设备共用的一些属性
const struct platform_device_id *id_table; // 设备ID表
};
因为设备要靠这个文件来识别驱动
(1)第一步:系统启动时在bus系统中注册platform
(2)第二步:内核移植的人负责提供platform_device
(3)第三步:写驱动的人负责提供platform_driver
(4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了
5.2、代码分析:platform本身注册
(1)每种总线(不光是platform,usb、i2c那些也是)都会带一个match方法,match方法用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但是实际上一般都是看name的。
(2)platform_match函数就是平台总线的匹配方法。
match函数的工作方法是:
如果有id_table就说明驱动可能支持多个设备,所以这时候要去对比id_table中所有的name,只要找到一个相同的就匹配上了不再找了
如果找完id_table都还没找到就说明没匹配上;
如果没有id_table或者没匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败。
6.1、以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程
(1)platform_driver_register
(2)platform_device_register
6.2、platdata怎么玩
(1)platdata其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称····)
(2)这些数据在设备和驱动match之后,会由设备方转给驱动方。驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。
(3)这样做的好处是:驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。
6.3、match函数的调用轨迹
6.4、probe函数的功能和意义
mach-x210.c 注册平台设备
//led 1,2,3
static struct s5pv210_led_platdata s5pv210_led1_pdata = {
.name = "led1",
.gpio = S5PV210_GPJ0(3),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "heartbeat",
};
static struct s5pv210_led_platdata s5pv210_led2_pdata = {
.name = "led2",
.gpio = S5PV210_GPJ0(4),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "heartbeat",
};
static struct s5pv210_led_platdata s5pv210_led3_pdata = {
.name = "led3",
.gpio = S5PV210_GPJ0(5),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "heartbeat",
};
static struct platform_device s5pv210_led1 = {
.name = "s5pv210_led",
.id = 0,
.dev = {
.platform_data = &s5pv210_led1_pdata,
},
};
static struct platform_device s5pv210_led2 = {
.name = "s5pv210_led",
.id = 1,
.dev = {
.platform_data = &s5pv210_led2_pdata,
},
};
static struct platform_device s5pv210_led3 = {
.name = "s5pv210_led",
.id = 2,
.dev = {
.platform_data = &s5pv210_led3_pdata,
},
};
/*******************************/
先定义结构体:
#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"
#define S5PV210_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */
#define S5PV210_LEDF_TRISTATE (1<<1) /* tristate to turn off */
struct s5pv210_led_platdata {
unsigned int gpio;
unsigned int flags;
char *name;
char *def_trigger;
};
#endif /* __ASM_ARCH_LEDSGPIO_H */
#include // module_init module_exit
#include // __init __exit
#include
#include
#include
#include
#include
#include
#include
#include //copy_from_user
#include
#include
#include
#include
#include
#include
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define GPJ0_LED1 S5PV210_GPJ0(3)
#define GPJ0_LED2 S5PV210_GPJ0(4)
#define GPJ0_LED3 S5PV210_GPJ0(5)
#define X210_LED_ON 0
#define X210_LED_OFF 1
/* our context */
struct s5pv210_gpio_led {
struct led_classdev cdev;
struct s5pv210_led_platdata *pdata;
};
//转换
static inline struct s5pv210_gpio_led *
pdev_to_gpio(struct platform_device *dev)//由platform_device得到s5pv210_gpio_led
{
return platform_get_drvdata(dev);
}
static inline struct s5pv210_gpio_led *
to_gpio(struct led_classdev *led_cdev)//由led_classdev得到s5pv210_gpio_led
{
return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}
//change brightness 0~255
void
led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness)
{
struct s5pv210_gpio_led *led = to_gpio(led_cdev);
printk(KERN_INFO "This is led_brightness_set1\n");
// 用户设置的值就是brightness
switch(brightness){
case LED_OFF:
//LED灭
gpio_set_value(led->pdata->gpio, X210_LED_OFF);
break;
case LED_FULL:
//LED亮
gpio_set_value(led->pdata->gpio, X210_LED_ON);
break;
default:
printk(KERN_INFO "anew input number\n");
}
}
//get led brightness
enum led_brightness
brightness_get(struct led_classdev *led_cdev)
{
//enum led_brightness brightness;
printk(KERN_INFO "This is led_brightness1\n");
return 0;
}
//platform 注册函数
static int
s5pv210_led_probe(struct platform_device *dev)
{
struct s5pv210_gpio_led *led = NULL;
struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
int ret = -1;
led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
if (led == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}
//gpio register
ret = gpio_request(pdata->gpio, pdata->name);//注册gpio
if (ret == 0){
printk(KERN_ERR "gpio_request led 0 failed\n");
//printk(KERN_ERR "ret = [%d]\n", ret);
return -EINVAL;
}else{
//printk(KERN_ERR "ret = [%d]\n", ret);
gpio_direction_output(pdata->gpio, 1);
}
//把led挂接到dev中的一个特殊变量上,方便后面的remove释放led
platform_set_drvdata(dev, led);
//register our new led device
led->cdev.name = pdata->name;//得到设备名字
led->cdev.brightness = 0;
led->cdev.brightness_set = led_brightness_set;
led->cdev.brightness_get = brightness_get;
led->pdata = pdata;
ret = led_classdev_register(&dev->dev, &led->cdev);//在设备文件下注册设备类leds
if(ret < 0){
printk(KERN_ERR "led_classdev_register failed\n");
return -EINVAL;
}
printk(KERN_INFO "led register success\n");
return 0;
}
// platform卸载函数
static int
s5pv210_led_remove(struct platform_device *dev)
{
struct s5pv210_gpio_led *led = pdev_to_gpio(dev);
struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
led_classdev_unregister(&led->cdev);//卸载设备类
gpio_free(pdata->gpio);
kfree(led);//释放led结构体的空间
printk(KERN_INFO "led unregister success\n");
return 0;
}
//led platform 结构体
static struct platform_driver s5pv210_led_driver = {//platform驱动结构体
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "s5pv210_led",
.owner = THIS_MODULE,
},
};
static int __init s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver);//注册设备驱动
}
static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver);//注销设备驱动
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("ljj"); // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led"); // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
运行结果:
[root@liu led1]# cd /sys/class/leds/
[root@liu leds]# ls -l
total 0
lrwxrwxrwx 1 root 0 0 Jan 1 12:03 led1 -> ../../devices/platform/s5pv210_led.0/leds/led1
lrwxrwxrwx 1 root 0 0 Jan 1 12:03 led2 -> ../../devices/platform/s5pv210_led.1/leds/led2
lrwxrwxrwx 1 root 0 0 Jan 1 12:03 led3 -> ../../devices/platform/s5pv210_led.2/leds/led3
[root@liu s5pv210_led.0]# cd /sys/bus/platform/devices/
[root@liu devices]# ls -l
s5pv210_led.0
s5pv210_led.1
s5pv210_led.2
[root@liu devices]# cd /sys/bus/platform/devices/s5pv210_led.0
[root@liu s5pv210_led.0]# ls
driver leds modalias power subsystem uevent
[root@liu s5pv210_led.0]# cd leds/
[root@liu leds]# ls
led1
[root@liu leds]# cd led1/
[root@liu led1]# ls
brightness max_brightness subsystem
device power uevent
[root@liu led1]# cd /sys/bus/platform/drivers/
[root@liu drivers]# ls
5pv210_led //有些驱动没写
[root@liu drivers]# cd s5pv210_led/
[root@liu s5pv210_led]# ls
bind s5pv210_led.0 s5pv210_led.2 unbind
module s5pv210_led.1 uevent
[root@liu s5pv210_led]# ls -l
total 0
--w------- 1 root 0 4096 Jan 1 12:16 bind
lrwxrwxrwx 1 root 0 0 Jan 1 12:16 module -> ../../../../module/module_test
lrwxrwxrwx 1 root 0 0 Jan 1 12:16 s5pv210_led.0 -> ../../../../devices/platform/s5pv210_led.0
lrwxrwxrwx 1 root 0 0 Jan 1 12:16 s5pv210_led.1 -> ../../../../devices/platform/s5pv210_led.1
lrwxrwxrwx 1 root 0 0 Jan 1 12:16 s5pv210_led.2 -> ../../../../devices/platform/s5pv210_led.2
--w------- 1 root 0 4096 Jan 1 12:00 uevent
--w------- 1 root 0 4096 Jan 1 12:16 unbind