前面九章分别对linux驱动模型中的细节部分进行了分析,本节作为小节,使用一个简单的例子,分别使用前面分析的内容,实现一个简单的总线,设备,驱动之间的关系。
实现一条总线
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "$Revision: 1.9 $";
/* 驱动和设备匹配 */
static int my_match(struct device *dev, struct device_driver *driver)
{
return !strncmp(dev_name(dev), driver->name, strlen(driver->name));
}
static void my_bus_release(struct device *dev)
{
printk(KERN_DEBUG "my bus release\n");
}
/* 总线也是一个设备 */
struct device my_bus = {
.init_name = "my_bus0",
.release = my_bus_release
};
/* 定义一个总线 */
struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_match,
};
/* 导出总线和总线设备 */
EXPORT_SYMBOL(my_bus);
EXPORT_SYMBOL(my_bus_type);
/*
* Export a simple attribute.
*/
static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);
static int __init my_bus_init(void)
{
int ret;
/*注册总线*/
ret = bus_register(&my_bus_type);
if (ret)
return ret;
/*创建属性文件*/
if (bus_create_file(&my_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Fail to create version attribute!\n");
/*注册总线设备*/
ret = device_register(&my_bus);
if (ret)
printk(KERN_NOTICE "Fail to register device:my_bus!\n");
return ret;
}
static void my_bus_exit(void)
{
device_unregister(&my_bus);
bus_unregister(&my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);
这里是注册一个总线,和前面总线章节那个例子相比,总线作为一个设备注册了下来(我们可以sys/devices/目录下面看到)。
当然也同样实现了一个属性文件,可以使用它的读功能,查看版本。
这里看一下注册总线前后的对比:
可以看到总线名和设备名都能对应上。
同时因为这对总线的设备注册时没父节点以及挂接到别的bus上的,所以这个设备直接是在/sys/devices/这个目录下。
同时我们这里对总线和总线所代表的设备的符号进行了导出,可以让其他内核模块使用。
实现一个挂接在上面总线的设备
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_LICENSE("Dual BSD/GPL");
/* 使用bus模块中导出的符号 */
extern struct device my_bus;
extern struct bus_type my_bus_type;
/* Why need this ?*/
static void my_dev_release(struct device *dev)
{
}
struct device my_dev = {
.bus = &my_bus_type,
.parent = &my_bus,
.release = my_dev_release,
};
/*
* Export a simple attribute.
*/
static ssize_t mydev_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%s\n", "This is my device!");
}
static DEVICE_ATTR(dev, S_IRUGO, mydev_show, NULL);
static int __init my_device_init(void)
{
int ret = 0;
/* 初始化设备 */
dev_set_name(&my_dev, "my_dev");
/*注册设备*/
ret = device_register(&my_dev);
/*创建属性文件*/
device_create_file(&my_dev, &dev_attr_dev);
return ret;
}
static void my_device_exit(void)
{
device_unregister(&my_dev);
}
module_init(my_device_init);
module_exit(my_device_exit);
这个设备的实现就简单很多了,初始化时,确定了设备是挂载那个总线下面,其父设备是那个后,注册这个设备即可。
这里也实现了一个读的属性文件。
设备名叫“my_dev”
实现一个挂接在上面总线的驱动程序
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_LICENSE("Dual BSD/GPL");
/* 使用导出的总线 */
extern struct bus_type my_bus_type;
static int my_probe(struct device *dev)
{
printk("Driver found device which my driver can handle!\n");
return 0;
}
static int my_remove(struct device *dev)
{
printk("Driver found device unpluged!\n");
return 0;
}
struct device_driver my_driver = {
.name = "my_dev", /* 注意这里和设备名字一样 */
.bus = &my_bus_type, /* 挂接我们实现的总线 */
.probe = my_probe,
.remove = my_remove,
};
/*
* Export a simple attribute.
*/
static ssize_t mydriver_show(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%s\n", "This is my driver!");
}
static DRIVER_ATTR(drv, S_IRUGO, mydriver_show, NULL);
static int __init my_driver_init(void)
{
int ret = 0;
/*注册驱动*/
ret = driver_register(&my_driver);
/*创建属性文件*/
ret = driver_create_file(&my_driver, &driver_attr_drv);
return ret;
}
static void my_driver_exit(void)
{
driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
驱动这里要注意两点:
1.同样绑定了我们实现的总线
2.驱动的name和设备那边的name一样
同时我们也知道,probe函数是设备和驱动匹配上后,被调用的。
实验测试:
显示看一下总线以及总线设备安装前后的对比。
当然这时候总线下面的设备和驱动都是没东西的。
在总线安装的前提下,安装设备
这里要知道的一点是,bus下的设备都是,devices下的符号链接,而具体设备里的subsystme则是bus下的具体总线的符号链接。
有了上面两个,就可以在devices目录下直接进入bus目录,bus也可以直接进入devices目录。
在总线安装的前提下,安装驱动
因为上面我们先安装了设备,这里再安装驱动。所以驱动安装完就直接匹配上了设备。
下面我们先卸载设备。
可以看到,在卸载了设备后,因为没了和驱动匹配的设备,所以驱动下面的设备的符号链接也被移除了。
如果我们分析一下linux中的那条虚拟总线platform总线,会发现,其实和我们上面的实现是一样的。
只不过platform总选,完善了match的id_table的情况。
platform总线,实现了一个uevnt函数,来通知上层。
总之,基本的驱动模型以及实现。