先介绍一下驱动的分层/分离。如下图
input.c为应用程序提供了接口,是核心层,而在核心层下面的那一层由两方面组成,一个是纯软件的,里面是很稳定的代码,还有一个是与硬件相关的代码,一般只需要通过修改与硬件相关的代码而达到我们的目的。
再介绍一个概念,总线-驱动-设备模型。如下图
bus、driver、device实际上都只是一个结构体。
左边的device是与硬件相关的代码,实现下面的功能:
①通过device_add把device放入bus的dev链表中;
②从bus的drv链表中取出每一个drv,用bus的match函数判断drv能否支持dev;
③若可以支持,调用drv的probe函数。
而右边的driver是比较稳定的代码,实现下面的功能:
①通过driver_register把driver放入bus的drv链表中;
②从bus的dev链表中取出每一个dev,用bus的match函数(通过名字)判断drv能否支持dev;
③若可以支持,调用probe函数。
但要注意的是,这只不过是一种使左右两边建立联系的机制,在probe函数中做什么由你自己决定,或者注册一个字符设备,或者注册一个input_dev结构体,都可以。
下面介绍一下具体怎么实现,先从device开始。
首先要注册一个平台设备
static struct platform_device led_dev = {
.name = "myled",
.id = -1,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
};
接下来就是对resource的设定
static struct resource led_resource[] = {
[0] = {//代表寄存器的地址
.start = 0x56000010,
.end = 0x56000010 + 8 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {//代表选定的寄存器的具体哪一位,这里选择了pin5
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
.dev = {
.release = led_release,
},
};
其中的flag在linux/ioport.h中定义。
当然还要在入口函数里注册平台设备:platform_device_register(&led_dev);
出口函数里卸载平台设备:platform_device_unregister(&led_dev);
还要用module_init对入口和出口函数进行修饰。
这样,一个平台设备就完成了。
接下来要注册一个平台驱动。
首先要定义一个平台驱动
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
要注意的是,.name一定要和之前注册的平台设备相同,这样才能通过match函数找到彼此。至于入口函数、出口函数和修饰,与之前的平台设备基本一致。
接下来就是构造led_probe和led_remove了,先从简单的做起,在这两个函数里我们先不实现点灯的功能,只是打印一些信息
static int led_probe(struct platform_device * pdev)
{
printk("led_probe,found led\n");
return 0;
}
static int led_remove(struct platform_device * pdev)
{
printk("led_remove,remove led\n");
return 0;
}
这样,我们就完成了一个简单的例子,把平台驱动和平台设备编译加载,可以看到打印出“led_probe,found led”,然后卸载的时候又打印出“led_remove,remove led”。与预期一致。
接下来加入复杂的代码。
在probe函数中,先要根据platform_device的资源进行ioremap,也就是得到之前在平台设备里的led_resource[],这就要用platform_get_resource(),然后再用ioremap得到具体的寄存器地址以及引脚。具体的代码如下:
struct resource *res;
/*根据platform_device的资源进行ioremap*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio_con = ioremap(res->start,res->end - res->start +1);
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
pin = res->start;
然后还需要在probe函数里注册字符设备驱动,这都是老一套了,跟以前我们学的过程完全一样(先构造一个file_operations结构体,然后设置系统自动分配主设备号……)具体的代码如下:
major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "myled");
接下来就是remove函数了,过程基本与probe的注册过程相反:
class_device_destroy(cls,MKDEV(major, 0));// /dev/xyz
class_destroy(cls);
unregister_chrdev(major, "myled");
iounmap(gpio_con);
至于在file_operations结构体定义的open和write函数的实现就不具体说明了,跟以前的一模一样。
由此,我们就完成了平台设备和平台驱动的构建。至于测试程序,完全使用第一个驱动程序的测试程序就行。
倘若我们想要修改led,只需要在平台设备里对resource进行修改就行了。