老查的ARM学习笔记:chapter-2(linux总线设备驱动详解)

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_type


int 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_type


int 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.c

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");

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.o

KDIR :=/home/S5-driver/lesson7/linux-ok6410

all:
    make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm

clean:
    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 0x7F008834


unsigned 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值