如何在linux内核中创建自己的总线

创建一条总线,首先是描述总线的结构,接着是注册总线,注销总线。

总线设备,如:usb总线,上面会有很多类型的usb的驱动,如:鼠标,键盘等,当我们插入一个usb设备时,usb总线会把每个驱动遍历一遍,找到相应的驱动程序执行。

在linux内核中,总线由:struct bus_type表示,定义在 <linux/device.h>

   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 <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)
{
    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 <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

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 <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

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函数,看到了打印的调试信息。
 

猜你喜欢

转载自blog.csdn.net/wenyue043/article/details/105220052