linux驱动(六)led驱动框架
1:在linux2.6板本内核开发人员开始建立驱动框架,以led驱动为例:
没有驱动框架的时候我们需要做一下事情:
module_init:
1:alloc_chrdev_region 注册字符驱动
2:cdev_alloc、cdev_init、cdev_add来向内核中添加驱动;
3:class_create 创建类
4:device_create创建驱动设备cdev
5:ioremap/使用静态的虚拟地址
module_exit:
1:iounmap
2:release_mem_region
3:device_destroy
4:class_destroy
5:cdev_del
6:unregister_chrdev_region
我们在不使用驱动框架的情况下需要自己来做以上事情,比较繁琐;下面来分析一下led的驱动框架:
内核工程师建立linux驱动框架的两个文件在/drivers/leds目录下文件名为:led-core.c、led-class.c
我们首先分下一下led-class.c文件,看一下内核工程师关于led驱动都做了哪些事情,
subsys_initcall(leds_init);
module_exit(leds_exit);
subsys_initcall 在/include/linux/init.h头文件中:
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __used __attribute__((__section__(".initcall" level ".init"))) = fn
展开以后:
static initcall_t __initcall_leds_init4 __used __attribute__((__section__(".initcall"4."init"))) = leds_init
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
leds_class->dev_attrs = led_class_attrs;
return 0;
}
首先是内核工程师建立的模块初始化:
leds_init 这个函数中调用的是class_create函数,创建了leds这个类,所以在/sys/class目录下会有leds这个类目录;
并且对这个类初始化
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;
};
static struct device_attribute led_class_attrs[] = {
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
__ATTR_NULL,
};
/************************************
#define __ATTR(_name,_mode,_show,_store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
*************************************************************/
这里是一个结构体数组,里面每个__ATTR宏表达式都是一个结构体,这个宏的结构如下所示,这个宏就是对device_attribute里的元素赋值;实际上device_attribute
结构体最终会通过device_create函数创建成具有一定权限的文件,文件名为 name 权限 mode_t 文件读的方式:.show 写的方式.store
这个文件直接代表了硬件的一个特性,以 __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),为例会在leds目录下创建一个brightness的文件,这个
文件使用来控制硬件led的brightness属性的,这个文件的执行权限位0644 即110100100 文件所有者权限可读可写。。。我们最终操作硬件就是通过读写这个文件来实现的,
这个文件的读写方法就是.show 和 .store 方法,
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);
};
struct attribute {
const char *name;
struct module *owner;
mode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
下面我们看一下linux内核为我们构建的一个led设备结构:
struct led_classdev {
const char *name;
int brightness;
int max_brightness;
int flags;
/* Lower 16 bits reflect status */
#define LED_SUSPENDED (1 << 0)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16)
/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
/* Activate hardware accelerated blink, delays are in
* miliseconds and if none is provided then a sensible default
* should be chosen. The call can adjust the timings if it can't
* match the values specified exactly. */
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off);
struct device *dev;
struct list_head node; /* LED Device list */
const char *default_trigger; /* Trigger to use */
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock;
struct led_trigger *trigger;
struct list_head trig_list;
void *trigger_data;
#endif
};
上面这个结构体就是linux内核为我们创建的通用的led设备模型,一个结构体对应一个led设备,这种思想就是面向对象的思想,其实c++以及java这种面向对象的编程语言,底层的实现
就是基于这方方式的,把一个对象的所有名称、属性,以及操作方法封装在一个结构体中,我们对这个对象执行的操作就相当于对这个结构体执行操作就可以了;
下面对这个结构体中的元素分析一下
*name:设备命
brightness 亮度
max_brightness:最大亮度
brightness_set函数指针,这个设置指向真正的设置亮度的函数
(
led_brightness_store
led_set_brightness
led_cdev->brightness_set(led_cdev, value);
//可以看出linux内核中调用是这么调用的应用层对brightness文件的写操作执行的是.store对应的led_brightness_store 然后调用led_set_brightness然后在调用结构体中的brightness_set函数真正的执行led的写操作这里一共有4层调用;同样show也是这个原理;
)
。。。。。。。。。。。。。。。。。。。
在来看一下驱动工程师应该做的事情,
这个函数中做的事情:
1:
struct s3c24xx_gpio_led *led;
struct s3c24xx_gpio_led { struct led_classdev cdev; struct s3c24xx_led_platdata *pdata; };
创建led驱动设备的结构体指针, struct led_classdev cdev; 这个结构体就是我们上面分析的led设备结构体
led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);
用kzalloc函数对这个指针分配内存;
led->cdev.brightness_set = s3c24xx_led_set; led->cdev.default_trigger = pdata->def_trigger; led->cdev.name = pdata->name; led->cdev.flags |= LED_CORE_SUSPENDRESUME; led->pdata = pdata; /* no point in having a pull-up if we are always driving */ if (pdata->flags & S3C24XX_LEDF_TRISTATE) { s3c2410_gpio_setpin(pdata->gpio, 0); s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT); } else { s3c2410_gpio_pullup(pdata->gpio, 0); s3c2410_gpio_setpin(pdata->gpio, 0); s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT); }
对这个结构体赋值,关键的最常用的几个值:
name
brightness_set
ret = led_classdev_register(&dev->dev, &led->cdev);
接下来就是注册led设备了;
led_classdev_register
device_create
在调用device_create函数创建相应的文件;
------------------------------------------------------------------------------------------------------------------------------------------
最后在分析一下自己写的led驱动与使用驱动框架的本质区别在哪里:
自己写的使用通用的设备模型cdev这个结构体,对设备操作时使用内核提供的file_opetations操作函数结构体,而应用层执行使用api调用/dev/目录下的设备文件节点直接操作即可;
而使用框架的话,就是内核专门为led提供了一个led的设备驱动模型,把所有的关于led的操作属性,都放到了这个驱动模型中,然后驱动工程师在对这个驱动模型进行初始化即可;
这就是使用和不使用驱动模型的本质区别;
参考文章:http://www.2cto.com/kf/201609/545540.html
-----------------------------------------------------------------------------------------------------------------------------------------------------
补充:对九鼎写的x210驱动进行分析(未完待续)