老查的ARM学习笔记:chapter-2(linux总线设备驱动详解)
来源:互联网 发布:软件研究所 导师 编辑:程序博客网 时间:2024/06/14 21:45
1 总线设备驱动模型概述
随着技术的不断进步,系统的拓扑结构也越来越复杂,对智能电源管理,热插拔的支持要求也越来越高,2.3内核已经难以满足这些要求,为了适应这种形势需要,linux2.6内核提供了全新的内核设备模型。
总线的作用就是感知设备是否连接上usb,网卡等等,总线设备驱动模型更好的支持热插拔的设备,也更好的提供移植性。
在设备模型中,我们将看到,设备驱动主要是由总线,驱动程序,设备三个部分构成,通过这三个标准部件,把各种纷繁杂乱的设备归结过来,达到简化设备驱动编写的目的,下面我们就将按三个部分总线,驱动和设备来学习一下。
2 总线
1 总线的描述
在Linux 内核中, 总线由bus_type 结构表示,在这个结构中最重要的是总线名称和匹配函数。
struct bus_type {const char *name; /*总线名称*/int (*match) (struct device *dev, struct device_driver *drv); /*驱动与设备的匹配函数*/………}
2 总线的注册
总线的注册使用如下函数
bus_register(struct bus_type *bus)
若成功,新的总线将被添加进系统,并可在/sys/bus 下看到相应的目录。
3 总线的注销
总线的注销使用如下函数
void bus_unregister(struct bus_type *bus)
代码例程bus.c:
#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/device.h>//for bus_typeint my_match (struct device *dev, struct device_driver *drv)//新加入的总线和新加入的设备进行匹配,若能匹配上返回非零,匹配不上返回零{ return 0;}struct bus_type my_bus_type = { //定义总线 .name="my_bus", .match=my_match, };int my_bus_init(){ int ret; ret=bus_register(&my_bus_type);//注册一个总线 return ret;}void my_bus_exit(){ bus_unregister(&my_bus_type);//注销总线}module_init(my_bus_init);module_exit(my_bus_exit);MODULE_LICENSE("GPL");
编好代码后,将bus.ko代码复制到开发板中去,加载模块后,可以通过ls /sys/bus 指令查看我们添加到linux系统中的总线
驱动程序
1 驱动的描述
在Linux内核中, 驱动由device_driver结构表示。
struct device_driver {{const char *name; /*驱动名称*/struct bus_type *bus; /*驱动程序所在的总线*/int (*probe) (struct device *dev);………}
当设备添加到总线上时,总线和设备匹配的时候会调用probe函数对设备进行相应的处理。
2 驱动的注册
驱动的注册使用如下函数
int driver_register(struct device_driver *drv)
3 驱动的注销
驱动的注销使用如下函数
void driver_unregister(struct device_driver *drv)
代码例程driver.c
#include <linux/init.h>#include <linux/module.h>#include <linux/device.h> #include <linux/kernel.h>extern struct bus_type my_bus_type; //使用外部一个模块int my_probe(struct device *dev){ printk(KERN_WARNING"driver found the device !\n"); return 0;}struct device_driver my_driver = { .name="my_dev", //这个名字很重要,要和设备的名字相同 .bus=&my_bus_type, //要挂载到的那条总线上 .probe=my_probe, //当设备名字和驱动的名字相同时,调用probe函数};int my_driver_init(){ int ret; ret = driver_register(&my_driver); //注册驱动 return ret;}void my_driver_exit(){ driver_unregister(&my_driver); //注销驱动}module_init(my_driver_init);module_exit(my_driver_exit);MODULE_LICENSE("GPL");
代码编号后,将driver.ko代码复制到开发板中去,加载insmod driver.ko模块后,可以通过ls /sys/bus 指令查看我们添加到linux系统中的总线,从实验结果看,在my_bus这条总线上,我们已经挂载上了drivers驱动了,进入drivers中,可以看到我们创建的驱动程序名称my_dev。
4 设备
1 设备的描述
在Linux内核中, 设备由struct device结构表示。
struct device {{const char *init_name; /*设备的名字*/struct bus_type *bus; /*设备所在的总线*/………}
设备的名字和驱动程序下的驱动的名字要一致。
2 设备的注册
设备的注册使用如下函数
int device_register(struct device *dev)
3 设备的注销
设备的注销使用如下函数
void device_unregister(struct device *dev)
代码例程:
device.c
#include <linux/init.h>#include <linux/module.h>#include <linux/device.h>#include <linux/kernel.h>extern struct bus_type my_bus_type; //调用bus.c中的my_bus_type总线struct device my_dev={ .init_name="my_dev", /*设备的名字,要和驱动的名字一样*/ .bus=&my_bus_type, /*设备所在的总线*/ };int my_device_init(){ int ret; ret= device_register(&my_dev); //设备的注册 return ret;}void my_device_exit(){ device_unregister(&my_dev); //设备的注销}module_init(my_device_init);module_exit(my_device_exit);MODULE_LICENSE("GPL");
bus.c
#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/device.h>//for bus_typeint my_match (struct device *dev, struct device_driver *drv)//新加入的总线和新加入的设备进行匹配,若能匹配上返回非零,匹配不上返回零{//匹配驱动模块中的名字和设备模块中的名字 //return !strncmp(dev->init_name,drv->name,strlen(drv->name)); /*在内核源码中Core.c文件中,device_register()函数中dev->init_name = NULL 会被清空, 后面匹配的时候会匹配空指针。*/ return !strncmp(dev->kobj_name,drv->name,strlen(drv->name)); /*实际上这个init_name被赋值到kobj.name中了。所以在strncmp()函数中,应该是dev->kobj.name*/ //return 0;}struct bus_type my_bus_type = { .name="my_bus", .match=my_match, };EXPORT_SYMBOL(my_bus_type); //输出模块给driver.c device.cint my_bus_init(){ int ret; ret=bus_register(&my_bus_type); return ret;}void my_bus_exit(){ bus_unregister(&my_bus_type);}module_init(my_bus_init);module_exit(my_bus_exit);MODULE_LICENSE("GPL");
driver.c
#include <linux/init.h>#include <linux/module.h>#include <linux/device.h> #include <linux/kernel.h>extern bus_type my_bus_type; //使用外部一个模块int my_probe(struct device *dev){ printk(KERN_WARNING"driver found the device it can handle!\n"); return 0;}struct device_driver = my driver{ .name="my_dev"; //这个名字很重要,要和设备的名字相同 .bus=&my_bus_type; //要挂载到的那条总线上 .probe=my_probe; //当设备名字和驱动的名字相同时,调用probe函数};int my_driver_init(){ int ret; ret = driver_register(&my_driver); //注册驱动 return ret;}void my_driver_exit(){ void driver_unregister(&my_driver); //注销驱动}module_init(my_driver_init);module_exit(my_driver_exit);MODULE_LICENSE("GPL");
Makefile
obj-m := bus.o device.o driver.oKDIR :=/home/S5-driver/lesson7/linux-ok6410all: make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=armclean: rm -f *.o *.ko *.bak
因为这里bus.c中的匹配函数修改了代码,具体见bus.c中match代码,所以这里编译好模块之后要重启开发板,将bus.ko,driver.ko 和device.ko拷贝到开发板上去。我们看到了我们期望看到的结果,”driver found the device!”,也就是说总线上的match函数将驱动和设备已经匹配上了,然后调用probe函数,就会出现我们所期望的结果。
5 平台总线设备设计
1 平台总线概述
平台总线: 平台总线(Platform bus)是linux2.6内核加入的一种虚拟总线,其优势在于采用了总线的模型对设备与驱动进行了管理,这样提高了程序的可移植性。有了平台总线后,我们就不需要自己创建总线,只要挂载到平台总线上即可。
在内核代码platform.c中有个结构struct bus_type platform_bus_type,在这个结构中有个匹配函数match,match主要是通过设备的名字和驱动的名字进行匹配的,这和前文所说的match函数是一样的,都是匹配设备的名字和驱动的名字。
2 平台总线的描述
平台设备使用struct platform_device来描述
struct platform_device {const char *name; /*设备名*/int id; /*设备编号,配合设备名使用*/struct device dev;u32 num_resources;struct resource *resource; /*设备资源,包括基地址和中断号*/}
struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags; /*资源的类型*/struct resource *parent, *sibling, *child;};
3 平台总线的注册
注册平台设备使用函数platform_device_register
int platform_device_register(struct platform_device *pdev)
4 平台总线的注销
注销平台设备使用函数 platform_device_register
int platform_device_register(struct platform_device *pdev)
结合上面的基础知识,将上节按键驱动修改为平台驱动模式,下面给出平台设备的模块代码key_dev.c
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h> //for platform_device#include <linux/interrupt.h>#define GPNCON 0x7f008830/*struct source 定义设备资源*/struct resource key_resource[] = { [0]={ //控制寄存器的基地址 .start=GPNCON, .end=GPNCON+8, .flags=IORESOURCE_MEM,/*资源类型由flags设置,寄存器属于内存地址的资源*/ }, [1]={ //中断号基地址 .start=IRQ_EINT(0), .end=IRQ_EINT(1), .flags=IORESOURCE_IRQ,/*中断号属于IORESOURCE_IRQ类型*/ }, };/*定义平台总线key_device*/struct platform_device key_device = { .name="my-key", .id=0, .num_resources = 2, //平台资源的数目 .resource = key_resource, //需要定义resource};int key_device_init(){ platform_device_register(&key_device); /*注册平台设备*/ return 0;}void key_device_exit(){ platform_device_unregister(&key_device);/*注销平台设备*/}module_init(key_device_init);module_exit(key_device_exit);MODULE_LICENSE("GPL");
将编译好的key_dev.ko模块复制到开发板中去,安装insmod key_dev.ko后,进入总线中可以看到有一条platform总线,进入这个总线中可以看到里面有devices和drivers,再进入到devices中查看发现有个设备名为my-dev.o,这就说明我们已经把这个设备添加到平台总线上去了。
6 平台驱动
1 平台驱动的描述
平台驱动使用struct platform_driver 描述:
struct platform_driver {int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);……}
2 平台驱动注册使用函数
int platform_driver_register(struct platform_driver *)
3 平台驱动的卸载
int platform_driver_unregister(struct platform_driver *)
接下来就是对按键驱动模式修改为平台驱动模式。将上节博客中的key.c复制过来修改名称key_dri.c并修改代码如下
#include<linux/module.h>#include<linux/init.h>#include<linux/miscdevice.h>#include<linux/interrupt.h>#include<linux/io.h>#include<linux/fs.h> /* for iormap */ #include<asm/uaccess.h> //for copy_to_user#include<linux/slab.h> /* for kmalloc */#include <linux/sched.h> //for wait_queue#include <linux/platform_device.h> //#define GPNCON 0x7f008830//#define GPNDAT 0x7F008834unsigned int key_num = 0;struct work_struct *work1;wait_queue_head_t key_q;//定义等待队列struct resource *res_irq;//定义一个变量可以在probe函数中从设备中取出相应的中断号struct resource *res_mem;//定义一个变量可以在probe函数中从设备中取出相应的基地址//unsigned int *gpio_data;unsigned int *key_base;//整个按键的虚拟地址struct timer_list key_timer;void key_timer_func(unsigned long data){ unsigned int key_val; //key_val = readw(gpio_data)&0x2; key_val = readw(key_base+1)&0x2; if (key_val == 0) { printk(KERN_WARNING"OK6410 S3 key down!\n"); key_num=3; } //key_val = readw(gpio_data)&0x1; key_val = readw(key_base+1)&0x1; //由于这里要读的是GPNDAT寄存器中的值,而GPNDAT的地址是GPNCON寄存器的地址+4。但是这里因为是整型int,只+1 if (key_val == 0) { printk(KERN_WARNING"OK6410 S2 key down!\n"); key_num=2; } //wake_up(&key_q);//唤醒等待队列}void work1_func(struct work_struct *work){ mod_timer(&key_timer, jiffies + (HZ /10)); //设置100ms超时 1HZ=1S }irqreturn_t key_int(int irq,void *dev_id){ //1 检测中断是否产生 //2 清除已经发生的按键中断 //3. 提交下半部 schedule_work(work1); //return 0; return IRQ_HANDLED; }void key_hw_init(){ //unsigned int *gpio_config; unsigned short data; //gpio_config=ioremap(GPNCON,4); data=readw(key_base); data&=~0b00; data |= 0b10; writew(data,key_base); //gpio_data=ioremap(GPNDAT,4);}int key_open(struct inode *node,struct file *filp){ return 0;}ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)//应用程序读取按键的时候,要实现read的设备方法被应用程序调用{ //wait_event(key_q,key_num); //若key_num为0,就是没有数据的时候,进入睡眠,并挂在key_q这个等待队列上 printk(KERN_WARNING"in kernel :key num is %d\n",key_num); copy_to_user(buf, &key_num, 4);//提供4个字节给应用程序 key_num=0;//此时清零用来下次读取 return 4;}struct file_operations key_fops={ .open = key_open, .read = key_read, };struct miscdevice key_miscdev = { .minor = 200, //次设备号 .name = "6410key", .fops = &key_fops, };static int __devinit key_probe(struct platform_device *pdev) { int size; misc_register(&key_miscdev); //将原来在模块初始化中的程序放到probe函数中来,这样只有在驱动和设备匹配到的时候才进行原来的初始化,简化代码 //按键硬件初始化 res_mem=platform_get_resource(pdev,IORESOURCE_MEM,0); //把物理地址转化为虚拟地址 size=(res_mem->end - res_mem->start)+1; key_base=ioremap(res_mem->start,size); key_hw_init(); //注册中断处理程序 res_irq=platform_get_resource(pdev,IORESOURCE_IRQ,0); //使用platform_get_resource函数取出资源 request_irq(res_irq->start,key_int,IRQF_TRIGGER_FALLING,"key",0); //相对比之前的key.c,这里修改了硬件相关部分,提高代码的可移植性 request_irq(res_irq->end,key_int,IRQF_TRIGGER_FALLING,"key",1); //创建工作 work1=kmalloc(sizeof(struct work_struct),GFP_KERNEL); INIT_WORK(work1,work1_func); //初始化定时器 init_timer(&key_timer); key_timer.function = key_timer_func; //注册定时器 add_timer(&key_timer); //初始化等待队列 // init_waitqueue_head(&key_q); return 0;}static int __devexit key_remove(struct platform_device *pdev){ misc_deregister(&key_miscdev); //注销混杂设备 //注销中断处理程序 free_irq(IRQ_EINT(0),0); free_irq(IRQ_EINT(1),1); return 0;}struct platform_driver key_driver={ .probe = key_probe, .remove = key_remove, .driver = { .name = "my-key", //和设备模块中的名字一致 .owner = THIS_MODULE, },};static int keys_init() //在模块的初始化中只做一个工作就是注册平台驱动,重要的一点是驱动和设备要分开{ return platform_driver_register(&key_driver);//注册平台驱动 }static void key_exit(){ platform_driver_unregister(&key_driver);//注销平台驱动}module_init(keys_init);module_exit(key_exit);MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("key driver");
同样将编译好的代码key_dri.ko复制到开发板上去,可以发现按下按键S2和S3的时候,打印相应的按键信息。
- 老查的ARM学习笔记:chapter-2(linux总线设备驱动详解)
- 老查的ARM学习笔记:chapter-1(按键驱动程序设计)
- 老查的ARM学习笔记:chapter-3(串口驱动程序分析 )
- 【学习笔记】设备,驱动,总线
- Linux Kernel 学习笔记16:总线设备驱动模型
- 《Linux4.0设备驱动开发详解》笔记--第十五章:Linux I2C核心、总线与设备驱动
- Linux SPI总线设备驱动模型详解
- Linux SPI总线设备驱动模型详解
- Linux SPI总线设备驱动模型详解
- ok6410学习笔记(13.总线设备驱动)
- Linux总线设备驱动模型学习
- 详解ARM的AMBA设备中的DMA设备(Linux驱动之DMA)
- Linux设备驱动开发详解(宋宝华)学习笔记-第一章 设备驱动概述
- Linux总线设备驱动
- linux 总线 设备 驱动
- linux学习笔记-读《Linux设备驱动开发详解》~第二章 驱动设计的硬件基础
- linux设备驱动的总线,设备和驱动的关系
- Linux总线设备驱动框架的理解
- Delete Node in a Linked List
- logstash-input-jdbc实现ElasticSearch与mysql同步
- 10/30 fb面经
- python的中文数组输出乱码问题
- linux中的定时及延时任务
- 老查的ARM学习笔记:chapter-2(linux总线设备驱动详解)
- 第四讲 类加载器的委托
- LeetCode 23. Merge k Sorted Lists
- How to: Shellcode to reverse bind a shell with netcat
- [51nod1920]空间统计学
- 第八章作业
- Python正则表达式(二)代码
- 记阿里UC跟cvte社招面试-----都挂了~
- 《刻意练习》读书笔记