编写设备驱动程序

第 7 章 设备访问:程控 I/O

Solaris OS 为驱动程序开发者提供了一整套用于访问设备内存的接口。这些接口旨在通过处理处理器和设备字节存储顺序之间的不匹配,并强制实施设备可能具有的任何数据顺序相关性,使驱动程序与平台无关。通过使用这些接口,可以开发一种可在 SPARC 和 x86 处理器体系结构以及每个相应处理器系列的各种平台上运行的单个源驱动程序。

本章介绍有关以下主题的信息:

设备内存

系统会为支持程控 I/O 的设备指定一个或多个总线地址空间区域,这些区域映射到设备的可寻址区域。这些映射在与设备相关的 reg 属性中描述为值对。每个值对描述一段总线地址。

驱动程序通过指定寄存器编号(即 regspec,设备的 reg 属性的索引)来标识特定的总线地址映射。reg 属性标识设备的 busaddrsize。驱动程序在调用 DDI 函数(如 ddi_regs_map_setup(9F))时传递寄存器编号。驱动程序通过调用 ddi_dev_nregs(9F) 可以确定已为设备指定的可映射区域数。

管理设备和主机字节序之间的差别

主机的数据格式可以与设备的数据格式具有不同的字节序特征。在这种情况下,主机与设备间传送的数据需要进行字节交换,才能符合目标位置的数据格式要求。与主机具有相同字节序特征的设备无需对数据进行字节交换。

驱动程序通过在传递给 ddi_regs_map_setup(9F)ddi_device_acc_attr(9S) 结构中设置相应的标志来指定设备的字节序特征。然后,DDI 框架在驱动程序调用 ddi_getX 例程(如 ddi_get8(9F))或 ddi_putX 例程(如 ddi_put16(9F))来读/写设备内存时,执行任何所需的字节交换。

管理数据排序要求

平台可以重新排列数据的负载和存储,以优化平台的性能。由于某些设备可能不允许重新排列,因此驱动程序在设置到设备的映射时需要指定设备的排序要求。

ddi_device_acc_attr 结构

此结构描述设备的字节序和数据顺序要求。驱动程序需要对此结构进行初始化并将其作为一个参数传递给 ddi_regs_map_setup(9F)

typedef struct ddi_device_acc_attr {
    ushort_t       devacc_attr_version;
    uchar_t    devacc_attr_endian_flags;
    uchar_t    devacc_attr_dataorder;
} ddi_device_acc_attr_t;
devacc_attr_version

指定 DDI_DEVICE_ATTR_V0

devacc_attr_endian_flags

描述设备的字节序特征。指定为一个位值,其可能值包括:

  • DDI_NEVERSWAP_ACC-从不交换数据

  • DDI_STRUCTURE_BE_ACC-设备数据格式为大端字节序

  • DDI_STRUCTURE_LE_ACC-设备数据格式为小端字节序

devacc_attr_dataorder

描述 CPU 根据设备的要求引用数据时必须遵循的顺序。指定为一个枚举值,其中数据访问限制的排列顺序为最严格到最不严格。

  • DDI_STRICTORDER_ACC-主机必须按程序员指定的顺序发出引用。此标志为缺省行为。

  • DDI_UNORDERED_OK_ACC-允许主机重新排列到设备内存的负载和存储。

  • DDI_MERGING_OK_ACC-允许主机将单个存储合并到连续位置。此设置还表明需要重新排列。

  • DDI_LOADCACHING_OK_ACC-允许主机从设备读取数据,直到发生存储。

  • DDI_STORECACHING_OK_ACC-允许主机对写入设备的数据进行高速缓存。然后,主机可以延迟将数据写入设备,直到将来某一时间。


注 –

系统对数据的访问可能会比驱动程序在 devacc_attr_dataorder 中所做指定更严格。就数据访问而言,由从必须遵循严格的数据排序到可以执行高速缓存存储操作,驱动程序对主机的限制依次降低。


映射设备内存

驱动程序通常会在执行 attach(9E) 期间映射设备的所有区域。驱动程序通过调用 ddi_regs_map_setup(9F)、指定要映射的区域寄存器编号、区域的设备访问属性以及偏移和大小来映射设备内存区域。DDI 框架为设备区域设置映射并将一个不透明句柄返回给驱动程序。在从设备区域读取数据或向其中写入数据时,此数据访问句柄将作为一个参数传递给 ddi_get8(9F)ddi_put8(9F) 系列例程。

驱动程序通过检查设备导出的映射数来验证设备映射的形式与驱动程序预期的形式是否匹配。驱动程序调用 ddi_dev_nregs(9F),然后调用 ddi_dev_regsize(9F) 来验证每个映射的大小。

映射设置示例

