总线设备驱动模型
总线:创建一条总线,跟我们前面的按键一样,首先是描述总线结构,接着是注册总线,注销总线。总线设备,例如usb总线,上面会有很多类型的usb的驱动,例如鼠标、键盘.....等,当我们把之一的usb插上的时候,usb总线会把每个驱动遍历一遍,找到相应的驱动程序执行。
接下来用bus.c创建一条总线。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
int my_match(struct device *dv, struct device_driver *drv)
{
return 0;
}
struct bus_type my_bus_type=
{
.name="mybus",
.match=my_match,
};
int mybus_init()
{
int ret;
ret=bus_register(&my_bus_type);
return ret;
}
void mybus_exit()
{
bus_unregister(&my_bus_type);
}
module_init(mybus_init);
module_exit(mybus_exit);
Make通过,拷贝到开发板运行:出现这错误:
错误的提示是未定义的bus总线相关的未定义符号。主要的要用是,我们的驱动程序没有遵循GPL协议,加上MODULE_LICENSE("GPL");就可以了。
下面的mybus总线就是我们刚新建的:
接下来是总线驱动的实现:
上面的prode函数,当我们有设备加到总线的时候,当设备与总线的某个借口相匹配的时候,系统就会调用prode函数。对我的设备进行相应的初始化。
接下来在我们上面的总线挂接个驱动。
我们的driver.c的代码:
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
extern struct bus_type my_bus_type;
int my_probe(struct device *dev)
{
printk("<0> driver found the device it can handle!\n");
return 0;
//如果是实际应用的驱动,这里会做很多的硬件初始化操作。
}
struct device_driver my_driver=
{
.name="my_dev",
.bus = &my_bus_type,//驱动是属于那一条总线的。来自外部的。所以总线的代码
//要有EXPORT_SYMBOL符号导出标志
.probe=my_probe,
};
int mydriver_init()
{
int ret;
ret=driver_register(&my_driver);
return ret;
}
void mydriver_exit()
{
driver_unregister(&my_driver);
}
module_init(mydriver_init);
module_exit(mydriver_exit);
由于驱动程序用到bus.c里的my_bus_type。所以在该结构的下面导出这个符号:EXPORT_SYMBOL(my_bus_type);//符号输出。最后重新make一遍。把生成的bus.ko和driver.ko拷贝到开发板执行的结果如下:
[root@FORLINX6410]# insmod bus.ko
[root@FORLINX6410]# insmod driver.ko
[root@FORLINX6410]# cd /sys/bus/
[root@FORLINX6410]# ls
ac97 i2c mybus sdio usb
event_source mdio_bus platform serio usb-serial
hid mmc scsi spi
[root@FORLINX6410]# cd mybus/
[root@FORLINX6410]# ls
devices drivers_autoprobe uevent
drivers drivers_probe
[root@FORLINX6410]# cd drivers/
[root@FORLINX6410]# ls
my_dev
[root@FORLINX6410]#
上面的目录/sys/bus/存的是系统总线的各类接口,我们看到了我们创建的mybus总线,进去,打开驱动drivers的目录,里面有我们创建的驱动my_dev。这说明了我们在总线上成功地挂载了我们的驱动。
接下来在我们上面的总线挂接个设备。
Device.c的代码:
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
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_init(my_device_init);
module_exit(my_device_exit);
对于我们的bus.c也需要做相应的修改。就是驱动程序和设备文件要对应上才能实现操作。把my_match函数修改如下:
int my_match(struct device *dev, struct device_driver *drv)
{
return !strncmp(dev->init_name,drv->name,strlen(drv->name));
//驱动程序和设备的匹配程序
}
最后就是在我们的Makefile里面添加device.o。后执行Make:
[root@FORFISH bus]# make
make -C /home/samba/linux-ok6410 M=/home/module/bus modules ARCH=arm CROSS_COMPILE=arm-linux-
make[1]: Entering directory `/home/samba/linux-ok6410'
CC [M] /home/module/bus/device.o
/home/module/bus/device.c:14: warning: function declaration isn't a prototype
/home/module/bus/device.c:21: warning: function declaration isn't a prototype
Building modules, stage 2.
MODPOST 3 modules
CC /home/module/bus/bus.mod.o
LD [M] /home/module/bus/bus.ko
CC /home/module/bus/device.mod.o
LD [M] /home/module/bus/device.ko
CC /home/module/bus/driver.mod.o
LD [M] /home/module/bus/driver.ko
make[1]: Leaving directory `/home/samba/linux-ok6410'
完成之后,拷贝bus.ko driver.ko.device.ko到开发板,测试。
[root@FORLINX6410]# insmod bus.ko
[root@FORLINX6410]# insmod driver.ko
[root@FORLINX6410]# insmod device.ko
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = cbcdc000
[0te=00000000
Internal error: Oop
Modules linked in: device(+) driver bus
CPU: 0 Tainted: G W (3.0.1 #439)
PC is at strncmp+0x14/0x68
LR is at my_match+0x2c/0x38 [bus]
pc : [<c01f9348>] lr : [<bf000064>] psr: 20000013
sp : cbd83e10 ip : cbd83e20 fp : cbd83e1c
r10: 00000000 r9 : bf008098 r8 : c07b121c
r7 : c0244e48 r6 : bf008090 r5 : bf008090 r4 : bf0040e4
r3 : 00000000 r2 : 00000006 r1 : bf0040e4 r0 : 00000000
Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
Control: 00c5387d Table: 5bcdc008 DAC: 00000015
Process insmod (pid: 128, stack limit = 0xcbd82268)
Stack: (0xcbd83e10 to 0xcbd84000)
3e00: cbd83e34 cbd83e20 bf000064 c01f9340
3e20: bf0040ec bf008090 cbd83e4c cbd83e38 c0244e78 bf000044 00000000 cbd83e50
3e40: cbd83e74 cbd83e50 c024405c c0244e54 cbc67b88 cc716874 bf008090 bf008090
3e60: bf0080c4 00000000 cbd83e94 cbd83e78 c0244f44 c0244000 00000000 bf008090
3e80: c0706008 00000000 cbd83ea4 cbd83e98 c0243e20 c0244ec8 cbd83efc cbd83ea8
3ea0: c0242994 c0243e00 00000000 00000000 00000001 00000000 cbd83f0c cbd83ec8
3ec0: cbd83ee4 cbd83ed0 c01f48f8 d1a3548d bf008090 bf008090 00000000 bf008138
3ee0: 00000000 cbd82000 bf00801c 00000000 cbd83f14 cbd83f00 c0242c4c c024257c
3f00: c07463c0 00000000 cbd83f24 cbd83f18 bf008030 c0242c3c cbd83f7c cbd83f28
3f20: c00343c8 bf008028 cbd83f64 cbd83f38 c0073e24 00000000 00000000 00000000
3f40: 00000000 000088a4 000d5bf9 bf008138 00000000 000088a4 000d5bf9 bf008138
3f60: 00000000 c0034ce8 cbd82000 00000000 cbd83fa4 cbd83f80 c0085960 c0034398
3f80: c00e8738 c00e8610 402704a8 000dfcf8 00000000 00000080 00000000 cbd83fa8
3fa0: c0034b40 c00858e0 402704a8 000dfcf8 01327038 000088a4 000d5bf9 ffff5f01
3fc0: 402704a8 000dfcf8 00000000 00000080 00000069 00000001 becf2e84 becf2e88
3fe0: becf2e88 becf2b34 00021cfc 40331d74 60000010 01327038 5fffe821 5fffec21
[<c01f9348>] (strncmp+0x14/0x68) from [<bf000064>] (my_match+0x2c/0x38 [bus])
[<bf000064>] (my_match+0x2c/0x38 [bus]) from [<c0244e78>] (__device_attach+0x30)
[<c0244e78>] (__device_attach+0x30/0x48) from [<c024405c>] (bus_for_each_drv+0x)
[<c024405c>] (bus_for_each_drv+0x68/0x94) from [<c0244f44>] (device_attach+0x88)
[<c0244f44>] (device_attach+0x88/0xa0) from [<c0243e20>] (bus_probe_device+0x2c)
[<c0243e20>] (bus_probe_device+0x2c/0x4c) from [<c0242994>] (device_add+0x424/0)
[<c0242994>] (device_add+0x424/0x6c0) from [<c0242c4c>] (device_register+0x1c/0)
[<c0242c4c>] (device_register+0x1c/0x20) from [<bf008030>] (init_module+0x14/0x)
[<bf008030>] (init_module+0x14/0x1c [device]) from [<c00343c8>] (do_one_initcal)
[<c00343c8>] (do_one_initcall+0x3c/0x188) from [<c0085960>] (sys_init_module+0x)
[<c0085960>] (sys_init_module+0x8c/0x1a4) from [<c0034b40>] (ret_fast_syscall+0)
Code: e92dd800 e24cb004 e3520000 0a00000e (e5d03000)
---[ end trace da227214a82491b9 ]---
Segmentation fault
[root@FORLINX6410]#
上面出现了空指针:是在strncmp里出现了空指针,这个空指针是init_name;但是我们在我的device.c里已经.init_name="my_dev",为什么还是空指针呢?接下来看内核代码:
首先是找device_register:
进入上面的device_add函数:会有下面的代码:
上面的代码就是把不为空的init_name,赋值给dev_set_name,然后自身的值变为NULL。所以,我们的程序出现空指针的原因。这个值被赋值到了成员kobj.name:
重新编译安装:
我们该了之后,看到了,我们的驱动使能了我们的设备,设备开始工作了。这样我们就实现了该功能。
流程:当我们往总线加设备的时候,我的总线会把设备和总线上的驱动,一一进行匹配,当匹配成功的时候,就会去调用驱动的my_probe这个函数。看到了打印的出现。
上面是先有驱动,再有设备,工作的。
那么现有设备,再有驱动呢。同样能实现: