Linux总线驱动设计(1)-总线设备驱动模型

1.总线模型概述

  • 随着技术的不断进步,系统的拓扑结构也越来越复杂,对热插拔,跨平台移植性的要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,从Linux 2.6内核开始提供了全新的设备模型。
  • 假如说现在有一条USB总线,它支持热插拔,支持鼠标、键盘、网卡。当网卡插入时,USB总线需要感知到新设备的插入,然后根据设备描述符逐个去匹配和它一样的设备驱动程序,可能先找到鼠标驱动程序,发现处理不了,然后找键盘驱动程序,发现页支持不了,最后找到了网卡驱动程序,可以处理。于是把USB的控制权交给网卡驱动程序,当网卡拔掉时,USB总线同样需要感知到设备的移除,然后通过网卡驱动程序做相应的处理。

2.总线

  • 在Linux系统中,总线的编程模型和字符型,混杂型也类似。在Linux内核中存在一种描述结构来描述总线,还有注册、注销总线设备的函数。

2.1 总线描述结构

  • 在 Linux 内核中, 总线由 struct bus_type结构表示,定义在 <linux/device.h>
struct bus_type {
    const char *name; /*总线名称*/
    int (*match) (struct device *dev, struct
    device_driver *drv); /*驱动与设备的匹配函数*/
    ………
}
  • 它的成员非常多,这里介绍2个重要的成员,第一个成员是总线的名字,第二个成员是对设备的匹配函数,上面提到过,设备插入时,需要匹配对应的设备驱动函数。
  • int (*match)(struct device * dev, struct device_driver * drv),当一个新设备或者新驱动被添加到这个总线时,该函数被调用。用于判断指定的驱动程序是否能处理指定的设备。若可以,则返回非零。

2.2 注册总线

  • 总线的注册使用如下函数
  • bus_register(struct bus_type *bus)
  • 若成功,新的总线将被添加进系统,并可在/sys/bus 下看到相应的目录。

2.3 注销总线

  • 总线的注销使用:
  • void bus_unregister(struct bus_type *bus)

2.4 创建一条总线

  • 使用上面的代码就可以注册一条总线了
  • bus.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

// 驱动与设备的匹配函数
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_LICENSE("GPL");

module_init(my_bus_init);
module_exit(my_bus_exit);
  • 安装这个模块后,使用:
  • #ls  /sys/bus,命令可以看到我们注册总线的名字
  • 注意,如果代码中没有声明遵循GPL协议,将会出现如下警告,且总线注册失败。
bus: module license 'unspecified' taints kernel.
Disabling lock debugging due to kernel taint
bus: Unknown symbol bus_unregister
bus: Unknown symbol bus_register
insmod: cannot insert 'bus.ko': unknown symbol in module or invalid parameter

3 驱动

  • 总线的设备驱动也分为描述结构,注册和注销这3个主要操作。

3.1 描述结构

  • 在 Linux内核中, 驱动由struct device_driver结构表示。
struct device_driver {
    const char *name; /*驱动名称*/
    struct bus_type *bus; /*驱动程序所在的总线*/
    int (*probe) (struct device *dev);
    ………
}
  • 这里列举出3个重要的成员,第一个驱动名称,第二个驱动程序所在的总线,第三个当总线设备和这个驱动匹配时调用probe这个函数

3.2 注册驱动

  • 驱动的注册使用如下函数
  • int driver_register(struct device_driver *drv)

3.3 注销驱动

  • 驱动的注销使用:
  • void driver_unregister(struct device_driver *drv)

3.4 创建一个驱动

  • 由于这个驱动是挂载在my_bus下面的,所以在bus.c的代码中需要把这个总线的描述结构导出,使用:
  • EXPORT_SYMBOL(my_bus_type);
  • 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("driver found the device it can handle!\n");
    
    return 0;		
}

struct device_driver my_driver = 
{
    .name = "my_dev",
    .bus = &my_bus_type,    // 驱动程序所在bus总线,还必须(1)当前模块extern struct bus_type my_bus_type;(2)原模块EXPORT_SYMBOL(my_bus_type);	
    .probe = my_probe,
};

int my_driver_init()
{
    int ret;

    // 注册驱动
    ret = driver_register(&my_driver); 
   
    return ret;   
}

void my_driver_exit()
{
    driver_unregister(&my_driver);  // 注销驱动	
}

MODULE_LICENSE("GPL");

module_init(my_driver_init);
module_exit(my_driver_exit);
  • 编写Makefile后编译
  • 并且把bus和driver都拷贝到开发板中,同时安装,查看总线情况:
  • #ls /sys/bus/my_bus
  • 在my_bus下有2个目录,devices保存设备,drivers保存驱动。
  • 查看driver目录,如果有my_dev驱动,说明驱动创建成功。

4 设备

  • 同样在Linux设备中仍然由设备描述结构,设备注册,设备注销。

4.1 设备描述结构

  • 在 Linux内核中, 设备由struct device结构表示。