下面的简单示例说明了 DDI 数据访问接口。此驱动程序用于虚构的小端字节序设备,该设备每次接受一个字符并在准备好接受另一个字符时生成中断。此设备实现两个寄存器集: 第一个是 8 位 CSR 寄存器,第二个是 8 位数据寄存器。


示例 7–1 映射设置

    #define CSR_REG 0
    #define DATA_REG 1
    /*
     * 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, CSR_REG, (caddr_t *)&(pio_p->csr), 0,
      sizeof (Pio_csr), &dev_acc_attr, &pio_p->csr_handle) != DDI_SUCCESS) {
    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, DATA_REG, (caddr_t *)&(pio_p->data), 0,
      sizeof (uchar_t), &dev_acc_attr, &pio_p->data_handle) \
            != DDI_SUCCESS) {
    mutex_destroy(&pio_p->mutex);
    ddi_regs_map_free(&pio_p->csr_handle);
    ddi_soft_state_free(pio_softstate, instance);
    return (DDI_FAILURE);
    }

设备访问函数

驱动程序结合使用 ddi_get8(9F)ddi_put8(9F) 系列例程以及 ddi_regs_map_setup(9F) 返回的句柄,以与设备相互传送数据。DDI 框架自动处理为满足主机或设备的字节序格式所需的任何字节交换,并强制实施设备可能具有的任何存储排序约束。

DDI 提供了用于传送 8 位、16 位、32 位和 64 位数据的接口,以及用于重复传送多个值的接口。有关这些接口的完整列表和说明,请参见 ddi_get8(9F)ddi_put8(9F)ddi_rep_get8(9F)ddi_rep_put8(9F) 系列例程的手册页。

以下示例建立在示例 7–1 的基础上,其中,驱动程序映射了设备的 CSR 寄存器和数据寄存器。在本示例中,调用驱动程序的 write(9E) 入口点时,会将数据缓冲区写入(每次一个字节)设备。


示例 7–2 映射设置:缓冲区

static  int
pio_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
    int  retval;
    int  error = OK;
    Pio *pio_p = ddi_get_soft_state(pio_softstate, getminor(dev));
    if (pio_p == NULL)
    return (ENXIO);
    mutex_enter(&pio_p->mutex);
    /*
     * enable interrupts from the device by setting the Interrupt
     * Enable bit in the devices CSR register
     */
    ddi_put8(pio_p->csr_handle, pio_p->csr,
      (ddi_get8(pio_p->csr_handle, pio_p->csr) | PIO_INTR_ENABLE));
    while (uiop->uio_resid > 0) {
    /*
     * This device issues an IDLE interrupt when it is ready
     * to accept a character; the interrupt can be cleared
     * by setting PIO_INTR_CLEAR.  The interrupt is reasserted
     * after the next character is written or the next time
     * PIO_INTR_ENABLE is toggled on.
     *
     * wait for interrupt (see pio_intr)
     */
     cv_wait(&pio_p->cv, &pio_p->mutex);
     /*
      * get a character from the user's write request
      * fail the write request if any errors are encountered
      */
     if ((retval = uwritec(uiop)) == -1) {
         error = retval;
         break;
     }
     /*
      * pass the character to the device by writing it to
      * the device's data register
      */
     ddi_put8(pio_p->data_handle, pio_p->data, (uchar_t)retval);
    }
    /*
     * disable interrupts by clearing the Interrupt Enable bit
     * in the CSR
     */
    ddi_put8(pio_p->csr_handle, pio_p->csr,
      (ddi_get8(pio_p->csr_handle, pio_p->csr) & ~PIO_INTR_ENABLE));
    mutex_exit(&pio_p->mutex);
    return (error);
}

备用设备访问接口

除通过 ddi_get8(9F)ddi_put8(9F) 接口系列实现所有设备访问之外,Solaris OS 还提供特定于特殊总线实现的接口。虽然在某些平台上这些函数会更加有效,但使用这些例程会限制驱动程序在设备的各总线版本间保持可移植的能力。

访问内存空间

对于内存映射访问,设备寄存器会出现在内存地址空间中。驱动程序可以将 ddi_getX 系列例程和 ddi_putX 系列用作标准设备访问接口的备用接口。

访问 I/O 空间

对于 I/O 空间访问,设备寄存器会出现在 I/O 空间中,其中每个可寻址元素都称为 I/O 端口。驱动程序可以将 ddi_io_get8(9F)ddi_io_put8(9F) 例程用作标准设备访问接口的备用接口。

PCI 配置空间访问

要在不使用常规设备访问接口的情况下访问 PCI 配置空间,驱动程序需要通过调用 pci_config_setup(9F)(而非 ddi_regs_map_setup(9F))来映射 PCI 配置空间。然后,驱动程序可以调用 pci_config_get8(9F)pci_config_put8(9F) 接口系列,以访问 PCI 配置空间。