老查的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的时候,打印相应的按键信息。
这里写图片描述

阅读全文
0 0