Linux设备驱动程序学习(13)
-Linux设备模型(总线、设备、驱动程序和类)
总线的注册和删除
总线的主要注册步骤:
(1)申明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:
struct bus_type ldd_bus_type = { .name = "ldd", .match = ldd_match, .uevent = ldd_uevent, };
|
(2)调用bus_register函数注册总线。
int bus_register(struct bus_type * bus)
|
调用可能失败, 所以必须始终检查返回值。若成功,新的总线子系统将被添加进系统,并可在 sysfs 的 /sys/bus 下看到。之后可以向总线添加设备。
例如:
ret = bus_register(&ldd_bus_type); if (ret) return ret;
|
当必须从系统中删除一个总线时, 调用:
void bus_unregister(struct bus_type *bus);
|
总线方法
在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:
int (*match)(struct device * dev, struct device_driver * drv); /*当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数, 因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序*/
int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size); /*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/
|
lddbus的match和uevent方法:
static int ldd_match(struct device *dev, struct device_driver *driver) { return !strncmp(dev->bus_id, driver->name, strlen(driver->name)); }/*仅简单比较驱动和设备的名字*/ /*当涉及实际硬件时, match 函数常常对设备提供的硬件 ID 和驱动所支持的 ID 做比较*/
static int ldd_uevent(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) { envp[0] = buffer; if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s", Version) >= buffer_size) return -ENOMEM; envp[1] = NULL; return 0; }/*在环境变量中加入 lddbus 源码的当前版本号*/
|
对设备和驱动的迭代
若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构, 但最好使用内核提供的辅助函数:
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *)); int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));
/*这两个函数迭代总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 传递给 fn, 同时传递 data 值。若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。*/
|
总线属性
几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 如下:
struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *, char * buf); ssize_t (*store)(struct bus_type *, const char * buf, size_t count); };
|
可以看出struct bus_attribute 和struct attribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。
内核提供了一个宏在编译时创建和初始化 bus_attribute 结构:
BUS_ATTR(_name,_mode,_show,_store)/*这个宏声明一个结构, 将 bus_attr_ 作为给定 _name 的前缀来创建总线的真正名称*/
/*总线的属性必须显式调用 bus_create_file 来创建:*/ int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);
/*删除总线的属性调用:*/ void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);
|
例如创建一个包含源码版本号简单属性文件方法如下:
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);
/*在模块加载时创建属性文件:*/ if (bus_create_file(&ldd_bus_type, &bus_attr_version)) printk(KERN_NOTICE "Unable to create version attribute\n");
/*这个调用创建一个包含 lddbus 代码的版本号的属性文件(/sys/bus/ldd/version)*/
|
实例:
#include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h>
MODULE_AUTHOR("Andy"); MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "$Revision: 1.0 $";
static int my_match(struct device *dev, struct device_driver *driver) { return !strncmp(dev->bus_id, driver->name, strlen(driver->name)); }
struct bus_type my_bus_type = { .name = "my_bus", .match = my_match, };
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); //名称 权限 show函数 store函数
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"); return ret; }
static void my_bus_exit(void) { bus_unregister(&my_bus_type); bus_remove_file(&my_bus_type, &bus_attr_version); }
module_init(my_bus_init); module_exit(my_bus_exit);
|
注意:
1.注册总线设备 检测返回值
2.定义总线 定义为变量 (name,match,uevent)
3.设备方法 match,uevent
4.创建属性文件 利用宏
5.实现show store
设备注册
设备的注册和注销函数为:
int device_register(struct device *dev); void device_unregister(struct device *dev);
|
一个实际的总线也是一个设备,所以必须单独注册,以下为 lddbus 在编译时注册它的虚拟总线设备源码:
static void ldd_bus_release(struct device *dev) { printk(KERN_DEBUG "lddbus release\n"); }
struct device ldd_bus = { .bus_id = "ldd0", .release = ldd_bus_release
}; /*这是顶层总线,parent 和 bus 成员为 NULL*/
/*作为第一个(并且唯一)总线, 它的名字为 ldd0,这个总线设备的注册代码如下:*/ ret = device_register(&ldd_bus); if (ret) printk(KERN_NOTICE "Unable to register ldd0\n"); /*一旦调用完成, 新总线会在 sysfs 中 /sys/devices 下显示,任何挂到这个总线的设备会在 /sys/devices/ldd0 下显示*/
|
设备属性
sysfs 中的设备入口可有属性,相关的结构是:
/* interface for exporting device attributes 这个结构体和《LDD3》中的不同,已经被更新过了,请特别注意!*/ struct device_attribute { struct attribute attr; ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); };
/*设备属性结构可在编译时建立, 使用以下宏:*/ DEVICE_ATTR(_name,_mode,_show,_store); /*这个宏声明一个结构, 将 dev_attr_ 作为给定 _name 的前缀来命名设备属性
/*属性文件的实际处理使用以下函数:*/ int device_create_file(struct device *device, struct device_attribute * entry); void device_remove_file(struct device * dev, struct device_attribute * attr);
|
实例:
#include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h>
MODULE_AUTHOR("David Xie"); MODULE_LICENSE("Dual BSD/GPL");
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, 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; /* 初始化设备 */ strncpy(my_dev.bus_id, "my_dev", BUS_ID_SIZE); /*注册设备*/ 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);
|
设备驱动程序
设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。
1.注册驱动 定义驱动(name,bus(导入符号),probe,remove 实现)
2.创建驱动属性文件 利用宏 show实现
实例:
#include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h>
MODULE_AUTHOR("David Xie"); 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; /*注册驱动*/ driver_register(&my_driver); /*创建属性文件*/ 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);
|
阅读(1203) | 评论(0) | 转发(0) |