第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
系统基于节点名称和 compatible 属性为内核设备树中的每个节点选择驱动程序(请参见将驱动程序绑定到设备)。相同的驱动程序可能会绑定到多个设备节点。驱动程序可以根据系统指定的实例编号来区分不同的节点。
为设备节点选择驱动程序之后,将调用该驱动程序的 probe(9E) 入口点以确定系统上是否存在该设备。如果 probe() 成功,将调用该驱动程序的 attach(9E) 入口点以设置和管理设备。当且仅当 attach() 返回成功信息时,才能打开该设备(请参见attach() 入口点)。
可能会取消配置设备以节省系统内存资源,或在系统仍在运行时使设备可以移除。要取消配置设备,系统首先会检查是否引用了设备实例。此检查将调用驱动程序的 getinfo(9E) 入口点以获取仅为该驱动程序所知的信息(请参见getinfo() 入口点)。如果未引用设备实例,将调用驱动程序的 detach(9E) 例程来取消配置设备(请参见detach() 入口点)。
要进行更新,每个驱动程序都必须定义内核用于设备配置的以下入口点:
请注意,attach()、detach() 和 getinfo() 是必需的。只有无法自我识别的设备需要 probe()。对于自标识设备,可以提供显式 probe() 例程,或者在 dev_ops 结构中为 probe() 入口点指定 nulldev(9F)。
系统会为每个设备指定一个实例编号。驱动程序可能无法可靠地预测指定给某个特定设备的实例编号值。驱动程序应通过调用 ddi_get_instance(9F) 来检索已指定的特定实例编号。
实例编号代表了系统中的设备。内核会为特定驱动程序的每个 dev_info(即设备树中的每个节点)指定一个实例编号。此外,实例编号可提供一种便捷的、为特定于某个物理设备的数据建立索引的机制。实例编号的最常见用法是 ddi_get_soft_state(9F),也就是使用实例编号检索特定物理设备的软状态数据。
![]() | 注意 - 对于伪设备(即伪结点的子结点),其实例编号是采用 instance 属性在 driver.conf(4) 文件中定义的。如果 driver.conf 文件不包含 instance 属性,则未定义此行为。对于硬件设备节点,当 OS 首次发现此类设备时,系统会为其指定实例编号。实例编号在系统重新引导以及 OS 升级期间保持不变。 |
驱动程序负责管理其次要设备号名称空间。例如,sd 驱动程序需要向每个磁盘的文件系统导出八个字符次要节点和八个块次要节点。每个次要节点代表部分磁盘的块接口或字符接口。getinfo(9E) 入口点通知系统有关次要设备号到设备实例的映射(请参见getinfo() 入口点)。
对于非自我识别设备,probe(9E) 入口点应确定系统上是否存在硬件设备。
对于 probe(),要确定是否存在设备实例,probe()需要执行通常 attach(9E) 也执行的许多任务。尤其是,probe() 可能需要映射设备寄存器。
探测设备寄存器是特定于设备的。驱动程序通常必须执行一系列硬件测试来确保硬件确实存在。测试条件必须足够严格以避免错误地识别设备。例如,在设备实际上不可用的情况下可能显示存在该设备,因为异常设备在行为上看起来与预期的设备相似。
测试返回以下标志:
DDI_PROBE_SUCCESS(探测成功)
DDI_PROBE_FAILURE(探测失败)
DDI_PROBE_DONTCARE(探测不成功,但仍需调用 attach(9E))
DDI_PROBE_PARTIAL(现在不存在实例,但将来可能会出现)
对于给定设备实例,直到 probe(9E) 在该设备上至少成功一次时,才会调用 attach(9E)。
probe(9E) 必须释放 probe() 已分配的所有资源,因为可能会调用 probe() 多次。但是,即使 probe(9E) 已成功,也不一定要调用 attach(9E)。
可以在驱动程序的 probe(9E) 例程中使用 ddi_dev_is_sid(9F) 来确定设备是否可以自我识别。在为同一设备的自我识别版本和非自我识别版本编写驱动程序时,ddi_dev_is_sid() 非常有用。
以下示例是一个样例 probe() 例程。
示例 6-3 probe(9E) 例程
static int
xxprobe(dev_info_t *dip)
{
ddi_acc_handle_t dev_hdl;
ddi_device_acc_attr_t dev_attr;
Pio_csr *csrp;
uint8_t csrval;
/*
* if the device is self identifying, no need to probe
*/
if (ddi_dev_is_sid(dip) == DDI_SUCCESS)
return (DDI_PROBE_DONTCARE);
/*
* Initalize the device access attributes and map in
* the devices CSR register (register 0)
*/
dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&csrp, 0, sizeof (Pio_csr),
&dev_attr, &dev_hdl) != DDI_SUCCESS)
return (DDI_PROBE_FAILURE);
/*
* Reset the device
* Once the reset completes the CSR should read back
* (PIO_DEV_READY | PIO_IDLE_INTR)
*/
ddi_put8(dev_hdl, csrp, PIO_RESET);
csrval = ddi_get8(dev_hdl, csrp);
/*
* tear down the mappings and return probe success/failure
*/
ddi_regs_map_free(&dev_hdl);
if ((csrval & 0xff) == (PIO_DEV_READY | PIO_IDLE_INTR))
return (DDI_PROBE_SUCCESS);
else
return (DDI_PROBE_FAILURE);
}
调用驱动程序的 probe(9E) 例程时,驱动程序并不知道正在探测的设备是否存在于总线上。因此,驱动程序可能会尝试访问不存在设备的设备寄存器。结果,在某些总线上可能会产生总线故障。
以下示例显示了使用 ddi_poke8(9F) 来检查设备是否存在的 probe(9E) 例程。ddi_poke8() 谨慎地尝试将值写入指定的虚拟地址,必要时使用父结点驱动程序协助进程。如果地址无效或无法在不出现错误的情况下写入值,则会返回错误代码。另请参见 ddi_peek(9F)。
在本示例中,使用 ddi_regs_map_setup(9F) 来映射设备寄存器。
示例 6-4 使用 ddi_poke8(9F) 的 probe(9E) 例程
static int
xxprobe(dev_info_t *dip)
{
ddi_acc_handle_t dev_hdl;
ddi_device_acc_attr_t dev_attr;
Pio_csr *csrp;
uint8_t csrval;
/*
* if the device is self-identifying, no need to probe
*/
if (ddi_dev_is_sid(dip) == DDI_SUCCESS)
return (DDI_PROBE_DONTCARE);
/*
* Initialize the device access attrributes and map in
* the device's CSR register (register 0)
*/
dev_attr.devacc_attr_version - DDI_DEVICE_ATTR_V0;
dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&csrp, 0, sizeof (Pio_csr),
&dev_attr, &dev_hdl) != DDI_SUCCESS)
return (DDI_PROBE_FAILURE);
/*
* The bus can generate a fault when probing for devices that
* do not exist. Use ddi_poke8(9f) to handle any faults that
* might occur.
*
* Reset the device. Once the reset completes the CSR should read
* back (PIO_DEV_READY | PIO_IDLE_INTR)
*/
if (ddi_poke8(dip, csrp, PIO_RESET) != DDI_SUCCESS) {
ddi_regs_map_free(&dev_hdl);
return (DDI_FAILURE);
csrval = ddi_get8(dev_hdl, csrp);
/*
* tear down the mappings and return probe success/failure
*/
ddi_regs_map_free(&dev_hdl);
if ((csrval & 0xff) == (PIO_DEV_READY | PIO_IDLE_INTR))
return (DDI_PROBE_SUCCESS);
else
return (DDI_PROBE_FAILURE);
}
内核调用驱动程序的 attach(9E) 入口点来连接设备实例或针对已通过电源管理框架暂停或关闭的设备实例恢复操作。本节仅讨论连接设备实例的操作。电源管理将在第 12 章中讨论。
调用驱动程序的 attach(9E) 入口点以连接每个绑定到驱动程序的设备实例。基于要连接的设备节点实例,并将 attach(9E) 的 cmd 参数指定为 DDI_ATTACH,来调用此入口点。attach 入口点主要包括以下类型的处理:
为了协助设备驱动程序编写人员分配状态结构,Oracle Solaris DDI/DKI 提供了一组内存管理例程,称为软件状态管理例程,也称为软状态例程。这些例程可动态分配、检索以及销毁指定大小的内存项,并可隐藏列表管理的详细信息。实例编号标识所需的内存项。此编号通常为系统指定的实例编号。
通常,驱动程序会为与其连接的每个设备实例分配软状态结构,方法是调用 ddi_soft_state_zalloc(9F) 并传递设备的实例编号。由于两个设备节点不能具有相同的实例编号,所以对于已经分配出去的给定实例编号,ddi_soft_state_zalloc(9F) 将失败。
驱动程序的字符入口点或块入口点(cb_ops(9S))通过先解码来自传递到入口点函数的 dev_t 参数的设备实例编号,来引用特定的软状态结构。随后,驱动程序调用 ddi_get_soft_state(9F),传递每个驱动程序的软状态列表和生成的实例编号。返回值 NULL 表明实际上不存在该设备并且应由驱动程序返回相应的代码。
有关实例编号和设备编号(dev_t 编号)之间关系的更多信息,请参见创建从设备节点。
驱动程序在连接期间应初始化所有基于实例的锁和条件变量。添加任何中断处理程序之前,必须先初始化驱动程序中断处理程序所获取的所有锁。有关锁的初始化和使用的说明,请参见第 3 章。有关中断处理程序和锁问题的讨论,请参见第 8 章。
连接过程的一个重要部分是为设备实例创建次要节点。次要节点包含由设备和 DDI 框架导出的信息。系统使用此信息为 /devices 下的次要节点创建特殊文件。
驱动程序调用 ddi_create_minor_node(9F) 时会创建次要节点。驱动程序提供次要设备号、次要名称、次要节点类型,以及次要节点是代表块设备还是字符设备。
驱动程序可以为设备创建任意数量的次要节点。Oracle Solaris DDI/DKI 期望某些类别的设备具有以特定格式创建的次要节点。例如,期望磁盘驱动程序为连接的每个物理磁盘实例创建 16 个次要节点。将创建八个代表块设备接口 a - h 的次要节点,另外八个次要节点代表字符设备接口 a,raw - h,raw。
传递给 ddi_create_minor_node(9F) 的次要设备号全部由驱动程序定义。次要设备号通常是设备实例编号和次要节点标识符的编码。在前面的示例中,驱动程序会为每个次要节点创建次要设备号,方法是将设备的实例编号左移三位,再将该结果与次要节点索引进行“或”运算。次要节点索引值的范围介于 0 和 7 之间。请注意,次要节点 a 和 a,raw 共用同一次要设备号。这些次要节点根据传递到 ddi_create_minor_node() 的 spec_type 参数来区分。
传递给 ddi_create_minor_node(9F) 的次要节点类型对设备类型进行分类,如磁盘、磁带、网络接口、帧缓存器等。
下表列出了可以创建的可能的节点类型。
表 6-1 可能节点类型
|
节点类型 DDI_NT_BLOCK、DDI_NT_BLOCK_CHAN、DDI_NT_CD 和 DDI_NT_CD_CHAN 会使 devfsadm(1M) 将设备实例标识为磁盘,并在 /dev/dsk 或 /dev/rdsk 目录中创建名称。
节点类型 DDI_NT_TAPE 会使 devfsadm(1M) 将设备实例标识为磁带,并在 /dev/rmt 目录中创建名称。
节点类型 DDI_NT_SERIAL 和 DDI_NT_SERIAL_DO 会使 devfsadm(1M) 执行以下操作:
将设备实例标识为串行端口
在 /dev/term 目录中创建名称
向 /etc/inittab 文件中添加项
供应商提供的字符串应包括使字符串唯一的标识值,如名称或股票名称。该字符串可与 devfsadm(1M) 和 devlinks.tab 文件(请参见 devlinks(1M) 手册页)一起使用以在 /dev 中创建逻辑名称。
在相应实例上的 attach(9E) 成功之前,可能会对次要设备调用 open(9E)。然后 open() 必须返回 ENXIO,这将导致系统尝试连接该设备。如果 attach() 成功,则会自动重试 open()。
示例 6-5 典型 attach() 入口点
/*
* Attach an instance of the driver. We take all the knowledge we
* have about our board and check it against what has been filled in
* for us from our FCode or from our driver.conf(4) file.
*/
static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance;
Pio *pio_p;
ddi_device_acc_attr_t da_attr;
static int pio_validate_device(dev_info_t *);
switch (cmd) {
case DDI_ATTACH:
/*
* first validate the device conforms to a configuration this driver
* supports
*/
if (pio_validate_device(dip) == 0)
return (DDI_FAILURE);
/*
* Allocate a soft state structure for this device instance
* Store a pointer to the device node in our soft state structure
* and a reference to the soft state structure in the device
* node.
*/
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(pio_softstate, instance) != 0)
return (DDI_FAILURE);
pio_p = ddi_get_soft_state(pio_softstate, instance);
ddi_set_driver_private(dip, (caddr_t)pio_p);
pio_p->dip = dip;
/*
* Before adding the interrupt, get the interrupt block
* cookie associated with the interrupt specification to
* initialize the mutex used by the interrupt handler.
*/
if (ddi_get_iblock_cookie(dip, 0, &pio_p->iblock_cookie) !=
DDI_SUCCESS) {
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
mutex_init(&pio_p->mutex, NULL, MUTEX_DRIVER, pio_p->iblock_cookie);
/*
* Now that the mutex is initialized, add the interrupt itself.
*/
if (ddi_add_intr(dip, 0, NULL, NULL, pio_intr, (caddr_t)instance) !=
DDI_SUCCESS) {
mutex_destroy(&pio_p>mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* Initialize the device access attributes for the register mapping
*/
dev_acc_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
dev_acc_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
dev_acc_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
/*
* Map in the csr register (register 0)
*/
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&(pio_p->csr), 0,
sizeof (Pio_csr), &dev_acc_attr, &pio_p->csr_handle) !=
DDI_SUCCESS) {
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* Map in the data register (register 1)
*/
if (ddi_regs_map_setup(dip, 1, (caddr_t *)&(pio_p->data), 0,
sizeof (uchar_t), &dev_acc_attr, &pio_p->data_handle) !=
DDI_SUCCESS) {
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
ddi_regs_map_free(&pio_p->csr_handle);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* Create an entry in /devices for user processes to open(2)
* This driver will create a minor node entry in /devices
* of the form: /devices/..../pio@X,Y:pio
*/
if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
instance, DDI_PSEUDO, 0) == DDI_FAILURE) {
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
ddi_regs_map_free(&pio_p->csr_handle);
ddi_regs_map_free(&pio_p->data_handle);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* reset device (including disabling interrupts)
*/
ddi_put8(pio_p->csr_handle, pio_p->csr, PIO_RESET);
/*
* report the name of the device instance which has attached
*/
ddi_report_dev(dip);
return (DDI_SUCCESS);
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
注 - attach() 例程不能对不同设备实例上的调用顺序做出任何假设。系统可以并行调用不同设备实例上的 attach()。系统还可以在不同设备实例上同时调用 attach() 和 detach()。
内核调用驱动程序的 detach(9E) 入口点,通过电源管理来分离设备的某个实例或暂停对设 备某个实例的操作。本节讨论分离设备实例的操作。有关电源管理问题的讨论,请参阅第 12 章。
调用驱动程序detach() 入口点以分离绑定到该驱动程序的设备的某个实例。该入口点是使用要分离的设备节点的实例和指定为该入口点的 cmd 参数的 DDI_DETACH 来调用的。
驱动程序需要取消或等待所有超时或回调完成,然后在返回前释放分配给设备实例的所有资源。如果由于某种原因,驱动程序无法取消未完成的回调以释放资源,则驱动程序需要将设备返回至其初始状态并从入口点返回 DDI_FAILURE,使设备实例保持连接状态。
有两种类型的回调例程:可取消回调例程和不可取消回调例程。驱动程序在 detach(9E) 期间可以原子方式取消 timeout(9F) 和 bufcall(9F) 回调例程。其他类型的回调例程,如 scsi_init_pkt(9F) 和 ddi_dma_buf_bind_handle(9F),则不能被取消。驱动程序必须要么阻塞在 detach() 中直到回调完成,要么使分离请求失败。
示例 6-6 典型 detach() 入口点
/*
* detach(9e)
* free the resources that were allocated in attach(9e)
*/
static int
xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
Pio *pio_p;
int instance;
switch (cmd) {
case DDI_DETACH:
instance = ddi_get_instance(dip);
pio_p = ddi_get_soft_state(pio_softstate, instance);
/*
* turn off the device
* free any resources allocated in attach
*/
ddi_put8(pio_p->csr_handle, pio_p->csr, PIO_RESET);
ddi_remove_minor_node(dip, NULL);
ddi_regs_map_free(&pio_p->csr_handle);
ddi_regs_map_free(&pio_p->data_handle);
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_SUCCESS);
case DDI_SUSPEND:
default:
return (DDI_FAILURE);
}
}
系统调用 getinfo(9E) 以获取仅为驱动程序所知的配置信息。次要设备号到设备实例的映射完全由驱动程序控制。有时系统需要询问驱动程序特定的 dev_t 代表哪个设备。
getinfo() 函数可以采用 DDI_INFO_DEVT2INSTANCE 或 DDI_INFO_DEVT2DEVINFO 作为其 infocmd 参数。DDI_INFO_DEVT2INSTANCE 命令请求设备的实例编号。DDI_INFO_DEVT2DEVINFO 命令请求指向设备的 dev_info 结构的指针。
如果是 DDI_INFO_DEVT2INSTANCE,则 arg 为 dev_t,并且 getinfo() 必须将 dev_t 中的次要设备号转换为实例编号。在以下示例中,次要设备号是实例编号,因此 getinfo() 仅传回次要设备号。在这种情况下,驱动程序不能假定状态结构可用,因为可能在调用 attach() 之前调用 getinfo()。由驱动程序定义的次要设备号和实例编号之间的映射关系可以与此示例中的不同。但是,在所有情况下,映射必须是静态的。
如果是 DDI_INFO_DEVT2DEVINFO,则 arg 仍为 dev_t,因此,getinfo() 首先将设备的实例编号解码。然后 getinfo() 传送回保存在相应设备的驱动程序软状态结构中的 dev_info 指针,如以下示例所示。
示例 6-7 典型 getinfo() 入口点
/*
* getinfo(9e)
* Return the instance number or device node given a dev_t
*/
static int
xxgetinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error;
Pio *pio_p;
int instance = getminor((dev_t)arg);
switch (infocmd) {
/*
* return the device node if the driver has attached the
* device instance identified by the dev_t value which was passed
*/
case DDI_INFO_DEVT2DEVINFO:
pio_p = ddi_get_soft_state(pio_softstate, instance);
if (pio_p == NULL) {
*result = NULL;
error = DDI_FAILURE;
} else {
mutex_enter(&pio_p->mutex);
*result = pio_p->dip;
mutex_exit(&pio_p->mutex);
error = DDI_SUCCESS;
}
break;
/*
* the driver can always return the instance number given a dev_t
* value, even if the instance is not attached.
*/
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)instance;
error = DDI_SUCCESS;
break;
default:
*result = NULL;
error = DDI_FAILURE;
}
return (error);
}
注 - getinfo() 例程必须与驱动程序创建的次要节点保持同步。如果次要节点不同步,则任何热插拔操作都可能失败并导致系统混乱。