15 platform 机制
1、引入
问:最终linux内核如何提高驱动的可移植性呢?具体如何实现呢
答:采用分离思想
也就是将一个驱动程序中纯硬件信息和纯软件信息进行分开将来驱动工程师只需维护纯硬件信息即可,纯软件代码一旦写好,将来无需做修改(甚至源文件都无需打开看)
问:linux内核分离思想又是如何实现呢?
答:采用platform机制实现,适用于所有的硬件
2、platform机制
2.1 实现原理
- 解释:
1:在linux内核里面存在一个虚拟总线bus,在总线上维护dev链表和drv链表,dev链表上的每一个节点用来描述纯硬件信息,drv链表上的每一个节点用来描述纯软件信息。
2:dev链表的每个节点都有一个platform_device的结构体,里面的name成员用来描述硬件的名称,在drv链表上都有platform_driver的结构体,里面的name成员用来描述软件的名称,probe函数指针
3:当定义好了一个硬件节点之后,内核会调用总线的match函数,将硬件节点的name与软件节点的name进行判断,如果相等,则将硬件节点的地址给软件节点的probe函数指针,完成两者的匹配,如果没有找到,则静静等待。 - 结论:如果采用platform机制实现驱动程序,只需维护两个数据结构:
struct platform_device
struct platform_driver
和定义初始化和注册硬件节点和软件节点
内核会帮你做四件事:
1.帮你遍历dev或者drv链表
2.帮你调用总线提供的match进行匹配
3.如果匹配成功,内核还会帮你调用软件节点的probe函数
4.如果匹配成功,内核还会给probe函数传递匹配成功的硬件节点首地址
2.2 重要的结构体
2.2.1 platform_device
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
- 功能:描述dev链表上每个纯硬件节点属性
- 属性
- name:指定一个硬件节点名称,用于匹配,必须初始化
- id:指定一个硬件节点编号,如果dev链表上只有一个名称为name的硬件节点,那么id=-1,如果dev链表上有多个同名的硬件节点,可以通过id进行标识:id=0,1,2,....
- dev:只需关注其中的void *platform_data字段,这是用于装载驱动工程师自定义的纯硬件信息
struct device {
void *platform_data;
};
用法如下,例如:
//驱动工程师声明描述LED硬件信息数据结构:
struct led_resource {
unsigned long phy_baseaddr; //起始物理地址
unsigned long size; //大小
unsigned long gpio; //GPIO编号
};
//驱动工程师定义初始化LED硬件信息对象
struct led_resource led = {
.pyh_baseaddr = 0xC001C000,
.size = 0x24,
.gpio = 12
};
//定义初始化硬件节点对象
struct platform_device led_dev = {
.name = "tarena", //用于匹配
.id = -1,
.dev = {
.platform_data = &led //将自定义的硬件信息添加到节点
}
};
- num_resources:用于指示resource描述的硬件信息的个数,因为别人无法通过resource指针来获取,resource描述硬件信息个数
即:num_resources = ARRAY_SIZE(led_res)
切记:num_resources只能和resource一起使用
- resource:用于装载resource描述的硬件信息,此数据结构struct resource是内核声明的供驱动使用,描述硬件信息:
struct resource {
unsigned long start; //起始信息
unsigned long end; //结束信息
unsigned long flags; //类型标识
};
- 功能:内核提供的描述硬件信息的数据结构,类似自己写的struct led_resource
- 属性
- start:起始信息
- end:结束信息
- flags:资源类型,宏:
IORESOURCE_MEM:地址类信息
IORESOURCE_IRQ:GPIO类或者中断号信息
用法,以LED为例:
//定义初始化LED硬件信息对象
struct resource led_res[] = {
//描述寄存器物理地址信息
{
.start = 0xC001C000, //起始地址
.end = 0xC001C000 + 0x24 - 1, //结束地址
.flags = IORESOURCE_MEM //地址类
},
//描述GPIO信息
{
.start = 12,
.end = 12,
.flags = IORESOURCE_IRQ //GPIO类型
}
//描述寄存器物理地址信息
{
.start = 0xC001C000, //起始地址
.end = 0xC001C000 + 0x24 - 1, //结束地址
.flags = IORESOURCE_MEM //地址类
},
};
//定义初始化硬件节点对象
struct platform_device led_dev = {
.name = "tarena", //用于匹配
.id = -1,
.resource = led_res, //装载resource描述的硬件信息
.num_resources = ARRAY_SIZE(led_res) //指定硬件信息的个数
};
总结:装载硬件信息的方法有两种:
1.自定义描述
2.resource描述
两种方法可以同时使用,也可以单独使用
- 配套函数:
int platform_device_register(&硬件节点对象);
- 功能:向内核dev链表添加硬件节点,此时内核会帮你遍历,匹配,调用probe,传递参数
void platform_device_unregister(&硬件节点对象);
- 功能:从内核dev链表删除硬件节点对象
案例:编写LED驱动,采用platform实现,先添加硬件信息
// 使用自定义描述实现
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
// 声明描述LED的纯硬件信息的结构体类型
struct led_resource{
unsigned long phy_baseaddress; // 寄存器的起始物理地址
unsigned long size; // 寄存器物理地址空间大小
int gpio; // GPIO编号
};
// 定义初始化LED纯硬件信息对象
static struct led_resource led={
.phy_baseaddress = 0xC001C000,
.size = 0x24,
.gpio = 12
};
static void led_release(struct platform_device *dev){}
// 定义初始化硬件节点对象
static struct platform_device led_device={
.name="led",// 用于匹配
.id=-1,// 节点编号
.dev = {
.platform_data = &led ,// 装载自定义的纯硬件信息
.release = &led_release
}
}
static int led_init(void){
// 注册硬件节点对象到dev链表,内核自动帮你遍历drv链表匹配,
//匹配成功调用probe函数并且给probe函数传参
platform_device_register(&led_device);
return 0;
}
static void led_exit(void){
// 卸载
platform_device_unregister(&led_device);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
// 采用系统提供的resource实现
...
static struct resource led_res[]={
// 描述寄存器的地址信息
{
.start = 0xC001C000,
.end = 0xC001C000+0x24,
.flags = IORESOURCE_MEM // 地址类型的硬件信息
},
// 描述GPIO编号信息
{
.start = 12,
.end = 12,
.flags = IORESOURCE_IRQ // GPIO编号类型的硬件信息
},
};
// 定义初始化硬件节点对象
static struct platform_device led_device={
.name="led",// 用于匹配
.id=-1,// 节点编号
.resource = led_res;// 装载resource描述的纯硬件信息
.num_resource = ARRAY_SIZE(led_res); // 指定resource描述的硬件信息的个数
.dev = {
.release = &led_release
}
}
...
2.2.2 platform_driver
struct platform_driver {
struct device_driver driver;
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
};
- 功能:描述软件节点信息
- 属性:
- driver:只关注其中的char *name字段,用于匹配
struct device_driver {
const char *name;
}
- probe:硬件节点和软件节点匹配成功,内核调用,形参pdev指向匹配成功的硬件节点(&led_dev)
- remove:删除软件节点或者硬件节点,内核调用此函数,形参pdev指向匹配成功的硬件节点(&led_dev),只调用一次
- 相关函数
int platform_driver_register(&软件节点对象);
- 功能:向drv链表添加软件节点,内核帮你做遍历,匹配,调用probe,传递参数
void platform_driver_unregister(&软件节点对象);
- 功能:从drv链表删除软件节点
案例:添加软件信息的添加
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
int led_probe(struct platform_device *pdev){
printk("%s\n",__func__);
return 0;
}
int led_remove(struct platform_device *pdev){
printk("%s\n",__func__);
return 0;
}
// 定义初始化led软件节点对象
static struct platform_driver led_drv={
.driver={
.name="led"
},
.probe = led_probe,// 硬件和软件匹配成功内核调用
.remove = led_remove // 卸载硬件或软件后内核调用
};
static int led_drv_init(void){
// 注册软件节点到drv链表,内核帮你遍历匹配,调用probe和传参
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void){
// 卸载
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
2.3 probe函数
所做的工作如下:
- 通过形参pdev获取纯硬件信息
① 获取自定义的硬件信息代码:
struct led_resource *pdata = pdev->dev.platform_data;//结果:pdata=&led(位于led_dev.c)
② 获取resource描述的硬件信息,用以下函数来获取:
struct resource *platform_get_resource(struct platform_device *pdev, unsigned long flags,int index);
- pdev:传递匹配成功的硬件节点首地址
- flags:传递要获取的硬件信息的类型
- index:传递要获取的硬件信息类型的偏移量
返回值:返回获取的resource硬件信息的地址
- 处理硬件信息
该地址映射的映射;该申请的申请;该初始化的初始化 - 给用户提供操作接口
注册字符设备或者混杂设备都行
注意:2和3都是在之前驱动的入口函数完成,现在迁移到probe函数中,最后要使用remove
案例1
使用ioremap+platform+ioctl实现控制led灯
驱动硬件结点:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
// 使用内核的resource
struct resource led_res[]={
// 描述寄存器的地址信息
{
.start = 0xC001C000,// 起始地址
.end = 0xC001C000 + 0x24,// 结束地址
.flags = IORESOURCE_MEM// 地址类
},
// 描述GPIO编号信息
{
.start = 12,
.end = 12,
.flags = IORESOURCE_IRQ // GPIO编号信息
},
{
.start = 7,
.end = 7,
.flags = IORESOURCE_IRQ // GPIO编号信息
},
{
.start = 11,
.end = 11,
.flags = IORESOURCE_IRQ // GPIO编号信息
},
{
.start = 0xC001B000,// 起始地址
.end = 0xC001B000 + 0x28,// 结束地址
.flags = IORESOURCE_MEM// 地址类
},
{
.start = 26,
.end = 26,
.flags = IORESOURCE_IRQ // GPIO编号信息
}
};
static void led_release(struct platform_device * dev){}
// 定义paltform_device
struct platform_device led_dev={
.name="led",
.id=-1,
.resource=led_res,
.num_resources = ARRAY_SIZE(led_res),
.dev={
.release = &led_release
}
};
static int led_dev_init(void){
// 将硬件节点放入dev中
platform_device_register(&led_dev);
return 0;
}
static void led_dev_exit(void){
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
驱动软件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/io.h>
struct led_gpio{
void* base_gpio;
unsigned long *gpio_out;
unsigned long *gpio_outenb;
unsigned long *gpio_altfnx;
int state;
char name[10];
int pin;
};
static struct led_gpio led_info[4];
struct led_req{
int index;
char name[10];
int state;
};
#define LED_ON 0x10000
#define LED_OFF 0x10001
#define LED_READ 0x10002
static long led_ioctl(struct file *file,unsigned int cmd,unsigned long buf){
struct led_req led;
copy_from_user(&led,(struct led_req*)buf,sizeof(led));
switch(cmd){
case LED_ON:
*led_info[led.index].gpio_out &=~(1<<led_info[led.index].pin);
break;
case LED_OFF:
*led_info[led.index].gpio_out |=(1<<led_info[led.index].pin);
break;
case LED_READ:
memcpy(led.name,led_info[led.index].name,5);
led.state = *led_info[led.index].gpio_out;
led.state = (led.state >> led_info[led.index].pin)&0x01;
copy_to_user((struct led_req*)buf,&led,sizeof(led));
break;
default : break;
}
return 0;
}
static struct file_operations led_ops={
.unlocked_ioctl = led_ioctl
};
// 定义混杂设备
struct miscdevice led_device={
.name = "myled",
.minor = MISC_DYNAMIC_MINOR,
.fops=&led_ops
};
// 硬件匹配成功后调用
int led_probe(struct platform_device *pdev){
struct resource *led_gpio,*led_pin;
led_gpio = platform_get_resource(pdev,IORESOURCE_MEM,0);
led_pin = platform_get_resource(pdev,IORESOURCE_IRQ,0);
// 申请映射
led_info[0].base_gpio = ioremap(led_gpio->start,led_gpio->end - led_gpio ->start);
led_info[0].gpio_out = (unsigned long *)(led_info[0].base_gpio +0x00);
led_info[0].gpio_outenb = (unsigned long *)(led_info[0].base_gpio +0x04);
led_info[0].gpio_altfnx = (unsigned long *)(led_info[0].base_gpio +0x20);
memcpy(led_info[0].name,"led0",5);
led_info[0].pin = led_pin->start;
*led_info[0].gpio_altfnx &=~(3<<(2*led_info[0].pin));
*led_info[0].gpio_altfnx |=(1<<(2*led_info[0].pin));
*led_info[0].gpio_outenb |=(1<<led_info[0].pin);
led_pin = platform_get_resource(pdev,IORESOURCE_IRQ,1);
led_info[1].base_gpio = ioremap(led_gpio->start,led_gpio->end - led_gpio ->start);
led_info[1].gpio_out = (unsigned long *)(led_info[1].base_gpio +0x00);
led_info[1].gpio_outenb = (unsigned long *)(led_info[1].base_gpio+0x04);
led_info[1].gpio_altfnx = (unsigned long *)(led_info[1].base_gpio +0x20);
memcpy(led_info[1].name,"led1",5);
led_info[1].pin = led_pin->start;
*led_info[1].gpio_altfnx &=~(3<<(2*led_info[1].pin));
*led_info[1].gpio_altfnx |=(1<<(2*led_info[1].pin));
*led_info[1].gpio_outenb |=(1<<led_info[1].pin);
led_pin = platform_get_resource(pdev,IORESOURCE_IRQ,2);
led_info[2].base_gpio = ioremap(led_gpio->start,led_gpio->end - led_gpio ->start);
led_info[2].gpio_out = (unsigned long *)(led_info[2].base_gpio +0x00);
led_info[2].gpio_outenb = (unsigned long *)(led_info[2].base_gpio +0x04);
led_info[2].gpio_altfnx = (unsigned long *)(led_info[2].base_gpio +0x20);
memcpy(led_info[2].name,"led2",5);
led_info[2].pin = led_pin->start;
*led_info[2].gpio_altfnx &=~(3<<2*led_info[2].pin);
*led_info[2].gpio_altfnx |=(1<<2*led_info[2].pin);
*led_info[2].gpio_outenb |=(1<<led_info[2].pin);
led_pin = platform_get_resource(pdev,IORESOURCE_IRQ,3);
led_gpio = platform_get_resource(pdev,IORESOURCE_MEM,1);
led_info[3].base_gpio = ioremap(led_gpio->start,led_gpio->end - led_gpio ->start);
led_info[3].gpio_out = (unsigned long *)(led_info[3].base_gpio +0x00);
led_info[3].gpio_outenb = (unsigned long *)(led_info[3].base_gpio +0x04);
led_info[3].gpio_altfnx = (unsigned long *)(led_info[3].base_gpio +0x24);
memcpy(led_info[3].name,"led3",5);
led_info[3].pin = led_pin->start;
*led_info[3].gpio_altfnx &=~(3<<2*(led_info[3].pin%15));
*led_info[3].gpio_altfnx |=(1<<2*(led_info[3].pin%15));
*led_info[3].gpio_outenb !=(1<<led_info[3].pin);
// 加载混杂设备
misc_register(&led_device);
return 0;
}
// 断开后调用
int led_remove(struct platform_device *pdev){
iounmap(led_info[0].base_gpio);
iounmap(led_info[1].base_gpio);
iounmap(led_info[2].base_gpio);
iounmap(led_info[3].base_gpio);
misc_deregister(&led_device);
return 0;
}
// 定义软件节点信息
struct platform_driver led_drv={
.driver={
.name="led"
},
.probe = led_probe,
.remove = led_remove
};
static int led_drv_init(void){
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void){
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define LED_ON 0x10000
#define LED_OFF 0x10001
#define LED_READ 0x10002
struct led_gpio{
int index;
char name[10];
int state;
};
int main(){
int fd;
char cmd[10];
struct led_gpio led;
fd= open("/dev/myled",O_RDWR);
if(fd<0){
printf("open myled failed\n");
return -1;
}
while(1){
scanf("%s %d",cmd,&led.index);
if(led.index<0 || led.index >3){
printf("index input error\n");
return -1;
}
if(!strcasecmp(cmd,"on")){
ioctl(fd,LED_ON,&led);
}else if(!strcasecmp(cmd,"off")){
ioctl(fd,LED_OFF,&led);
}else if(!strcasecmp(cmd,"read")){
ioctl(fd,LED_READ,&led);
printf("%s -- %d --%s\n",led.name,led.index,led.state?"关":"开");
}
}
return 0;
}
案例2
使用gpio库函数+platform+ioctl实现控制led灯
硬件节点
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/platform_device.h>
// 硬件初始化
// led初始化
struct led_gpio{
int gpio;
char name[10];
};
static struct led_gpio led_info[]={
{
.name = "led0",
.gpio = PAD_GPIO_C+12
},
{
.name = "led1",
.gpio = PAD_GPIO_C+7
}
};
struct platform_data {
struct led_gpio *pd;
int led_num;
};
struct platform_data led={
.pd=led_info,
.led_num = ARRAY_SIZE(led_info)
};
// 定义硬件
static struct platform_device led_device = {
.name="led",
.id = -1,
.dev={
.platform_data = &led
}
};
static int led_dev_init(void){
platform_device_register(&led_device);
return 0;
}
static void led_dev_exit(void){
platform_device_unregister(&led_device);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
软件节点
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
struct led_gpio{
int gpio;
char name[10];
};
struct platform_data {
struct led_gpio *led;
int led_num;
};
static struct platform_data *pd;
#define LED_ON 0x10000
#define LED_OFF 0x10001
#define LED_READ 0x10002
struct led_state{
int index;
int state;
char name[10];
};
static long led_ioctl(struct file *file,unsigned int cmd,unsigned long buf){
struct led_state led;
copy_from_user(&led,(struct led_state *)buf,sizeof(led));
printk("led:%d cmd %#x\n",led.index,cmd);
switch(cmd){
case LED_ON:
gpio_set_value(pd->led[led.index].gpio,0);
break;
case LED_OFF:
gpio_set_value(pd->led[led.index].gpio,1);
break;
case LED_READ:
memcpy(led.name,pd->led[led.index].name,10);
led.state = gpio_get_value(pd->led[led.index].gpio);
copy_to_user((struct led_state*)buf,&led,sizeof(led));
break;
}
return 0;
}
static struct file_operations led_fops={
.unlocked_ioctl= led_ioctl
};
// 混杂设备
static struct miscdevice led_device = {
.name="myled",
.minor=MISC_DYNAMIC_MINOR,
.fops=&led_fops
};
// led 软件操作
static int led_probe(struct platform_device *pdev){
// 申请GPIO资源
int i;
pd = pdev->dev.platform_data;
for(i=0;i<pd->led_num;i++){
gpio_request(pd->led[i].gpio,pd->led[i].name);
gpio_direction_output(pd->led[i].gpio,1);
}
misc_register(&led_device);
return 0;
}
static int led_remove(struct platform_device *pdev){
int i;
// 释放GPIO
for(i=0;i<pd->led_num;i++){
gpio_set_value(pd->led[i].gpio,1);
gpio_free(pd->led[i].gpio);
}
misc_deregister(&led_device);
return 0;
}
static struct platform_driver led_driver = {
.driver = {
.name="led"
},
.probe=led_probe,
.remove=led_remove
};
static int led_drv_init(void){
platform_driver_register(&led_driver);
return 0;
}
static void led_drv_exit(void){
platform_driver_unregister(&led_driver);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
应用程序同上