笔记整理—linux驱动开发部分(4)驱动框架
内核中,针对每种驱动都设计了一套成熟的、标准的、典型的驱动框架,实现将相同部分实现,不同部分留出接口给工程师自行发挥。具有以下特点:①简单化;②标准化;③统一管控系统资源;④特定化接口函数与数据结构。eg:led_class.c led_core.c 去实现自己的LED_xxx.c,其中xxx为SOC厂商,产品商又会以厂商代码做出产品,对soc厂商代码做移植与调试。
模块分析方法:从下往上进行分析。驱动框架:实现了一个类的通用class。
内核驱动开发以级别定启动顺序与段(n=1~7s,可选1~7s,但实际为0~7s)。内核启动时按段启动顺序执行。
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
attribute,对应于/sys/class/xxx/目录中的内容,一般为文件或文件夹,是sysfs给应用层的一些接口,类似/dev/下的设备文件。
驱动操作硬件:①file_operation;②attribute。
class_create:在/sys/class下创建一个类
device_create:创建属于一个类的一个设备,本质是注册一个设备,是驱动框架的注册接口
file_operations:用于register_chrdev注册
驱动模型分为三种
platform:平台设备
system:系统设备
virtual:虚拟设备
echo 1>led1 可操作led1,因为在led1的ston中有方法支持(X210开发板)。
基于LED class实现LED驱动,关键点在于led_classdev_register,这是厂商已经写好的,自己不用在写一次。
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev);
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
up_write(&leds_list_lock);
if (!led_cdev->max_brightness)
led_cdev->max_brightness = LED_FULL;
led_update_brightness(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
#endif
printk(KERN_DEBUG "Registered led device: %s\n",
led_cdev->name);
return 0;
}
当注册成功在/sys/class/led/下会出现相关信息。
static int __init s5pv210_led_init(void);//注册
static void __exit s5pv210_led_exit(void);//注销
struct led_classdev mydev;//led_classdev 是内核类设备
led_classdev_register(NULL,&mydev);
led_classdev_unregister(&mydev);
mydev.name="mydev";
mydev.brightness=0;
mydev.brightness_set=s5pv210_led_set;
store 方法对应echo写法;show方法对应cat(show)读方法。
show方法读硬件信息,返回到应用层,但因为驱动框架提供的方法无法直接提取到信息,因为没有特殊性的去细化每一个设备,只是一个类的方法,所以在show和store之间使struct led_classdev结构体中相对应的细化方法去实现。struct led_classdev结构体中的方法要自己去写。
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
};
static void s5pv210_led_set(struct led_classdev *led_cdev,emum led_brightness value)
{
if(LED_OFF==value)
{
write(0xxxxxxxxx,GPJ0CON);
//方法
write(0xxxxxxxxx,GPJ0DAT);
}else if(LED_ON==value)
{
//方法
}
}
硬件驱动的机制与策略/机制——怎么实现操作方法,也即是驱动;策略——如何使硬件按照自己的要求工作,也就是应用。
机制不应该去提供策略方法,操作LED的方法就是想灭就灭,想关就关,机制只应该提供开关的接口,不应该去管要亮几个LED,等应用层要LED亮就亮就行了。
读改写保证开关一个LED:
writel(((readl(GPJ0DAT)|(1<<3),GPJ0DAT);//灭
writel(((readl(GPJ0DAT)&~(1<<3),GPJ0DAT);//亮
GPIOLIB是先申请,再使用的。大部分硬件都使用GPIO工作以及复用(同GPIO工作不同硬件);同一个GPIO被两个驱动同时控制会出现BUG;内核提供gpiolib进行统一管理系统中所有gpio(只要一方不释放,别人就别想用);gpiolib本质属于驱动框架的一部分(/kernel/drivers/gpio)。
GPIO的使用方法:申请——>使用——>释放。
gpio_chip结构体,gpio操作方法框架。
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
int (*request)(struct gpio_chip *chip,
unsigned offset);
void (*free)(struct gpio_chip *chip,
unsigned offset);
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);
int (*get)(struct gpio_chip *chip,
unsigned offset);
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);
int (*set_debounce)(struct gpio_chip *chip,
unsigned offset, unsigned debounce);
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int base;
u16 ngpio;
const char *const *names;
unsigned can_sleep:1;
unsigned exported:1;
};
xxx_gpio_cfg:xxxgpio配置。xxx_gpio_pm,xxx电源管理;__iomem是虚拟地址基地址。
struct s3c_gpio_chip {
struct gpio_chip chip;
struct s3c_gpio_cfg *config;
struct s3c_gpio_pm *pm;
void __iomem *base;
int eint_offset;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[7];
#endif
};
struct s3c_gpio_cfg {
unsigned int cfg_eint;
s3c_gpio_pull_t (*get_pull)(struct s3c_gpio_chip *chip, unsigned offs);
int (*set_pull)(struct s3c_gpio_chip *chip, unsigned offs,
s3c_gpio_pull_t pull);
int (*set_pin)(struct s3c_gpio_chip *chip, unsigned offs,
s3c_gpio_pull_t level);
unsigned (*get_config)(struct s3c_gpio_chip *chip, unsigned offs);
int (*set_config)(struct s3c_gpio_chip *chip, unsigned offs,
unsigned config);
};
struct s3c_gpio_pm {
void (*save)(struct s3c_gpio_chip *chip);
void (*resume)(struct s3c_gpio_chip *chip);
};
端口与IO口,一个端口包含多个IO口,如GPA0是一个端口,GPA0_0是其中的一个IO口,每一个端口与相接端口相差0x20。
内核中为每个IO分配唯一一个连续编号,编号可用让程序识别,每一个GPIO label(是给人看的端口),base是每个GPIO的起始编码。
内核中也对终端号进行了统一管控。
static struct s3c_gpio_chip s5pv210_gpio_4bit[]
实现了对s5pv210开发板上的端口信息概括:
ARRAY_SIZE(s5pv210_gpio_4bit);//实现端口数计算
void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
int nr_chips)
{
for (; nr_chips > 0; nr_chips--, chip++) {
samsung_gpiolib_add_4bit(chip);
s3c_gpiolib_add(chip);
}
}
//这是三星注册gpio的方法。
int gpiochip_add(struct gpio_chip *chip)
{
unsigned long flags;
int status = 0;
unsigned id;
int base = chip->base;
if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
&& base >= 0) {
status = -EINVAL;
goto fail;
}
spin_lock_irqsave(&gpio_lock, flags);
if (base < 0) {
base = gpiochip_find_base(chip->ngpio);
if (base < 0) {
status = base;
goto unlock;
}
chip->base = base;
}
/* these GPIO numbers must not be managed by another gpio_chip */
for (id = base; id < base + chip->ngpio; id++) {
if (gpio_desc[id].chip != NULL) {
status = -EBUSY;
break;
}
}
if (status == 0) {
for (id = base; id < base + chip->ngpio; id++) {
gpio_desc[id].chip = chip;
/* REVISIT: most hardware initializes GPIOs as
* inputs (often with pullups enabled) so power
* usage is minimized. Linux code should set the
* gpio direction first thing; but until it does,
* we may expose the wrong direction in sysfs.
*/
gpio_desc[id].flags = !chip->direction_input
? (1 << FLAG_IS_OUT)
: 0;
}
}
unlock:
spin_unlock_irqrestore(&gpio_lock, flags);
if (status == 0)
status = gpiochip_export(chip);
fail:
/* failures here can mean systems won't boot... */
if (status)
pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
chip->base, chip->base + chip->ngpio - 1,
chip->label ? : "generic");
return status;
}
//进行gpiochip注册,这是通用的
__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
int ret;
BUG_ON(!chip->base);
BUG_ON(!gc->label);
BUG_ON(!gc->ngpio);
spin_lock_init(&chip->lock);
if (!gc->direction_input)
gc->direction_input = s3c_gpiolib_input;
if (!gc->direction_output)
gc->direction_output = s3c_gpiolib_output;
if (!gc->set)
gc->set = s3c_gpiolib_set;
if (!gc->get)
gc->get = s3c_gpiolib_get;
#ifdef CONFIG_PM
if (chip->pm != NULL) {
if (!chip->pm->save || !chip->pm->resume)
printk(KERN_ERR "gpio: %s has missing PM functions\n",
gc->label);
} else
printk(KERN_ERR "gpio: %s has no PM function\n", gc->label);
#endif
/* gpiochip_add() prints own failure message on error. */
ret = gpiochip_add(gc);
if (ret >= 0)
s3c_gpiolib_track(chip);
}
//其中提供了chip的input、output、set、get四种方法
同一个虚拟地址不可关联多个chip。
gpiochip_add是将封装的一个GPIO端口所有信息变量挂载到内核gpiolib模块定义的一个gpio_desc数组中。gpiochip_add是给厂商用来注册gpio接口的,在正常开发过程中是用不上的。gpio_request给驱动工程师使用gpiolib的接口,也是正常开发使用的接口。
驱动过程中,使用gpio接口,应先调用gpio_request去申请gpio接口,gpio_free用于释放申请的gpio接口。
int gpio_request_array(struct gpio *array, size_t num);可以一次申请多个gpio
void gpio_free_array(struct gpio *array, size_t num);一次释放多个gpio
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);申请一个gpio,支持flag调试模式
const char *gpiochip_is_requested(struct gpio_chip *chip, unsigned offset);查看gpio是否被申请
int gpio_direction_input/output(unsigned gpio);设置gpio输入/输出模式(只是在soc方法做了封装)本质上指向了samsung_gpiolib_4bit_output/input.
框架就是给不同soc留有接口去定制专项方法。
在gpiochip n文件夹中有base、lable、ngpio方法。
echo n>export可导出相关gpio信息。
gpiolib使用方法:①申请gpio_request;②设置输入输出模式gpio_direction_input/output;③设置输入输入的值gpio_get_value、gpio_set_value。
在mach/gpio中可看到用gpio与申请方法:
#defile GPIO_LED1 s5pv210_GPJ0(3);
#defile GPIO_LED2 s5pv210_GPJ0(4);
#defile GPIO_LED3 s5pv210_GPJ0(5);
if(gpio_request(GPIO_LED1,"gpio_3"))
{
//err
}else{
gpio_direction_output(GPIO_LED1,1);
}
//释放
gpio_free(GPIO_LED1);
//gpio设置高低电平
gpio_set_value(GPIO_LED1,0);
//申请多个gpio口
gpio_request_array(struct gpio,size);
查看某个gpio释放被使用 debugfs虚拟文件系统:①mount -t dugfs debugfs /tmp;②cat /tmp/gpio 可查看gpio信息;③umount /tmp卸载debugfd。
自己写的驱动应该放在文件夹中的/eds/下,后修改MAKEFILE、修改kconfig文件。在make menuconfig后可进行配置,在.config中可查看配置结果,make就可进行内核编译。编译成为模块的文件在同文件夹下可见.ko文件。