一个总线是处理器和一个或多个设备之间的通道. 为设备模型的目的, 所有的设备都通过
一个总线连接, 甚至当它是一个内部的虚拟的,"平台"总线. 总线可以插入另一个 - 一个
USB 控制器常常是一个 PCI 设备, 例如. 设备模型表示在总线和它们控制的设备之间的
实际连接.
在 Linux 设备模型中, 一个总线由 bus_type 结构代表, 定义在
个结构看来象:
struct bus_type {
char *name;
struct subsystem subsys;
struct kset drivers;
struct kset devices;
int (*match)(struct device *dev, struct device_driver *drv);
struct device *(*add)(struct device * parent, char * bus_id);
int (*hotplug) (struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
/* Some fields omitted */
};
name 成员是总线的名子, 有些同 pci. 你可从这个结构中见到每个总线是它自己的子系
统; 这个子系统不位于 sysfs 的顶层, 但是. 相反, 它们在总线子系统下面. 一个总线
包含 2 个 ksets, 代表已知的总线的驱动和所有插入总线的设备. 所以, 有一套方法我
们马上将涉及.
如同我们提过的, 例子源码包含一个虚拟总线实现称为 lddbus. 这个总线建立它的
bus_type 结构, 如下:
struct bus_type ldd_bus_type = { .name = "ldd", .match = ldd_match, .hotplug =
ldd_hotplug, };
注意很少 bus_type 成员要求初始化; 大部分由设备模型核心处理. 但是, 我们确实不得
不指定总线的名子, 以及任何伴随它的方法.
不可避免地, 一个新总线必须注册到系统, 通过一个对 bus_register 的调用. lddbus
代码这样做以这样的方式:
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
这个调用可能失败, 当然, 因此返回值必须一直检查. 如果它成功, 新总线子系统已被添
加到系统; 在 sysfs 中 /sys/bus 的下面可以见到, 并且可能启动添加设备.
如果有必要从系统中去除一个总线(当关联模块被去除, 例如), 调用调用
bus_unregister:
void bus_unregister(struct bus_type *bus);
有几个给 bus_type 结构定义的方法; 它们允许总线代码作为一个设备核心和单独驱动之
间的中介. 在 2.6.10 内核中定义的方法是:
int (*match)(struct device *device, struct device_driver *driver);
这个方法被调用, 大概多次, 无论何时一个新设备或者驱动被添加给这个总线. 它
应当返回一个非零值如果给定的设备可被给定的驱动处理. (我们马上进入设备和
device_driver 结构的细节). 这个函数必须在总线级别处理, 因为那是合适的逻
辑存在的地方; 核心内核不能知道如何匹配每个可能总线类型的设备和驱动.
int (*hotplug) (struct device *device, char **envp, int num_envp, char *buffer,
int buffer_size);
这个模块允许总线添加变量到环境中, 在产生一个热插拔事件在用户空间之前. 参
数和 kset 热插拔方法相同( 在前面的 "热插拔事件产生" 一节中描述 ).
lddbus 驱动有一个非常简单的匹配函数, 它仅仅比较驱动和设备的名子:
static int ldd_match(struct device *dev, struct device_driver *driver)
{
return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}
当涉及到真实硬件, match 函数常常在有设备自身提供的硬件 ID 和驱动提供的 ID 之间,
做一些比较.
lddbus 热插拔方法看来象这样:
static int ldd_hotplug(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 *));
这个函数列举总线上的每个设备, 传递关联的设备结构给 fn, 连同作为 data 来传递的
值. 如果 start 是 NULL, 列举从总线的第一个设备开始; 否则列举从 start 之后的第
一个设备开始. 如果 fn 返回一个非零值, 列举停止并且那个值从 bus_for_each_dev 返
回.
有一个类似的函数来列举驱动:
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int
(*fn)(struct device_driver *, void *));
这个函数就像 buf_for_each_dev, 除了, 当然, 它替之作用于驱动.
应当注意, 这 2 个函数持有总线子系统的读者/写者旗标在工作期间. 因此试图一起使用
这 2 个会死锁 -- 每个将试图获取同一个旗标. 修改总线的操作( 例如注销设备 )也将
锁住. 因此, 小心使用 bus_for_each 函数.
几乎 Linux 驱动模型中的每一层都提供一个添加属性的接口, 并且总线层不例外.
bus_attribute 类型定义在
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf,
size_t count);
};
我们已经见到 struct attribute 在 "缺省属性" 一节. bus_attribute 类型也包含 2
个方法来显示和设置属性值. 大部分在 kobject 之上的设备模型层以这种方式工作.
已经提供了一个方便的宏为在编译时间创建和初始化 bus_attribute 结构:
BUS_ATTR(name, mode, show, store);
这个宏声明一个结构, 产生它的名子通过前缀字符串 bus_attr_ 到给定的名子.
任何属于一个总线的属性应当明确使用 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);
lddbus 驱动创建一个简单属性文件, 再次, 包含源码版本号. show 方法和
bus_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);
创建属性文件在模块加载时间完成:
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Unable to create version attribute\n");
这个调用创建一个属性文件(/sys/busldd/version) 包含 lddbus 代码的版本号.