struct device {
    const char *init_name; /*设备的名字*/
    struct bus_type *bus; /*设备所在的总线*/
………
}

4.2 设备的注册与注销

  • 设备的注册使用如下函数:int device_register(struct device *dev)
  • 设备的注销使用:void device_unregister(struct device *dev)

4.3 创建一个设备

  • 根据上面的介绍就可以创建一个设备了
  • device.c
​
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

extern struct bus_type 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_LICENSE("GPL");

module_init(my_device_init);
module_exit(my_device_exit);

5.设备与驱动的匹配

  • 编写好驱动程序,创建好设备后,总线怎么把驱动程序和设备对应在一起呢?其实是通过match来实现的。当然不同的总线匹配规则也不一样,比如说USB总线,在匹配驱动程序时会依次比较设备和驱动的ID,如果相同就匹配成功。我们这里采用名字匹配的方法,比如设备的名字和驱动的名字一样,则认为匹配成功,调用这个驱动的prob函数。下面开始实现总线中的match函数。
int my_macth(struct device * dev, struct device_driver * drv)
{
    return !strncmp(dev->init_name, drv->name,strlen(drv->name));
}
 
  • 最后,bus的代码如下:
  • bus.c
​
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

// 驱动与设备的匹配函数
int my_match (struct device *dev, struct device_driver *drv)
{
    return !strncmp(dev->init_name,drv->name,strlen(drv->name));  
}

// 定义总线
struct bus_type my_bus_type = 
{
    .name = "my_bus",
    .match = my_match,
}; 

// 输出my_bus_type供别的驱动使用
EXPORT_SYMBOL(my_bus_type);

int my_bus_init()
{
    int ret;   
   
    // 注册总线 
	ret = bus_register(&my_bus_type);  
    
    return ret;
}

void my_bus_exit()
{
    bus_unregister(&my_bus_type);  // 注销总线
}

MODULE_LICENSE("GPL");

