平台总线是linux2.6内核加入的一种虚拟总线,使用流程:
1、定义设备
2、注册设备
3、定义驱动
4、注册驱动
总线上的设备和驱动相互匹配由总线来完成。
一、定义设备
平台设备描述结构: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; };
重要成员:
name:设备名
struct resource:设备资源(包括中断号、寄存器等)
id:设备id
num_resources:硬件占有资源的数量
struct resource { resource_size_t start; resource_size_t end; const char *name; unsigned long flags; struct resource *parent, *sibling, *child; };
start和end的意义由flags来定,flags代表硬件资源的类型,linux规定了如下类型
/* * IO resources have these defined flags. */ #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */ #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */ #define IORESOURCE_IO 0x00000100 #define IORESOURCE_MEM 0x00000200 #define IORESOURCE_IRQ 0x00000400 #define IORESOURCE_DMA 0x00000800 #define IORESOURCE_BUS 0x00001000 #define IORESOURCE_PREFETCH 0x00002000 /* No side effects */ #define IORESOURCE_READONLY 0x00004000 #define IORESOURCE_CACHEABLE 0x00008000 #define IORESOURCE_RANGELENGTH 0x00010000 #define IORESOURCE_SHADOWABLE 0x00020000 #define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */ #define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */ #define IORESOURCE_MEM_64 0x00100000 #define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */ #define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */ #define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */ #define IORESOURCE_DISABLED 0x10000000 #define IORESOURCE_UNSET 0x20000000 #define IORESOURCE_AUTO 0x40000000 #define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
例如当flags取IORESOURCE_MEM时,start、end代表硬件占有的首末内存地址;当flags取IORESOURCE_IRQ时,start、end则代表硬件占有的首末中断号。
二、注册平台设备
函数原型:int platform_device_register(struct platform_device *pdev)
以mini2440键盘为例,定义并注册设备代码如下:
#include <linux/module.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #define GPGCON 0x56000060 #define GPGDAT 0x56000064 struct resource key_resource[] = { [0] = { .start = GPGCON, .end = GPGCON + 8, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_EINT8, .end = IRQ_EINT19, .flags = IORESOURCE_IRQ, }, }; struct platform_device key_device = { .name = "my key", .id = 0, .num_resources = 2, //硬件占有寄存器和中断号两个资源 .resource = key_resource, }; static int key_dev_init() { /*注册平台设备*/ platform_device_register(&key_device); return 0; } void key_dev_exit() { /*注销平台设备*/ platform_device_unregister(&key_device); } MODULE_LICENSE("GPL"); module_init(key_dev_init); module_exit(key_dev_exit);
三、定义驱动
平台驱动描述结构:struct 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 (*resume)(struct platform_device *); struct device_driver driver; const struct platform_device_id *id_table; };
重要成员:
name:驱动名称 (仍采取设备名称和驱动名称来匹配,所以二者一定要一致)
probe函数: 匹配成功后调用此函数
remove函数:设备移除后执行此函数
四、注册驱动平台驱动
函数原型:int platform_driver_register(struct platform_driver *drv)
将按键驱动程序由混杂设备驱动优化为平台总线驱动。之前的驱动程序都包含了硬件资源的信息,如GPGCON、GPGDAT寄存器的地址,各个按键的中断号等,移植性不高。优化过后,这些硬件信息都从匹配到的设备的platform_device结构中去取,从而将驱动和设备彻底分开了。从匹配到的设备中取硬件信息使用platform_get_resource函数
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num) { int i; for (i = 0; i < dev->num_resources; i++) { struct resource *r = &dev->resource[i]; if (type == resource_type(r) && num-- == 0) return r; } return NULL; }
该函数遍历设备资源后返回与type指定类型相同的资源。
优化后代码如下
key.h:
#include <linux/module.h> #include <linux/init.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/sched.h> #include <linux/platform_device.h> /*存储键盘控制寄存器基地址*/ unsigned int *key_base; /*定义工作项结构体*/ struct work_struct *work1; /*定义定时器变量结构体*/ struct timer_list key_timer; unsigned int key_num = 0; /*定义等待队列*/ wait_queue_head_t key_queue; struct resource *res_irq; struct resource *res_mem; /*open函数接口*/ int key_open(struct inode *node, struct file *filp) { return 0; } /*read函数接口*/ ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos) { wait_event(key_queue,key_num); copy_to_user(buf,&key_num,4); /*清空按键键值*/ key_num = 0; return 4; } /* 函数映射关系表*/ struct file_operations key_fops = { .open = key_open, .read = key_read, //.unlocked_ioctl = key_ioctl, }; /*字符设备描述结构*/ struct miscdevice key_miscdev = { .minor = 200, .name = "key", .fops = &key_fops, };
驱动程序:
#include "key.h" /******************** 函数名:work1_func 参数:无 返回值:无 函数功能:实现工作项 结构体中的func成员 ********************/ void work1_func() { /*启动定时器*/ mod_timer(&key_timer,jiffies + (HZ/10)); } /************************ 函数名:que_init 参数:无 返回值:无 函数功能:创建一个工作项 *************************/ int que_init() { /*创建工作*/ work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL); INIT_WORK(work1, work1_func); } /************************ 函数名:key_int 参数:无 返回值:0 函数功能:按键中断处理函数 *************************/ irqreturn_t key_int(int irq, void *dev_id) { /*1、检测设备是否产生中断*/ /*2、清除中断产生标志*/ /*3、提交下半部分工作*/ schedule_work(work1); return 0; } /************************ 函数名:key_hw_init 参数:无 返回值:无 函数功能:初始化与按键相关 的寄存器 *************************/ void key_hw_init() { unsigned int data; data = readw(key_base); data &= ((3)|(3<<6)|(3<<10)|(3<<12)|(3<<14)|(3<<22));//~(0b11); data |= (2|(2<<6)|(2<<10)|(2<<12)|(2<<14)|(2<<22));//0b10; writew(data,key_base); } /************************ 函数名:key_timer_func 参数:无 返回值:无 函数功能:定时器超时处理函 数,达到规定时间执行此函数 *************************/ void key_timer_func() { unsigned int key_val,i; for(i = 0;i < 15;i++) { if((i == 0)||(i == 3)||(i == 5)||(i == 6)||(i == 7)||(i == 11)) { key_val = readw(key_base + 1) & (1 << i); if(key_val == 0) key_num = i+1; } } wake_up(&key_queue); } static int __devinit key_probe(struct platform_device *pdev) { int size,ret; /*注册设备*/ ret = misc_register(&key_miscdev); /*从匹配到的设备中取出设备硬件信息*/ res_irq = platform_get_resource(pdev,IORESOURCE_IRQ,0); res_mem = platform_get_resource(pdev,IORESOURCE_MEM,0); /*注册中断*/ request_irq(res_irq->start,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start + 3,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start+5,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start+6,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start+7,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->end,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); size = res_mem->end - res_mem->start + 1; key_base = ioremap(res_mem->start,size); /*硬件初始化*/ key_hw_init(); /*工作队列初始化*/ que_init(); /*初始化定时器*/ init_timer(&key_timer); key_timer.function = key_timer_func; /*注册定时器*/ add_timer(&key_timer); /*初始化等待队列*/ init_waitqueue_head(&key_queue); return ret; } static int key_remove(struct platform_device *pdev) { /*注销设备*/ misc_deregister(&key_miscdev); /*注销中断*/ free_irq(IRQ_EINT8, 0); } /*定义平台总线驱动*/ struct platform_driver key_driver = { .driver = { .name = "my key", }, .probe = key_probe, .remove = key_remove, }; static int button_init() { /*注册平台驱动*/ platform_driver_register(&key_driver); printk("key.ko is ready\n"); return 0; } static void button_exit() { /*注销平台驱动*/ platform_driver_unregister(&key_driver); } MODULE_LICENSE("GPL"); module_init(button_init); module_exit(button_exit);
这次的随笔内容大致这么多,这里我有一个小问题想不明白,就是platform_get_resource这个函数的第三个参数num意义何在?我查阅linux内核代码发现大多数用到这个函数的地方将num赋为0,表示不知道为什么。如果有知道的麻烦告诉我,感激不尽~
此代码适用mini2440开发板,不同型号开发板IO口和中断号不同。如果有疑问或建议,欢迎指出。