Newbus
实现了一种基于抽象层的新型总线结构, 可以在FreeBSD 3.0中看到这种总线结构的介绍,当时Alpha的移植被导入到 代码树中。直到4.0它才成为设备驱动程序使用的默认系统。其目的是为主机 系统提供给
操作系统
的各种总线和设备的互连提供更加 面向对象的方法。
最显著的改变之一是从平面和特殊系统演变为设备树布局。
顶层驻留的是
"根"
设备,它作为 父设备,所有其他设备挂接在它上面。对于每个结构,通常"根" 只有单个孩子,其上连接着诸如
host-to-PCI桥
等东西。对于x86,这种"根"设备为
"nexus"
设备,对于Alpha,Alpha的各种 不同型号有不同的顶层设备,对应不同的硬件芯片组,包括
lca
,
apecs
,
cia
和
tsunami
。
Newbus上下文中的设备表示系统中的单个硬件实体。例如,每个PCI设备被 表示为一个Newbus设备。系统中的任何设备可以有孩子;有孩子的设备通常被 称为
"bus"
。系统中常用总线的例子就是 ISA和PCI,他们各自管理连接到ISA和PCI总线上的设备列表。
通常,不同类型的总线之间的连接被表示为
"桥"
设备,它的孩子就是它所连接的 总线。一个例子就是
PCI-to-PCI桥
,它在父PCI总线上被 表示为
pcibN
,而用它的孩子 -
pciN
表示连接在它上面的 总线。这种布局简化了PCI总线树的实现,允许公共代码同时用于顶层和桥接的 总线。
Newbus结构中的每个设备请求它的父设备来为其映射资源。父设备接着请求 它的父设备,直到到达nexus。因此,基本上nexus是Newbus系统中唯一知道所有 资源的部分。
|
|
ISA设备可能想在
0x230
映射其IO端口,因此它向其 父设备请求,这种情况下是ISA总线。ISA总线将它交给PCI-to-ISA桥,PCI-to-ISA 桥接着请求PCI总线,PCI总线到达host-to-PCI桥,最后到达nexus。这种向上 过渡的优美之处在于可以有空间来变换请求。对
0x230
IO端口 的请求在MIPS机器上可以被PCI桥变成
0xb0000230
处的内存映射。
|
资源分配可以在设备树的任何地方加以控制。例如,在很多Alpha平台上, ISA中断与PCI中断是单独管理的,对ISA中断的资源分配是由Alpha的ISA总线设备 管理的。在IA-32上,ISA和PCI中断都由顶层的nexus设备管理。对于两种移植, 内存和端口地址空间由单个实体管理 - 在IA-32上是nexus,在Alpha(例如,CIA 或tsunami)上是相关的芯片组驱动程序。
为了规范化对内存和端口映射资源的访问,Newbus整合了NetBSD的
bus_space
API。他们提供了单一的API来代替inb/outb 和直接内存读写。这样做的优势在于单个驱动程序就可以使用内存映射寄存器 或端口映射寄存器(有些硬件支持两者)。
这种支持被合并到了资源分配机制中。分配资源时,驱动程序可以从资源 中检取关联的
bus_space_tag_t
和
bus_space_handle_t
。
Newbus也允许在专用于此目的的文件中定义接口方法。这些是
.m
文件,可以在
src/sys
目录树中找到。
Newbus系统的核心是可扩展的"基于对象编程(object-based programming)"的模型。系统中的每个设备具有它所支持的一个方法表。 系统和其他设备使用这些方法来控制设备并请求服务。设备所支持的不同方法 被定义为多个"接口"。"接口"只是 设备实现的一组相关的方法。
在Newbus系统中,设备方法是通过系统中的各种设备驱动程序提供的。当
自动配置(auto-configuration)
期间设备被连接(attach) 到驱动程序,它使用驱动程序声明的方法表。以后设备可以从其驱动程序
分离(detach)
,并
重新连接(re-attach)
到具有新方法表的新驱动程序。这就 允许驱动程序的动态替换,而动态替换对于驱动程序的开发非常有用。
接口通过与文件系统中用于定义vnode操作的语言相似的接口定义语言来 描述。接口被保存在方法文件中(通常命名为
foo_if.m
)。
例 1. Newbus的方法
# Foo 子系统/驱动程序(注释...)
INTERFACE foo
METHOD int doit {
device_t dev;
};
# 如果没有通过DEVMETHOD()提供一个方法,则DEFAULT为将会被使用的方法
METHOD void doit_to_child {
device_t dev;
driver_t child;
} DEFAULT doit_generic_to_child;
当接口被编译后,它产生一个头文件 "
foo_if.h
",其中包含函数声明:
int FOO_DOIT(device_t dev);
int FOO_DOIT_TO_CHILD(device_t dev, device_t child);
伴随自动产生的头文件,也会创建一个源文件 "
foo_if.c
";其中包含一些函数的实现, 这些函数用于在对象方法表中查找相关函数的位置并调用那个函数。
系统定义了两个主要接口。第一个基本接口被称为
"设备(device)"
,并包括与所有设备相关 的方法。
"设备(device)"
接口中的方法 包括
"探测(probe)"
,
"连接(attach)"
和
"分离(detach)"
,他们用来控制硬件的侦测, 以及
"关闭(shutdown)"
,
"挂起(suspend)"
和
"恢复(resume)"
,他们用于关键事件通知。
另一个,更加复杂接口是
"bus"
。 此接口包含的方法适用于带有孩子的设备,包括访问总线特定的每设备信息 ,事件通知 (
child_detached
,
driver_added
)和响应管理 (
alloc_resource
,
activate_resource
,
deactivate_resource
,
release_resource
)。
"bus"接口中的很多方法为总线设备的某些孩子执行服务。 这些方法通常使用前两个参量指定提供服务的总线和请求服务的子设备。为了 简化设备驱动程序代码,这些方法中的很多都有访问者(accessor)函数,访问者 函数用来查找父设备并调用父设备上的方法。例如,方法
BUS_TEARDOWN_INTR(device_t dev, device_t child, …)
可以使用函数
bus_teardown_intr(device_t child, …)
来调用。
系统中的某些总线类型提供了额外接口以提供对总线特定功能的访问。 例如,PCI总线驱动程序定义了"pci"接口,此接口有两个方法
read_config
和
write_config
,用于访问PCI设备 的配置寄存器。