当我们在一块开发板上写好了驱动,但换一块不同芯片的开发板,我们就需要重新写一个驱动。其中主要是硬件连接也就是接口发生了改变,而软件框架几乎不用通用的。所以为了更加方便地移植,能够仅修改很小的内容就达到我们的目的。Linux内核提供了将驱动的硬件部分和软件部分分离开来的方法,移植的时候只需要修改其中的硬件部分。
内核中实现软硬件分离的机制:设备-总线-驱动模型。
这里的总线不是指IIC,SPI,UART等物理总线 ,而是指一条虚拟的总线platform,Linux从2.6起就加入了 platform 设备驱动,也叫平台设备总线驱动。
platform驱动模型介绍
在内核中,platform总线是bus_type的一个具体实例 ,platform_bus_type就是platform平台总线。
struct bus_type 结构体:
struct bus_type platform_bus_type = {
.name = "platform", //总线名字
.dev_attrs = platform_dev_attrs, //设备属性、含获取sys文件名
.match = platform_match, //匹配设备和驱动
.uevent = platform_uevent, //消息传递
.pm = &platform_dev_pm_ops,
};
这条总线上维护这两条链表:dev(设备)链表,drv(驱动)链表,在platform_match函数中定义。
dev链表中的节点保存设备信息:struct platform_device。
struct platform_device {
const char * name;//设备的名字,用于和驱动进行匹配的
int id; //用来区分同名设备
struct device dev;//内核中维护的所有的设备必须包含该成员
u32 num_resources;//资源个数
struct resource * resource;//硬件信息数组(资源)
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
struct resource {
resource_size_t start; //表示资源的起始值,
resource_size_t end; //表示资源的最后一个字节的地址, 如果是中断,end和satrt相同
const char *name; // 可不写
unsigned long flags; //资源的类型
struct resource *parent, *sibling, *child;
};
// [flags] 类型,通常将该硬件使用的物理地址、中断号视为资源:
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200 //内存: 物理地址资源
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400 //中断: 中断号资源
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
drv链表中的节点保存驱动信息:struct platform_driver。
struct platform_driver {
//当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中
int (*probe)(struct platform_device *);
//设备信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
//内核维护的所有的驱动必须包含该成员,通常driver->name用于和设备进行匹配
struct device_driver driver;
//往往一个驱动可能能同时支持多个设备,这些设备的名字都放在该结构体数组中
const struct platform_device_id *id_table;//id信息
};
platform的设备节点和驱动节点匹配
我们使用platform来编写驱动,重要是需要将其中的驱动节点与相应的设备节点进行匹配形成一个完整的驱动程序。那如何进行匹配呢?
匹配的动作发生在添加设备或驱动节点的时候。
如果往drv链表中添加一个驱动节点(struct platform_driver)(或者向dev链表中添加一个设备节点(struct platform_device)),内核将会去遍历dev链表(drv链表),取出dev链表(drv链表)中的每一个节点与新加入的节点进行匹配(总线上match函数),如果匹配成功,自动调用驱动节点的probe函数,并且将设备节点的地址传递给probe函数,在probe函数中可以实现驱动。
match函数的四种匹配方式:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/*When driver_override is set,only bind to the matching driver*/
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
- OF类型的匹配(设备树采用的匹配方式):设备树中传递的设备信息和drv链表进行匹配。
- ACPI匹配方式:找到当前的driver是匹配acpi_match_table中的哪一个id。
- id_table匹配:dev节点中name和drv节点中的id_table(名字数组)进行匹配。
- name字段匹配:dev节点中name和drv节点中的name进行匹配。
OF匹配优先级最高,name匹配优先级最低。只要有一种方式匹配成功,则返回。
platform的使用
platform的管理匹配机制内核中已经实现,驱动开发时只需要往内核中添加节点即可。
需要的头文件:
#include <linux/platform_device.h>
1、设备节点
1)初始化struct platform_device 中的成员。
struct platform_device btn_dev = {
.name = "btn", //用于和drv节点匹配
.id = -1, //用于区分同名成员,没有同名成员,给-1
.resource = btn_res, //硬件信息数组首地址
.num_resources = ARRAY_SIZE(btn_res), //硬件信息数组长度
.dev = {
.platform_data = btn_info, //传递额外的数据信息
.release = btn_release, //删除硬件节点自动调用的函数(必须实现)
},
};
如果resource数组传递不了所有的设备信息,可以借助platform_data进行传递 。
2)向内核注册设备节点platform_device:
int platform_device_register(struct platform_device *pdev);
3)向内核注销设备节点platform_device:
void platform_device_unregister(struct platform_device *pdev);
2、驱动节点
1)初始化struct platform_driver中的成员。
struct platform_driver btn_drv = {
.id_table = btn_table, //数组首地址,数组以""结束,用于名字匹配,优先级高于name
.probe = btn_probe, //匹配成功自动调用的函数
.remove = btn_remove, //解除匹配自动调用的函数
.driver = {
.name = "btn", //用于匹配的名称
},
};
前面说过设备节点和驱动节点通过match()函数来匹配有四种方式,一般都是实现后面两种中的一种方式,所以 id_table和driver.name选择其中一种方式就行。
2)向内核注册驱动节点platform_driver:
int platform_driver_register(struct platform_driver *drv);
3)向内核注销设备节点platform_device:
void platform_driver_unregister(struct platform_driver *drv);
3、获取设备资源
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
参数:
dev:设备节点地址
type:获取的资源类型(resource中的flags)
num:不同资源类型中的第几个资源
返回资源的地址,失败返回NULL
4、platform的按键驱动
把上一篇等待队列的按键驱动(等待队列按键驱动)按照platform规则来实现驱动的分离与分层。
GPIO头文件:
#ifndef _GPIO_BTN_H_
#define _GPIO_BTN_H_
struct btn_dest{
int gpio;//gpio端口号
char *name;//名称
char code;//键值(代表哪个按键)
};
#endif
设备节点btn_dev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <mach/platform.h>
#include "gpio_btn.h"
//使用platform_data传递额外数据
//定义btn的硬件信息
struct btn_dest btn_info[] = {
[0] = {
.gpio = PAD_GPIO_A+28,
.name = "K2",
.code = 0x50,
},
[1] = {
.gpio = PAD_GPIO_B+9,
.name = "K6",
.code = 0x60,
},
[2] = {
.gpio = PAD_GPIO_B+30,
.name = "K3",
.code = 0x70,
},
[3] = {
.gpio = PAD_GPIO_B+31,
.name = "K4",
.code = 0x80,
}
};
//硬件信息数组
struct resource btn_res[] = {
[0] = {
.start = PAD_GPIO_A+28,
.end = PAD_GPIO_A+28,
.flags = IORESOURCE_IO,
},
[1] = {
.start = PAD_GPIO_B+9,
.end = PAD_GPIO_B+9,
.flags = IORESOURCE_IO,
},
[2] = {
.start = PAD_GPIO_B+30,
.end = PAD_GPIO_B+30,
.flags = IORESOURCE_IO,
},
[3] = {
.start = PAD_GPIO_B+31,
.end = PAD_GPIO_B+31,
.flags = IORESOURCE_IO,
}
};
void btn_release(struct device *dev)
{
printk("enter btn_release!\n");
}
//分配初始化platform_device
struct platform_device btn_dev = {
.name = "btn",
.id = -1,
.resource = btn_res,
.num_resources = ARRAY_SIZE(btn_res),
.dev = {
.platform_data = btn_info,//额外数据
.release = btn_release,
},
};
//加载函数
int platform_dev_init(void)
{
return platform_device_register(&btn_dev);
}
//卸载函数
void platform_dev_exit(void)
{
platform_device_unregister(&btn_dev);
}
//声明为模块的入口和出口
module_init(platform_dev_init);
module_exit(platform_dev_exit);
MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("btn device module!");//描述信息
驱动节点btn_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <asm/gpio.h>
#include <mach/soc.h>
#include <mach/platform.h>
#include <linux/miscdevice.h>
#include <linux/semaphore.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include "gpio_btn.h"
//信号量
struct semaphore sem;
//内核定时器
struct timer_list btn_timer;
//声明等待队列头
wait_queue_head_t wqh;
//代表键值和状态
char key = 0;
//按键事件发生标志
int flag = 0;//默认没有发生0-没有 1-发生
int btn_open(struct inode *inode, struct file *filp)
{
printk("enter btn_open!\n");
if(down_trylock(&sem))
return -EBUSY;
flag = 0;
return 0;
}
ssize_t btn_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
if(size!=1){
return -EINVAL;
}
//阻塞等待按键事件
if(wait_event_interruptible(wqh, flag==1))
return -EINTR;//被信号打断
//上报键值和状态
if(copy_to_user(buf, &key, size))
return -EFAULT;
//上报完数据flag清0
flag = 0;
return size;
}
int btn_release(struct inode *inode, struct file *filp)
{
printk("enter btn_release!\n");
up(&sem);
return 0;
}
//声明操作函数集合
struct file_operations btn_fops = {
.owner = THIS_MODULE,
.open = btn_open,
.read = btn_read,
.release = btn_release,//对应用户close接口
};
//分配初始化miscdevice
struct miscdevice btn_dev = {
.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号
.name = "btn",//设备文件名
.fops = &btn_fops,//操作函数集合
};
//超时处理函数--- 真实按键事件
void btn_timer_function(unsigned long data)
{
struct btn_dest *pdata = (struct btn_dest *)data;//引脚数据
//区分按下松开
//设置键值和状态
key = pdata->code|gpio_get_value(pdata->gpio);
flag = 1;
//唤醒睡眠的进程
wake_up_interruptible(&wqh);
}
//中断处理函数
irqreturn_t btn_handler(int irq, void *dev_id)
{
//设置超时处理函数的参数
btn_timer.data = (unsigned long)dev_id;
//重置定时器--- 10ms超时
mod_timer(&btn_timer, jiffies+msecs_to_jiffies(10));
return IRQ_HANDLED;//处理成功
}
//匹配成功调用的函数
int btn_probe(struct platform_device *pdev)
{
int ret,i,j;
struct resource *res = NULL;//硬件信息
//额外信息
struct btn_dest *pdata = (struct btn_dest *)(pdev->dev.platform_data);
//注册miscdevice
ret = misc_register(&btn_dev);
if(ret<0){
printk("misc_register faibtn!\n");
goto failure_misc_register;
}
//申请中断
for(i=0;i<pdev->num_resources;i++){
//获取resource
res = platform_get_resource(pdev, IORESOURCE_IO, i);
//申请中断
ret = request_irq(gpio_to_irq(res->start), //中断号
btn_handler, //中断处理函数
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, //中断标志,包括触发方式上升下降沿触发
pdata[i].name, //中断名称
&pdata[i]);//传递给中断处理函数的参数
if(ret<0){
printk("request_irq failed!\n");
goto failure_request_irq;
}
}
//初始化信号量
sema_init(&sem, 1);
//初始化等待队列
init_waitqueue_head(&wqh);
//初始化定时器
init_timer(&btn_timer);
btn_timer.function = btn_timer_function;
printk("btn init!\n");
return 0;
failure_request_irq:
for(j=0;j<i;j++){
free_irq(gpio_to_irq(res->start), &pdata[j]);
}
misc_deregister(&btn_dev);
failure_misc_register:
return ret;
}
//解除匹配调用的函数
int btn_remove(struct platform_device *pdev)
{
int i;
struct resource *res = NULL;//硬件信息
//额外信息
struct btn_dest *pdata = (struct btn_dest *)(pdev->dev.platform_data);
del_timer(&btn_timer);
//释放所有申请的中断
for(i=0;i<pdev->num_resources;i++){
//获取resource
res = platform_get_resource(pdev, IORESOURCE_IO, i);
free_irq(gpio_to_irq(res->start), &pdata[i]);
}
//注销miscdevice
misc_deregister(&btn_dev);
return 0;
}
//名字数组
struct platform_device_id btn_table[] = {
{"btn"},
{"btn1"},
{"btn2"},
{""}//结束成员,根据match中id_table 匹配的源码决定
}; //需要有一个空字符串来表示结束,不然会非法访问
//分配初始化platform_driver
struct platform_driver btn_drv = {
.id_table = btn_table,
.probe = btn_probe,
.remove = btn_remove,
.driver = {
.name = "btn",
},
};
//加载函数
int platform_drv_init(void)
{
return platform_driver_register(&btn_drv);
}
//卸载函数
void platform_drv_exit(void)
{
platform_driver_unregister(&btn_drv);
}
//声明为模块的入口和出口
module_init(platform_drv_init);
module_exit(platform_drv_exit);
MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("btn driver module!");//描述信息
Makefile
ifeq ($(KERNELRELEASE),)
#内核源代码路径
KERNELDIR ?= /home/xin/6818GEC/kernel
#交叉编译器路径
CROSS_PATH := /home/xin/6818GEC/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
#模块源代码路径
PWD := $(shell pwd)
default:
$(MAKE) CROSS_COMPILE=$(CROSS_PATH) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko *.mod .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
#obj-m表示编译生成可加载模块,obj-y表示直接将模块编译进内核。
obj-m := btn_drv.o btn_dev.o
endif
如图所示,只有当设备节点与驱动节点都加载到内核中,才会匹配成功。而无论哪个节点从内核中卸载,就会导致匹配失败。
这样我们就相当于把软硬件实现了分离,之后移植到别的平台,也只需要修改硬件部分的内容。
好了,以上就是platform的全部被内容了,如果有什么疑问和建议欢迎在评论区中提出来喔。