这篇文章中介绍Linux总线设备驱动框架,重点是体会整个框架,特别是感受一下分离的思想和抽象的方法,很多结构体中的成员不必深究到底如何实现或者有何作用,我将在今后的文章中深入探讨。
参考资料:
嵌入式Linux驱动学习-6.platform总线设备驱动模型
1. 分离思想
回顾第一篇,最简单的驱动框架:
如下,在驱动程序中直接把资源(IO引脚)写死
static unsigned long gpio_va;
#define GPIO_OFT(x) ((x) - 0x56000000)
#define GPFCON (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000050)))
#define GPFDAT (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000054)))
有单片机基础,或者c程序写得多的朋友可能就发现这种写法的缺陷了:
当程序简单时还好,程序复杂了,又需要换硬件时,将会变得难以修改。所以需要将硬件相关部分的代码给抽离开,这就叫分离,硬件层一部分代码,驱动层一部分代码,在单片机中,常见的处理方法是利用单独的头文件定义引脚或者其他功能,比写在一个文件中好一些,但是如果有修改还是需要整个程序重新编译。
Linux系统中采用总线设备驱动模型,完美解决了这些问题。
引入platform_device/platform_driver,将“硬件资源”与“驱动”分离开。
同样以led驱动为例,我们编写两个源文件,led_drv.c表示驱动,led_dev.c表示设备(资源),编译生成led_drv.ko和led_dev.ko两个文件,insmod后即可正常使用,当我们需要修改硬件时,只需要rmmod led_dev,然后重新加载即可,而不需要卸载修改led_drv。
2. platform_device
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);
先从入口看,和第一篇的基本驱动一样,只不过入口从register_chrdev变成了注册platform_device。
platform_device_register(&led_dev)函数中的led_dev是一个platform_device结构体
static struct platform_device led_dev = {
.name = "myled",
.id = -1,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
.dev = {
.release = led_release,
},
};
结构体原型:
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
第一项:name,取个名字
第二项:id,设备实例号,或者用“-1”表示只有一个实例。
第三项:dev,一个device结构体,这里实现一个空函数,不会报警告就行,其他成员用不到。之后会出文章细讲。
.dev = {
.release = led_release,
},
static void led_release(struct device * dev)
{
}
第四项:num_resources,资源的数目,ARRAY_SIZE(led_resource)返回资源数目
第五项:resource,资源数组
使用resource 结构体实现一个资源数组,数组内容就是我们的硬件资源
start为起始,end为结束,flags为资源类型,我的开发板GPF4、GPF5、GPF6为三个led灯
GPFCON为0x56000050,GPFDAT为0x56000054,定义如下资源,实际上有第一个就能控制所有灯,这里这样写是为了突出展示使用资源数组的作用和便捷性。
static struct resource led_resource[] = {
{
.start = 0x56000050,
.end = 0x56000050 + 8 - 1,
.flags = IORESOURCE_MEM,
},
{
.start = 4,
.flags = IORESOURCE_IO,
},
{
.start = 5,
.flags = IORESOURCE_IO,
},
{
.start = 6,
.flags = IORESOURCE_IO,
},
};
3. platform_driver
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);
一样从入口看,注册platform_driver。
platform_driver_register(&led_drv)函数中的led_drv是一个platform_driver结构体
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
结构体原型:
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;
};
以后出文章细讲这个结构体,这里只需要几个基本成员:
1.probe,探针,在这个函数中要获取资源、注册字符设备,创建设备等
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
led_num=0;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;
major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled");
while(1)
{
res = platform_get_resource(pdev, IORESOURCE_IO, led_num);
if(res==NULL)
break;
led_num++;
pin[led_num] = res->start;
printk("led_probe, found led%d\n",led_num);
class_device_create(cls, NULL, MKDEV(major, led_num), NULL, "led%d",led_num); /* /dev/led */
}
return 0;
}
使用platform_get_resource获取资源,pin[]数组中保存了引脚的值,然后循环创建设备节点,有几个led资源就创建几个led节点。
static int led_open(struct inode *inode, struct file *file)
{
int minor;
minor=iminor(inode);
led_init(minor);
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
int minor = iminor(file->f_path.dentry->d_inode);
printk("write led%d\n",minor);
copy_from_user(&val, buf, count); // copy_to_user();
led_ctl(minor,int);
return 0;
}
file_operations结构体中的open和write和第一篇其实是差不多的,区别在于把初始化操作封装进了led_init()函数,把控制操作封装进了led_ctl()函数,并通过次设备号来区分对哪一个led进行操作。
从inode结构体中获取次设备号:
minor=iminor(inode);
从file结构体中获取次设备号:
int minor = iminor(file->f_path.dentry->d_inode);
2.remove,这个函数表示移除,销毁设备
static int led_remove(struct platform_device *pdev)
{
int i;
i=0;
for(i=0;i<led_num;i++)
{
class_device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
unregister_chrdev(major, "myled");
iounmap(gpio_con);
return 0;
}
3.driver,这个结构体中的名字要与platform_device中的name对应,这样platform_dev与platform_drv才能对应起来。
insmod led_drv.ko没有反应很正常
insmod led_dev.ko后打印出了找到三个设备说明platform_dev和platform_drv对应上了
再通过命令看一下设备节点是否创建成功
接下来便可通过应用程序控制着三个led了。
./ledtest /dev/led1 on使led1亮,以此类推,应用程序源码和第一篇中的基本差不多。
这篇文章讲得不是很细致,因为重点其实就是感受一下分层思想,并且了解linux系统中一些抽象出来的结构体,实际上手工作,你在源码中搜索,便可以找到很多可以参考的代码,随着工作深入,自然会对这些结构体熟悉。今后我也将深入分析部分结构体。