引言
分层就是将一个复杂的工作分成了4层, 分而做之,降低难度。每一层只专注于自己的事情, 系统已经将其中的核心层和事件处理层写好了,所以我们只需要来写硬件相关的驱动层代码即可。
分离是指把硬件相关的部分(驱动层)从纯软件部分(事件处理层)抽离出来,使我们只需要关注硬件相关部分代码的编写。具体来说就是在驱动层中使用platform机制(将所有设备挂接到一个虚拟的总线上,方便sysfs节点和设备电源的管理,使得驱动代码,具有更好的扩展性和跨平台性,就不会因为新的平台而再次编写驱动)把硬件相关的代码(固定的,如板子的网卡、中断地址)和驱动(会根据程序作变动,如点哪一个灯)分离开来,即要编写两个文件:dev.c和drv.c(platform设备和platform驱动)
![](https://i-blog.csdnimg.cn/blog_migrate/eb470926562d76d7ce04cc81609ec73b.png)
接下来我们来分析platform机制以及分离概念。
1、platform机制
![](https://i-blog.csdnimg.cn/blog_migrate/043ab0bc5d0d0f7637b895dcce8d9a57.png)
-
device设备:挂接在platform总线下的设备,属于platform_device结构体类型;
platform_device结构体定义如下
struct platform_device { const char* name; //设备名称,要与platform_driver的name一样,这样总线才能匹配成功 u32 id; //id号,插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1 struct device dev; //内嵌的具体的device结构体,其中成员platform_data,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等) u32 num_resources; //资源数量, struct resource * resource; //资源结构体,保存设备的信息 };
resource结构体定义如下
struct resource { resource_size_t start; //起始资源,如果是地址的话,必须是物理地址 resource_size_t end; //结束资源,如果是地址的话,必须是物理地址 const char *name; //资源名 unsigned long flags; //资源的标志 //比如IORESOURCE_MEM,表示地址资源, IORESOURCE_IRQ表示中断引脚... ... struct resource *parent, *sibling, *child; //资源拓扑指针父、兄、子,可以构成链表 };
涉及到的函数如下(在dev设备的入口出口函数中用到)
int platform_device_register(struct platform_device * pdev);//注册dev设备 int platform_device_unregister(struct platform_device * pdev);//注销dev设备
-
driver驱动:也是挂接在platform总线下,与某类设备相应的驱动程序,属于platform_driver结构体类型;
platform_driver(驱动)结构体的定义
struct platform_driver { 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 (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *); //唤醒 struct device_driver driver; //内嵌的driver,其中的name成员要等于设备的名称才能匹配 };
定义一个platform_driver(驱动)示例
struct platform_driver gpio_keys_device_driver = { .probe = gpio_keys_probe, //设备的检测,当匹配成功就会调用这个函数(需要自己编写) .remove = __devexit_p(gpio_keys_remove), //删除设备(需要自己编写) .driver = { .name = "gpio-keys", //驱动名称,用来与设备名称匹配用的 } };
涉及到的函数如下
//位于init入口函数中,用来注册driver驱动 int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; //(1)挂接到虚拟总线platform_bus_type上 if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; if (drv->suspend) drv->driver.suspend = platform_drv_suspend; if (drv->resume) drv->driver.resume = platform_drv_resume; return driver_register(&drv->driver); //注册到driver目录下 } int platform_driver_unregister(struct platform_driver *drv); //位于exit出口函数中,用来卸载驱动 struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num); //获取设备的某个资源,获取成功,则返回一个resource资源结构体 //参数: // *dev :指向某个platform device设备 // type:获取的资源类型 // num: type资源下的第几个数组
-
platform总线:属于虚拟设备总线(全局变量),属于platform_bus_type类型,正是通过这个总线将设备和驱动联系了起来,属于Linux中bus的一种。
platform_match()匹配函数
static int platform_match(struct device * dev, struct device_driver * drv) { /*找到所有的device设备*/ struct platform_device *pdev = container_of(dev, struct platform_device, dev); return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); //找BUS_ID_SIZE次 }
通过查看
/sys/bus/platform/
目录,可以看到两个文件:devices和drivers。它们分别用来存放platform的设备和驱动。无论驱动或设备,只要有一方注册,就会调用platform_bus_type的.match匹配函数来寻找对方。如果匹配成功,就调用driver驱动结构体里的.probe函数来使总线将设备和驱动联系起来。
platform_bus_type的结构体定义如下所示(位于drivers/base):
struct bus_type platform_bus_type = { .name = "platform", //设备名称 .dev_attrs = platform_dev_attrs, //设备属性、含获取sys文件名,该总线会放在/sys/bus下 .match = platform_match, //匹配设备和驱动,匹配成功就调用driver的.probe函数 .uevent = platform_uevent, //消息传递,比如热插拔操作 .suspend = platform_suspend, //电源管理的低功耗挂起 .suspend_late = platform_suspend_late, .resume_early = platform_resume_early, .resume = platform_resume, //恢复 };
2、利用platform机制,编写LED驱动层
- 环境:JZ2440开发板V3 + linux内核3.4.2 + arm-linux-gcc 4.3.2
- 参考:韦东山第2期视频
2.1 创建设备代码
- 创建led_dev.c文件:用来指定灯的引脚地址,当更换平台时只需要修改这个就行。
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
static struct resource led_resource[] = { //资源数组,用来保存设备的信息
[0] = {
.start = 0x56000050, //led的寄存器GPFCON起始地址
.end = 0x56000050 + 8 - 1, // led的寄存器GPFDAT结束地址
.flags = IORESOURCE_MEM, //表示地址资源
},
[1] = {
.start = 5, //表示GPF第几个引脚开始
.end = 5, //结束引脚
.flags = IORESOURCE_IRQ, //表示中断资源
}
};
//释放函数
static void led_release(struct device * dev)
{
//释放函数,必须向内核提供一个release函数, 否则卸载时,内核找不到该函数会报错。
}
/*分配、设置一个LED设备 */
static struct platform_device led_dev = {
.name = "myled", //对应的platform_driver驱动的名字
.id = -1, //表示只有一个设备
.num_resources = ARRAY_SIZE(led_resource), //资源数量,ARRAY_SIZE()函数:获取数量
.resource = led_resource, //资源数组led_resource
.dev = {
.release = led_release,
},
};
//入口函数,注册dev设备
static int led_dev_init(void)
{
platform_device_register(&led_dev);
return 0;
}
//出口函数,注销dev设备
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init); //修饰入口函数
module_exit(led_dev_exit); //修饰出口函数
MODULE_LICENSE("GPL"); //声明函数
2.2 创建驱动代码
-
创建led_drv.c文件:用来初始化灯以及如何控制灯的逻辑,当更换控制逻辑时,只需要修改这个就行。
1. 分配、设置一个platform_driver结构体
#include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> /* 1)先写要注册的led驱动:platform_driver结构体*/ /*函数声明*/ static int led_remove(struct platform_device *led_dev); static int led_probe(struct platform_device *led_dev); struct platform_driver led_drv = { .probe = led_probe, //当与设备匹配,则调用该函数 .remove = led_remove, //删除设备 .driver = { .name = "myled", //与设备名称一样 } };
编写file_operations 结构体以及成员函数(.open、.write)
static struct class *cls; //类,用来注册,和注销 static volatile unsigned long *gpio_con; //被file_operations的.open函数用 static volatile unsigned long *gpio_dat; //被file_operations的.write函数用 static int pin; //LED位于的引脚值 static int led_open(struct inode *inode, struct file *file) { *gpio_con &= ~(0x03<<(pin*2)); *gpio_con |= (0x01<<(pin*2)); return 0; } static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { char val=0; if(count!=1) return -EINVAL; copy_from_user(&val,buf,count); //从用户(应用层)拷贝数据 if(val) //开灯 { *gpio_dat &= ~(0x1<<pin); } else { *gpio_dat |= (0x1<<pin); } return 0 ; } static struct file_operations led_fops= { .owner = THIS_MODULE, //被使用时阻止模块被卸载 .open = led_open, .write = led_write, };
编写.probe函数
/* 当驱动和设备都insmod加载后,然后bus总线会匹配成功,就进入.probe函数. * 里面的内容没有固定要求,你可以根据需要自行添加。 */ static int led_probe(struct platform_device *pdev) { printk("enter probe\n"); //调试用 /* 1.根据platform_device的资源进行ioremap */ struct resource *res; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取LED寄存器地址 gpio_con = ioremap(res->start, res->end - res->start + 1); //获取虚拟地址 gpio_dat = gpio_con + 1; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //获取LED引脚值 pin = res->start; /* 2.注册字符设备驱动程序 */ major = register_chrdev(0, "myled", &led_fops); //赋入file_operations结构体 cls = class_create(THIS_MODULE, "myled"); device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */ return 0; }
编写.remove函数
/* 如果驱动与设备已联系起来,当卸载驱动时,就会调用.remove函数卸载设备 */ static int led_remove(struct platform_device *pdev) { printk("enter remove\n"); //调试用 /* 1.卸载字符设备驱动程序 */ device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "myled"); iounmap(gpio_con); //注销虚拟地址 return 0; }
编写drv的入口出口函数
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");
2.3 创建Make file 文件
KERN_DIR = /home/leon/linux-3.4.2 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += led_drv.o obj-m += led_dev.o
2.4 创建测试文件
当用户输入led_test on
时,LED点亮;输入led_test off
时,LED熄灭。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> /* led_test on * led_test off */ int main(int argc, char **argv) { int fd; char val = 1; fd = open("/dev/led", O_RDWR); if (fd < 0) { printf("can't open!\n"); } if (argc != 2) { printf("Usage :\n"); printf("%s <on|off>\n", argv[0]); return 0; } if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; } write(fd, &val, 1); //将从变量val地址起始处的4个字节传给驱动 return 0; }
2.5 实验结果
![](https://i-blog.csdnimg.cn/blog_migrate/c075bfad6a73546c0547ba22c5dca41f.png)
3、小结:
![](https://i-blog.csdnimg.cn/blog_migrate/550721851a3e223a387a5db8ee6d47a1.jpeg)