一.linux内核platform机制
1.1.回顾ioremap实现开关灯驱动需求:
硬件LED的GPIO由GPC0_3,GPC0_4更换为GPF1_4,GPF1_5
通过分析代码可知:
1.一个完整的驱动包括两部分:纯硬件和纯软件
2.之前的驱动如果硬件发生变化,代码几乎重头都要进行检查和修改,增大了驱动开发的工作量,可移植性非常差;
3.如果要优化此问题,内核提出分离思想,把纯软件和纯硬件的信息进行分离,将来驱动开发者的重心放在纯硬件上,软件一经写好,无需在改动;
1.2.linux内核分离思想
将硬件和软件进行分离;
分离思想的实现依靠platform机制,platform依赖设备-总线-驱动编程模型;
1.3.设备-总线-驱动编程模型:
实现的步骤参见PPT即可
总结:如果要采用设备-总线-驱动编程模型实现一个设备驱动,只需关注:
struct platform_device
struct platform_driver
问:如何使用以上两个数据结构呢?
1.4.struct platform_device
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
...
}
struct device {
void *platform_data;
};
struct resource {
resource_size_t start; //硬件的起始信息
resource_size_t end;//硬件的结束信息
unsigned long flags; //硬件信息的标志
有两种标志:
IORESOURCE_MEM:内存资源,例如寄存器地址
IORESOURCE_IRQ: IO资源,例如中断号,GPIO编号
};
数据结构作用:描述硬件外设的纯硬件信息
成员:
name:硬件节点的名称,相当重要,用于匹配
id:硬件节点的编号,如果只有一个硬件信息,一般赋值给-1,如果有多个同名的硬件信息,通过id来区分:0,1,2,3,...
dev:其中利用platform_data来装载驱动开发者自定义的硬件信息(struct led_resource, struct btn_resource,将自己声明的描述硬件信息添加到platform_data即可)
resource:指向resource类型的硬件信息
num_resourcs:resource类型的硬件信息的个数
注意:驱动开发者将来用resource来描述硬件还是用自定义的数据结构来描述硬件信息都可以,可以单独使用,也可以同时使用;
注册方法:
platform_device_register
卸载方法:
platform_device_unregister
案例
利用platform_device描述硬件信息
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
//声明描述LED硬件相关的数据结构(自定义)
struct led_resource {
unsigned long phys_addr; //保存起始物理地址
int size; //物理内存大小
int pin; //GPIO编号
};
//定义初始化自定义的LED硬件信息
static struct led_resource led_info = {
.phys_addr = 0xE0200060,
.size = 8,
.pin = 3 //GPC0_3
};
//定义初始化resource类型的硬件信息(resource描述)
static struct resource led_res[] = {
//描述寄存器地址信息
[0] = {
.start = 0xE0200060, //寄存器起始地址
.end = 0xE0200060 + 8, //寄存器结束地址
.flags = IORESOURCE_MEM //标记为内存资源
},
//描述GPIO编号信息
[1] = {
.start = 3, //GPC0_3
.end = 3, //GPC0_3
.flags = IORESOURCE_IRQ //标记为IO资源
}
};
static void led_release(struct device *dev) {}
//定义初始化描述LED硬件节点
static struct platform_device led_dev = {
.name = "myled", //用于匹配
.id = -1, //硬件节点编号
.dev = {
.release = led_release, //解除警告
.platform_data = &led_info //装载自定义的硬件信息
},
.resource = led_res, //装载resource类型的硬件信息
.num_resources = ARRAY_SIZE(led_res) //resource类型的硬件个数
};
static int led_dev_init(void)
{
//注册硬件节点到总线的dev链表
//并且内核帮你遍历drv链表,进行匹配
//如果匹配成功,内核调用软件节点的probe函数
//把led_dev首地址传递给probe函数
platform_device_register(&led_dev);
printk("led_dev = %#x\n", &led_dev);
return 0;
}
static void led_dev_exit(void)
{
//从dev链表卸载硬件节点
//调用软件节点的remove函数,remove的形参也执行led_dev
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
1.5.struct platform_driver {int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
struct device_driver driver;
};
struct device_driver {
char *name; //用于匹配
};
说明:描述软件信息
成员:
probe:软件和硬件匹配成功,内核调用,pdev指向匹配成功的硬件信息
remove:删除软件或者硬件,内核调用,pdev指向匹配成功的硬件信息
driver:其中的name用于匹配
注册方法:
platform_driver_register
卸载方法:
platform_driver_unregister
注意:probe和remove做什么事,完全由驱动开发者来决定;
总结:probe一般做三件事:
1.通过pdev指针获取硬件信息
有自定义的硬件信息
有resource类型的硬件信息
2.处理硬件信息
地址映射
申请GPIO资源
注册中断
配置GPIO为输出等
3.注册硬件操作方法
注册一个字符设备
注册一个混杂设备
remove是probe函数的死对头!
案例:利用platform_driver描述软件信息
实验步骤:
1.insmod led_dev.ko //先添加硬件
2.insmod led_drv.ko //在添加软件
3.rmmod led_dev //先卸载硬件
4.rmmod led_drv //在卸载软件
5.insmod led_drv.ko //先添加软件
6.insmod led_dev.ko //再添加硬件
7.rmmod led_drv //先卸载软件
8.rmmod led_dev //再卸载硬件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/io.h>
#include <linux/uaccess.h>
//定义LED开关命令
#define LED_ON 0x100001
#define LED_OFF 0x100002
struct led_resource {
unsigned long phys_addr;
int size;
int pin
};
static unsigned long *gpiocon, *gpiodata;
static int g_pin;
static long led_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
//分配内核缓冲
int kindex;
//拷贝用户数据到内核
copy_from_user(&kindex, (int *)arg, sizeof(kindex));
//解析命令
switch(cmd) {
case LED_ON:
if (kindex == 1)
*gpiodata |= (1 << g_pin);
break;
case LED_OFF:
if (kindex == 1)
*gpiodata &= ~(1 << g_pin);
break;
}
printk("%s:配置寄存器=%#x, 数据寄存器=%#x\n",
__func__, *gpiocon, *gpiodata);
return 0;
}
//定义初始化硬件操作方法
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = led_ioctl
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
//匹配成功调用,pdev指向匹配成功的硬件节点(led_dev)
static int led_probe(struct platform_device *pdev)
{
//1.获取自定义的硬件信息
//pdata指向led_dev.c的led_info
struct led_resource *pdata =
pdev->dev.platform_data;
//2.处理硬件信息
//将物理地址映射到内核空间
gpiocon = ioremap(pdata->phys_addr, pdata->size);
gpiodata = gpiocon + 1;
g_pin = pdata->pin;
//配置GPC0_3输出口,输出0
*gpiocon &= ~(0xf << (g_pin *4));
*gpiocon |= (1 << (g_pin * 4));
*gpiodata &= ~(1 << g_pin);
//3.注册混杂设备
misc_register(&led_misc);
return 0; //成功返回0,失败返回负值
}
//卸载软件或者硬件调用,pdev指向匹配成功的硬件节点(led_dev)
static int led_remove(struct platform_device *pdev)
{
//1.卸载混杂设备
misc_deregister(&led_misc);
//2.解除映射
iounmap(gpiocon);
return 0; //成功返回0,失败返回负值
}
//定义初始化软件节点
static struct platform_driver led_drv = {
.driver = {
.name = "myled", //用于匹配
},
.probe = led_probe,//匹配成功,内核调用
.remove = led_remove //卸载软件或者硬件,内核调用
};
static int led_drv_init(void)
{
//添加软件节点到内核
//内核帮你遍历dev链表,进行匹配
//匹配成功,内核调用probe函数,把硬件节点的首地址传递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");
内核提供了获取resource描述的硬件信息函数:
struct resource *platform_get_resource(
struct platform_device *dev,
unsigned int type,
unsigned int num)
函数功能:
通过pdev指针(指向匹配成功的硬件节点)获取resource类型的硬件信息
参数:
dev:就是pdev
type:资源的类型:
IORESOURCE_MEM或者IORESOURCE_IRQ
num:同类硬件资源的偏移量
返回值:返回硬件信息的首地址
例如:
static struct resource led_res[] = {
//描述寄存器地址信息
[0] = {
.start = 0xE0200060, //寄存器起始地址
.end = 0xE0200060 + 8 - 1, //寄存器结束地址
.flags = IORESOURCE_MEM //标记为内存资源
},
//描述GPIO编号信息
[1] = {
.start = 3, //GPC0_3
.end = 3, //GPC0_3
.flags = IORESOURCE_IRQ //标记为IO资源
},
//描述寄存器地址信息
[2] = {
.start = 0xE0200090, //寄存器起始地址
.end = 0xE0200090 + 8, //寄存器结束地址
.flags = IORESOURCE_MEM //标记为内存资源
},
//描述GPIO编号信息
[3] = {
.start = 4, //GPC0_4
.end = 4, //GPC0_4
.flags = IORESOURCE_IRQ //标记为IO资源
},
};
在probe函数中获取:
int led_probe(pdev) {
struct resource *preg_res;
struct resource *preg1_res;
struct resource *ppin_res;
struct resource *ppin1_res;
preg_res =
platform_get_resource(pdev, IORESOURCE_MEM, 0);
preg1_res =
platform_get_resource(pdev, IORESOURCE_MEM, 1);
ppin_res =
platform_get_resource(pdev, IORESOURCE_IRQ, 0);
ppin1_res =
platform_get_resource(pdev, IORESOURCE_IRQ, 1);
}