module_init(my_bus_init);
module_exit(my_bus_exit);
  • 把bus,driver,device分别在开发板上安装,发现在安装device的时候内核发生了异常,提示如下:
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c39ec000
[00000000] *pgd=339de031, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1]
last sysfs file: /sys/devices/platform/soc-audio/sound/card0/mixer/dev
Modules linked in: device(+) driver bus
CPU: 0    Not tainted  (2.6.32.2-FriendlyARM #45)
PC is at strncmp+0x14/0x68
LR is at my_match+0x2c/0x38 [bus]
pc : [<c015a6a4>]    lr : [<bf000064>]    psr: 20000013
sp : c39e7e38  ip : c39e7e48  fp : c39e7e44
r10: 00000000  r9 : 00000000  r8 : bf00c070
r7 : c0192080  r6 : bf00c068  r5 : bf00c068  r4 : bf0060a4
r3 : 00000000  r2 : 00000006  r1 : bf0060a4  r0 : 00000000
Flags: nzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: c000717f  Table: 339ec000  DAC: 00000015
Process insmod (pid: 645, stack limit = 0xc39e6270)
Stack: (0xc39e7e38 to 0xc39e8000)
7e20:                                                       c39e7e5c c39e7e48
7e40: bf000064 c015a6a0 bf0060ac bf00c068 c39e7e74 c39e7e60 c01920b4 bf000048
7e60: 00000000 c39e7e78 c39e7e9c c39e7e78 c01913d0 c0192090 c39605c8 c396fe74
7e80: bf00c068 bf00c09c bf00c108 00000000 c39e7eb4 c39e7ea0 c0192158 c0191374
7ea0: c04c4778 bf00c068 c39e7ec4 c39e7eb8 c01911c4 c0192104 c39e7f0c c39e7ec8
7ec0: c018fa00 c01911a8 00000000 c0346bcc c39e7ef4 c39e7ee0 c01564fc c01574b8
7ee0: bf00c068 bf00c068 0018a8ad bf00c108 c39e6000 c0499760 00000000 bf00c01c
7f00: c39e7f24 c39e7f10 c018fc28 c018f6f8 00000966 0018a8ad c39e7f34 c39e7f28
7f20: bf00c030 c018fc1c c39e7f7c c39e7f38 c002f32c bf00c02c 00000000 00000000
7f40: 00000000 00000966 0018a8ad bf00c108 00000000 00000966 0018a8ad bf00c108
7f60: 00000000 c00300c8 c39e6000 00000000 c39e7fa4 c39e7f80 c006f144 c002f300
7f80: c009a118 c009a008 00000000 00000000 bed99eb8 00000080 00000000 c39e7fa8
7fa0: c002ff20 c006f084 00000000 00000000 001a2fd8 00000966 0018a8ad 00000000
7fc0: 00000000 00000000 bed99eb8 00000080 bed99eb4 bed99eb8 00000001 bed99eb4
7fe0: 00000069 bed99b7c 00020d54 000094b4 60000010 001a2fd8 30551031 30551431
Backtrace:
[<c015a690>] (strncmp+0x0/0x68) from [<bf000064>] (my_match+0x2c/0x38 [bus])
[<bf000038>] (my_match+0x0/0x38 [bus]) from [<c01920b4>] (__device_attach+0x34/0x4c)
 r5:bf00c068 r4:bf0060ac
[<c0192080>] (__device_attach+0x0/0x4c) from [<c01913d0>] (bus_for_each_drv+0x6c/0x98)
 r5:c39e7e78 r4:00000000
[<c0191364>] (bus_for_each_drv+0x0/0x98) from [<c0192158>] (device_attach+0x64/0x7c)
 r7:00000000 r6:bf00c108 r5:bf00c09c r4:bf00c068
[<c01920f4>] (device_attach+0x0/0x7c) from [<c01911c4>] (bus_probe_device+0x2c/0x4c)
 r5:bf00c068 r4:c04c4778
[<c0191198>] (bus_probe_device+0x0/0x4c) from [<c018fa00>] (device_add+0x318/0x524)
[<c018f6e8>] (device_add+0x0/0x524) from [<c018fc28>] (device_register+0x1c/0x20)
[<c018fc0c>] (device_register+0x0/0x20) from [<bf00c030>] (init_module+0x14/0x1c [device])
 r5:0018a8ad r4:00000966
[<bf00c01c>] (init_module+0x0/0x1c [device]) from [<c002f32c>] (do_one_initcall+0x3c/0x1c8)
[<c002f2f0>] (do_one_initcall+0x0/0x1c8) from [<c006f144>] (sys_init_module+0xd0/0x204)
[<c006f074>] (sys_init_module+0x0/0x204) from [<c002ff20>] (ret_fast_syscall+0x0/0x28)
 r7:00000080 r6:bed99eb8 r5:00000000 r4:00000000
Code: e92dd800 e24cb004 e3520000 0a00000e (e5d0c000)
---[ end trace f10357e95bff24ea ]---
Segmentation fault
  • 我们要学会分析这段提示,这里有一段非常重要的函数调用回溯表:
Backtrace:
[<c015a690>] (strncmp+0x0/0x68) from [<bf000064>] (my_match+0x2c/0x38 [bus])
[<bf000038>] (my_match+0x0/0x38 [bus]) from [<c01920b4>] (__device_attach+0x34/0x4c)
 r5:bf00c068 r4:bf0060ac
[<c0192080>] (__device_attach+0x0/0x4c) from [<c01913d0>] (bus_for_each_drv+0x6c/0x98)
 r5:c39e7e78 r4:00000000
[<c0191364>] (bus_for_each_drv+0x0/0x98) from [<c0192158>] (device_attach+0x64/0x7c)
 r7:00000000 r6:bf00c108 r5:bf00c09c r4:bf00c068
[<c01920f4>] (device_attach+0x0/0x7c) from [<c01911c4>] (bus_probe_device+0x2c/0x4c)
 r5:bf00c068 r4:c04c4778
[<c0191198>] (bus_probe_device+0x0/0x4c) from [<c018fa00>] (device_add+0x318/0x524)
[<c018f6e8>] (device_add+0x0/0x524) from [<c018fc28>] (device_register+0x1c/0x20)
[<c018fc0c>] (device_register+0x0/0x20) from [<bf00c030>] (init_module+0x14/0x1c [device])
 r5:0018a8ad r4:00000966
[<bf00c01c>] (init_module+0x0/0x1c [device]) from [<c002f32c>] (do_one_initcall+0x3c/0x1c8)
[<c002f2f0>] (do_one_initcall+0x0/0x1c8) from [<c006f144>] (sys_init_module+0xd0/0x204)
[<c006f074>] (sys_init_module+0x0/0x204) from [<c002ff20>] (ret_fast_syscall+0x0/0x28)
 r7:00000080 r6:bed99eb8 r5:00000000 r4:00000000
Code: e92dd800 e24cb004 e3520000 0a00000e (e5d0c000)
---[ end trace f10357e95bff24ea ]---
Segmentation fault
  • 可以看到问题出在strncmp函数,然后结合第一句话,内核不能处理空指针,所以strncmp比较函数中一定有一个是空指针。原因在于设备的注册函数中,在设备注册过程的时候会把init_name赋值给另外一个参数,然后把它清空。所以应该使用别的参数比较:
int my_macth(struct device * dev, struct device_driver * drv)
{
	return !strncmp(dev->kobj.name, drv->name,strlen(drv->name));
}
  • 重新编译后,再安装,最后安装设备的时候会打印出一条信息,到这里就完成了总线的驱动了!

  • 如果说先挂载设备,再安装驱动,会发生什么情况呢?设备能够正常工作吗?我们试一试,
  • 结果发现同样打印出了信息。因为在安装驱动的时候,驱动同样会去匹配当前总线上的设备,如果匹配成功,则开始处理这里设备!
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值