笔记整理—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_TRIGGERSinit_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_TRIGGERSled_trigger_set_default(led_cdev);
#endifprintk(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_PMu32 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(是给人看的,本质上是对于申请的GPIO_n所起的别名,在后面可用该别名进行端口申请情况查找),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_PMif (chip->pm != NULL) {if (!chip->pm->save || !chip->pm->resume)printk(KERN_ERR "gpio: %s has missing PM functions\n",gc->label);} elseprintk(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文件。