总线设备驱动模型(举足轻重:这个模型运用到众多驱动中)
1.总线模型概述
如usb总线,总线上有鼠标驱动,网卡驱动,键盘驱动。现在往总
线上插入一个设备,这个设备是usb网卡。首先总线会感知到有一
个设备插上来了,那么这个设备到底使用的是哪一个设备,那么这
个时候总线就会将总线上挂载的驱动一一和这个设备来匹配。匹配
的规则,不同的设备匹配的规则是不一样的。usb总线就会将控制
权交给网卡。接下来就会由网卡驱动来处理这个设备。拔出设备与
这个过程一样。
2.总线
2.1描述结构<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);//驱动与设备的匹配函数,*match:函数指针
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 iommu_ops *iommu_ops;
struct subsys_private *p;
2.2注册
int bus_register(struct bus_type *bus)//总线的注册,如果注
册成功,新的总线将会被添加进系统,并可在/sys/bus下看到相应
的目录。
2.3注销
void bus_unregister(struct bus_type *bus)//总线注销。
接下我们来创建一条总线:
touch bus.c
chmod 777 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->kobj.name,drv->name,strlen(drv-
>name));//返回0表示匹配不上。
}
struct bus_type my_bus_type =
{
.name = "my_bus",
.match = my_match,
}
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_init(my_bus_init);
module_exit(my_bus_exit);
MODULE_LICEDSE("GPL");
安装模块的时候会提示:bus_register but_unregister未定义符
号。因为模块没有遵循GPL协议。
cd /sys/bus/在里面可以看到linux系统所支持的总线。
接下来要做的就是往总线中添加驱动
3.驱动
3.1.描述结构
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);//由总线来调用,当找到驱
动程序能够去处理设备的时候被调用,用来初始化设备
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;
}
3.2.注册
int driver_register(struct device_driver *drv)//驱动注册
3.3.注销
void driver_unregister(struct device_driver *drv)//驱动注
销。
下面在总线上挂载驱动:
touch driver.c
chmod 777 driver.c
vim makefile +drivre.o
vim driver.c
3include<linux/init.h>
#include<linux/module.h>
#inlclude<linux/kernel.h>
#include<linux/device.h>
MODULE_LICEDSE("GPL");
extern struct bus_type my_bus_type;//使用导出符号
int (*probe) (struct device *dev)
{
printk("driver found the device is can handle!\n");
return 0;
}
//初始化驱动
struct device_driver my_driver = {
.name = "my_dev",//非常重要
.bus = &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_init(my_driver_init);
module_exit(my_driver_exit);
insmod driver.ko
发现符号没有定义,因为之前加载的模块没有导出符号,需要重新
安装两个模块。
insmod bus.ko
insmod driver.ko
cd /sys/bus/my_bus:发现有两个目录,一个是device ,另一个是
driver,分别保存了总线上的设备和驱动。
cd driver
4.设备
4.1.描述结构
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the
device */设备的名字
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize
struct bus_type *bus;/* type of bus 设备所在的总线
device is on */
struct device_driver *driver; /* which driver has
device */
void *platform_data; /*
}
4.2.设备注册
nt device_register(struct device *dev)
4.3.设备注销
void device_unregister(struct device *dev)
下面在总线上挂载一个设备:
touch device.c
chmod 777 device.c
vim Makefile +device.o
#include<linux/init.h>
#include<linux/module.h>
#include<linux/device.h>
#include<linux/kernel.h>
MODULE_LICENSE("GPL");
extern struct bus_type my_bus_type;
struct device my_device= {
.init_name = "my_dev",//与设备驱动的名字一样
.bus = my_bus_type,
};
int my_device_init()
{
int ret;
ret = device_register(&my_device);
return ret;
}
void my_device_exit()
{
device_unregister(&my_device);
}
module_init(my_device_init);
module_exit(my_device_exit);
驱动与设备进行匹配,如果硬件,硬件会有一个对应的id,如果是
虚拟的设备,那么就会给设备定义一个名字,然后再给驱动定义一
个名字,如果设备的名字与驱动的名字相同,那么设备和驱动就能
够匹配了。
如果设备加载成功,那么在probe函数中就会打印出一条信息。
但事与愿违,出现了内核异常,出现了null pointer
在这里需要学会看反馈的错误信息,这里的错误信息是反着来的最
上面的是出错的地方。
最终发现init_name出现了空指针?我们找到内核代码在
device_add这个函数中我们发现这么几行代码:
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
这里直接把init_name指针清空了。实际上dev的name被赋值到了
dev->kobj.name里面来了。
还有一点内核异常之后通常都是需要重新启动的。
insmod bus.ko
insmod driver.ko
insmod device.ko
结果成功打印出一行文字。设备与驱动加载顺序可颠倒。很好的适
应热插拔。提高了驱动的可移植性。驱动与设备的名字需保持一致
。