创建一条总线,首先是描述总线的结构,接着是注册总线,注销总线。
总线设备,如:usb总线,上面会有很多类型的usb的驱动,如:鼠标,键盘等,当我们插入一个usb设备时,usb总线会把每个驱动遍历一遍,找到相应的驱动程序执行。
在linux内核中,总线由:struct bus_type表示,定义在
struct bus_type {
const char *name; //总线名称
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv); //驱动与设备的匹配函数
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
其中:int (*match)(struct device *dev, struct device_driver *drv); 当一个新设备或者新驱动被添加到这个总线时,该函数被调用。
用于判断指定的驱动程序是否能处理指定的设备。若可以,则返回非零。
总线注册: extern int __must_check bus_register(struct bus_type *bus);
总线注销: extern void bus_unregister(struct bus_type *bus);
创建一条总线的代码如下:bus.c
#include
#include
#include
#include
int my_match(struct device * dev,struct device_driver *drv)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
//return !strncmp(dev->init_name,drv->name,strlen(drv->name)); //跟读注册代码发现,这样有问题
return !strncmp(dev->kobj.name,drv->name,strlen(drv->name));
}
struct bus_type my_bus_type = {
.name = "mybus",
.match = my_match,
};
EXPORT_SYMBOL(my_bus_type);
static int __init mybus_init(void)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
return bus_register(&my_bus_type);
}
static void __exit mybus_exit(void)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
bus_unregister(&my_bus_type);
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
编译之后,在开发板上加载,如下:
[root@farsight /drv_module]# insmod bus.ko
--------^_^ mybus_init------------
下面的mybus总线就是我们刚新建的:
[root@farsight /drv_module]# ls /sys/bus
mybus platform scsi serio
接下来实现总线上层驱动部分的代码:driver.c
在linux内核中,驱动由:struct device_driver结构表示
struct device_driver {
const char *name; //驱动的名称
struct bus_type *bus; //驱动程序所在的总线
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
int (*probe) (struct device *dev); //当有设备加到总线的时候,同时设备与总线的某个相匹配的时候,系统就会调用probe
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
驱动的注册:extern int __must_check driver_register(struct device_driver *drv);
驱动的注销: extern void driver_unregister(struct device_driver *drv);
在上面创建的总线上挂接驱动,如下
#include
#include
#include
#include
extern struct bus_type my_bus_type;
int my_probe(struct device *dev)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
//如果是实际应用的驱动,这里会做很多硬件初始化操作
return 0;
}
struct device_driver my_driver = {
.name = "my_dev",
.bus = &my_bus_type, //驱动属于哪条总线,来自于外部的,需要: EXPORT_SYSBOL
.probe = my_probe,
};
static int __init mydriver_init(void)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
return driver_register(&my_driver);
}
static void __exit mydriver_exit(void)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
driver_unregister(&my_driver);
}
module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");
编译,并在开发板中测试:
[root@farsight /drv_module]# insmod bus.ko
--------^_^ mybus_init------------
[root@farsight /drv_module]# insmod driver.ko
--------^_^ mydriver_init------------
[root@farsight /drv_module]# ls /sys/bus //此目录下是系统的各类接口,mybus为我们创建的总线
mybus platform scsi serio
[root@farsight /drv_module]# ls /sys/bus/mybus/
devices drivers_autoprobe uevent
drivers drivers_probe
[root@farsight /drv_module]# ls /sys/bus/mybus/drivers
my_dev
//该目录下有我们创建的驱动my_dev,这说明在总线上成功的挂载了我们的驱动。
接下来在总线上挂接一个设备:
在linux内核中,设备由:struct device 结构体表示
struct device {
struct kobject kobj;
const char *init_name; /* initial name of the device 设备的名称*/
struct bus_type *bus; /* type of bus device is on 设备所在的总线 */
.............
}
设备注册:int device_register(struct device *dev)
设备注销:void device_unregister(struct device *dev)
设备层代码如下: device.c
#include
#include
#include
#include
extern struct bus_type my_bus_type;
int my_probe(struct device *dev)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
return 0;
}
struct device my_dev = {
.init_name = "my_dev", //与驱动一致
.bus = &my_bus_type, //驱动属于哪条总线,来自于外部的,需要: EXPORT_SYSBOL
};
static int __init mydevice_init(void)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
return device_register(&my_dev);
}
static void __exit mydevice_exit(void)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
device_unregister(&my_dev);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL");
在总线中,需要匹配驱动和设备,驱动程序和设备文件对应上才能实现操作,my_match如下:
int my_match(struct device * dev,struct device_driver *drv)
{
printk("--------^_^ %s------------\n",__FUNCTION__);
//return !strncmp(dev->init_name,drv->name,strlen(drv->name)); //跟读注册代码发现,这样有问题
return !strncmp(dev->kobj.name,drv->name,strlen(drv->name));
}
如果使用return !strncmp(dev->init_name,drv->name,strlen(drv->name)); 加载时会有段错误,提示:
[root@farsight /drv_module]# insmod device.ko
--------^_^ mydevice_init------------
--------^_^ my_match------------
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = bfb0c000
[00000000] *pgd=5f94c831, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1] PREEMPT
Modules linked in: device(+) driver bus plat_led_dev plat_led_drv [last unloaded: bus]
CPU: 0 Not tainted (3.0.8 #2)
PC is at strncmp+0x8/0x68
LR is at my_match+0x3c/0x48 [bus]
pc : [<80158110>] lr : [<7f01403c>] psr: 20000013
sp : bf98fe28 ip : 00000007 fp : 00000031
r10: 00000001 r9 : 00000000 r8 : 80367d54
r7 : 00000000 r6 : 8018c800 r5 : 7f0180b4 r4 : 7f01c0e0
r3 : 7f0180bb r2 : 00000006 r1 : 7f0180b4 r0 : 00000000
Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
Control: 10c5387d Table: 5fb0c019 DAC: 00000015
Process insmod (pid: 910, stack limit = 0xbf98e2e8)
Stack: (0xbf98fe28 to 0xbf990000)
fe20: 7f014000 7f0180e0 7f01c0e0 8018c828 7f01c0e0 bf98fe48
fe40: 8018c800 8018b60c bf9a9268 bf14f344 00000000 7f01c114 7f01c0e0 00000000
上面出现空指针,是在strncmp里出现了空指针,这个空指针是init_name,原因如下:
在内核代码中:
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
进入device_add(dev);,会看到:
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
上面的代码就是把不为空的init_name,赋值给dev_set_name,然后自身的值变为NULL,所以,我们的程序出现空指针,这个值被赋值到了成员:kobj.name,所以应该用:
return !strncmp(dev->kobj.name,drv->name,strlen(drv->name));
编译,并在开发板中测试:
[root@farsight /drv_module]# insmod bus.ko
--------^_^ mybus_init------------
[root@farsight /drv_module]# insmod driver.ko
--------^_^ mydriver_init------------
[root@farsight /drv_module]# insmod device.ko
--------^_^ mydevice_init------------
--------^_^ my_match------------
--------^_^ my_probe------------
上面的测试说明:我们的驱动使能了我们的设备,设备开始工作了,这样我们就实现了该功能。
总线驱动执行流程:当往总线上加设备时,总线会把设备和总线上的驱动进行匹配,当匹配成功,就会调用驱动的my_probe函数,看到了打印的调试信息。