4.linux内核platform机制
4.1.案例:硬件工程师将某个LED灯的GPIO引脚由GPIOC12
修改为GPIOE4,来完成此驱动,采用ioremap操作寄存器实现
分析:只要基于GPIOC12+ioremap实现的驱动基础上做修改即可
于是乎将昨天ioremap驱动一点点改改.
结论:如果基于之前的驱动进行修改,发现此驱动代码改动的位置
比较多,如果有一个地方没有照顾到,硬件操作必然有问题
也就是昨天的ioremap驱动代码的可移植性非常差劲
并且发现此驱动只改纯硬件信息(物理起始地址,GPIO编号等)
而软件代码无需改动(if...else,switch...case等)
解决:势必想到#define来提高驱动代码的可移植性
定义一个头文件,将可变的纯硬件信息以#define宏的形式
表现出来,将来驱动工程师
只需维护头文件即可,驱动代码一旦写好,无需改动!
问:如果用#define来描述纯硬件信息,如何用#define
来区分硬件信息的类型呢?
例如:
#define GPIO_1 (0xC001C000)
#define GPIO_2 (12)
如何知道GPIO_1是地址呢?GPIO_2是GPIO编号呢
也许认为GPIO_2是地址呢?GPIO_1是GPIO编号呢
答:无法知道,暴露#define缺陷是描述硬件信息时,描述的
属性是单一的
linux内核为了解决此问题,采用数据结构来描述纯硬件信息
即提高了代码的可移植性,又能给硬件信息添加多种属性
例如:
//自行设计:描述纯硬件信息的数据结构
struct resource {
unsigned long start; //起始信息
unsigned long end; //结束信息
unsigned long flags; //类型标识
IORESOURCE_ADDRESS:地址信息
IORESOURCE_GPIO:GPIO编号信息
};
//定义初始化对象描述物理地址和GPIO编号信息
struct resource led_res[] = {
//描述寄存器物理地址信息
{
.start = 0xC001C000, //起始地址
.end = 0xC001C000 + 0x24, //结束地址
.flags = IORESOURCE_ADDRESS //地址类
},
//描述GPIO信息
{
.start = 12,
.end = 12,
.flags = IORESOURCE_GPIO //GPIO类型
}
};
4.2.问:最终linux内核如何提高驱动的可移植性呢?具体如何实现呢
答:采用分离思想
也就是将一个驱动程序中纯硬件信息和纯软件信息进行分开
将来驱动工程师只需维护纯硬件信息即可,纯软件代码一旦
写好,将来无需做修改(甚至源文件都无需打开看)
问:linux内核分离思想又是如何实现呢?
答:采用platform机制实现,适用于所有的硬件
4.3.linux内核platform机制实现原理
参见:ESD_LDD_DAY09.pptx,P28开始
面试时:边说边画图
4.4.结论:如果采用platform机制实现驱动程序,只需:
驱动工程师只需维护两个数据结构:
struct platform_device
struct platform_driver
只需做:定义初始化和注册硬件节点和软件节点
内核会帮你做四件事:
1.帮你遍历dev或者drv链表
2.帮你调用总线提供的match进行匹配
3.如果匹配成功,内核还会帮你调用软件节点的probe函数
4.如果匹配成功,内核还会给probe函数传递匹配成功的硬件节点首地址
4.5.详解struct 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 //将自定义的硬件信息添加到节点
//类似把白菜放到卡车上
}
};
resource定义硬件信息
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, //结束地址
.flags = IORESOURCE_MEM //地址类
},
//描述GPIO信息
{
.start = 12,
.end = 12,
.flags = IORESOURCE_IRQ //GPIO编号信息
}
};
//定义初始化硬件节点对象(类似卡车)
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链表删除硬件节点对象
案例:利用platform机制优化LED驱动
目前先编写纯硬件信息
上位机执行:
mkdir /opt/drivers/day10/1.0
cd /opt/drivers/day10/1.0
vim led_dev.c //描述自定义的硬件信息
cp /opt/drivers/day10/1.0 /opt/drivers/day10/2.0
cd /opt/drivers/day10/2.0
vim led_dev.c //resource描述的硬件信息
4.6.详解struct 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)
只调用一次
配套函数:
//向drv链表添加软件节点
//内核帮你做遍历,匹配,调用probe,传递参数
int platform_driver_register(&软件节点对象);
//从drv链表删除软件节点
void platform_driver_unregister(&软件节点对象);
案例:利用platform机制优化LED驱动
现在添加软件节点的代码
上位机执行:
cd /opt/drivers/day10/1.0
vim led_drv.c //描述软件信息,自定义硬件信息
vim led_drv.c
make
cd /opt/drivers/day10/2.0
vim led_drv.c //描述软件信息,resource描述硬件信息
vim led_drv.c
框架是一样的,resouce描述与自定义描述的不同在与probe函数内部不同,此处只是给出框架
make
下位机测试:
insmod led_dev.ko
insmod led_drv.ko
rmmod led_dev
rmmod led_drv
为了去除警告
无警告
先安装哪个都可以
4.7.probe函数所做的工作如下:
1.通过形参pdev获取纯硬件信息
1.获取自定义的硬件信息代码:
struct led_resource *pdata = pdev->dev.platform_data;
//结果:pdata=&led(位于led_dev.c)
2.获取resource描述的硬件信息,用以下函数来获取:
struct resource *platform_get_resource(
struct platform_device *pdev,
unsigned long flags,
int index);
pdev:传递匹配成功的硬件节点首地址
flags:传递要获取的硬件信息的类型
index:传递要获取的硬件信息类型的偏移量(坑)
返回值:返回获取的resource硬件信息的地址
2.处理硬件信息
该地址映射的映射
该申请的申请
该初始化的初始化
3.给用户提供操作接口
注册字符设备或者混杂设备都行
注意:2和3都是在之前驱动的入口函数完成,现在迁移到probe函数中
remove跟probe死对头!
1 //led_drv.c:描述软件节点的信息//自定义硬件设备时
2 #include <linux/init.h>
3 #include <linux/module.h>
4 #include <linux/platform_device.h>
5 #include <linux/fs.h>
6 #include <linux/miscdevice.h>
7 #include <linux/io.h>
8 #include <linux/uaccess.h>
9
10 //声明描述LED硬件信息的结构体类型
11 struct led_resource {
12 unsigned long gpio_phy_address; //GPIO寄存器开始物理地址
13 unsigned long size; //GPIO寄存器物理地址空间大小
14 int gpio; //GPIO编号
15 };
16
17 static void* gpiobase;
18 static unsigned long *gpiocaltfn0, *gpiocoutenb, *gpiocout;
19 static int pin;
20
21 #define LED_ON 0x100001
22 #define LED_OFF 0x100002
23 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long buf) {
24 int kindex;
25 copy_from_user(&kindex, (int *)buf, sizeof(kindex));
26 switch(cmd) {
27 case LED_ON:
28 if(kindex == 1)
29 *gpiocout &= ~ (1 << pin);
30 break;
31 case LED_OFF:
32 if(kindex == 1)
33 *gpiocout |= (1 << pin);
34 break;
35 }
36 printk("ALT:%#X, OUTENB:%#X, OUT:%#X\n", *gpiocaltfn0, *gpiocoutenb, *gpiocout);
37 return 0;
38 }
39
40 static struct file_operations led_fops = {
41 .unlocked_ioctl = led_ioctl
42 };
43
44 static struct miscdevice led_misc = {
45 .minor = MISC_DYNAMIC_MINOR,
46 .name = "myled",
47 .fops = &led_fops
48 };
49
50 //硬件节点和软件节点匹配成功,内核调用此函数
51 //形参pdev指向匹配成功的硬件节点,就是:pdev = &led_dev(位于led_dev.c)
52 static int led_probe(struct platform_device *pdev){
53 //1.通过pdev获取自定义的硬件信息
54 //目标:pdata = &led(位于led_dev.c)
55 struct led_resource *pdata = pdev->dev.platform_data;
56 pin = pdata->gpio; // pin = 12
57 //2.处理获取的自定义硬件信息,各种该
58 //将寄存器的物理地址映射到内核虚拟地址上
59 gpiobase = ioremap(pdata->gpio_phy_address, pdata->size);
60 //换算各个寄存器的内核虚拟地址
61 gpiocout = (unsigned long*)(gpiobase + 0x00);
62 gpiocoutenb = (unsigned long*)(gpiobase + 0x04);
63 gpiocaltfn0 = (unsigned long*)(gpiobase + 0x20);
64 //四选一,二选一,默认输出1
65 *gpiocaltfn0 &= ~(3 << (2*pin));
66 *gpiocaltfn0 |= (1 << (2*pin));
67 *gpiocoutenb |= (1 << pin);
68 *gpiocout |= (1 << pin);
69 //3.注册混杂设备
70 misc_register(&led_misc);
71 printk("%s\n", __func__);
72 return 0;//成功返回0,失败返回负值
73 }
74
75 //删除硬件节点或者软件节点,内核调用此函数
76 //形参pdev指向匹配成功的硬件节点,就是:pdev = &led_dev(位于led_dev.c)
77 static int led_remove(struct platform_device *pdev){
78 misc_deregister(&led_misc);
79 *gpiocout |= (1 << pin);
80 iounmap(gpiobase);
81 printk("%s\n", __func__);
82 return 0;//成功返回0,失败返回负值
83 }
84
85 //定义初始化软件节点对象
86 static struct platform_driver led_drv = {
87 .driver = {
88 .name = "tarena" //用于匹配
89 },
90 .probe = led_probe, //硬件和软件匹配成功,内核调用此函数
91 .remove = led_remove //删除硬件或者软件,内核调用此函数
92 };
93
94 static int led_drv_init(void) {
95 //向内核drv链表注册软件节点,内核会帮你遍历,匹配,调用probe,传递参数
96 platform_driver_register(&led_drv);
97 return 0;
98 }
99
100 static void led_drv_exit(void) {
101 //从内核drv链表上卸载软件节点
102 platform_driver_unregister(&led_drv);
103
104 }
105
106 module_init(led_drv_init);
107 module_exit(led_drv_exit);
108 MODULE_LICENSE("GPL");
resource描述硬件信息时
总结:
定义初始化硬件节点led_dev
resouce描述硬件信息 自定义描述硬件信息
static struct resource led_info[ ] = sturct led_resource
static struct led_resouce led
static void led_release(struct device *dev) static void led_release(struct device *dev)
static struct platform_device led_dev={ static sturct platform_device led_lev={
.name .name
.id .id
.resource硬件信息
.num_resources硬件信息个数
.dev={ .dev={
.platform_data = &led,
.release=led_release去除警告 .release=led_release去除警告
} }
} }
static int led_dev_init(void){
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");