编写设备驱动程序

第 6 章 驱动程序自动配置

自动配置表示驱动程序会将代码和静态数据装入内存中。随后在系统中注册此信息。在自动配置过程中还会连接由驱动程序控制的各个设备实例。

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

驱动程序的装入和卸载

系统从用于自动配置的内核模块目录的 drv 子目录装入驱动程序二进制模块。请参见将驱动程序复制到模块目录

将模块读入内存且解析了所有符号之后,系统将调用此模块的 _init(9E) 入口点。_init() 函数将调用 mod_install(9F),实际上就是装入此模块。


注 –

调用 mod_install() 期间,一旦调用了 mod_install(),其他线程便可以调用 attach(9E)。从编程角度来看,在调用 mod_install() 之前必须执行所有 _init() 初始化。如果 mod_install() 失败(即返回非零值),则必须取消初始化。


一旦 _init() 成功完成,便会在系统中正确注册驱动程序。实际上,此时驱动程序并不管理任何设备。设备管理是在设备配置过程中进行的。

为了节省系统内存或根据用户的明确请求,系统会卸载驱动程序二进制模块。从内存中删除驱动程序代码和数据之前,将调用该驱动程序的 _fini(9E) 入口点。当且仅当 _fini() 返回成功信息时,才会卸载驱动程序。

下图概述了设备驱动程序的结构。阴影区域突出显示驱动程序的数据结构和入口点。阴影区域的上半部分包括支持驱动程序装入和卸载的数据结构和入口点。下半部分与驱动程序配置相关。

图 6–1 模块装入和自动配置入口点

图中显示了用于自动配置和模块装入的结构和入口点。

驱动程序必需的数据结构

为了支持自动配置,驱动程序需要静态初始化以下数据结构:

驱动程序依赖于图 5-1 中的数据结构。必须提供并正确初始化这些数据结构。没有这些数据结构,可能无法正确装入驱动程序。结果导致可能无法装入必需的例程。如果驱动程序不支持某个操作,则 nodev(9F) 例程的地址可以用作占位符。在某些情况下,驱动程序支持入口点,并且仅需要返回成功信息或失败信息。在这种情况下,可以使用例程 nulldev(9F) 的地址。


注 –

应该在编译时对这些结构进行初始化。在任何其他时间,驱动程序都不应访问或更改这些结构。


modlinkage 结构

static struct modlinkage xxmodlinkage = {
    MODREV_1,       /* ml_rev */
    &xxmodldrv,     /* ml_linkage[] */
    NULL            /* NULL termination */
};

第一个字段是装入子系统的模块的版本号。该字段应为 MODREV_1。第二个字段指向接下来定义的驱动程序的 modldrv 结构。该结构的最后一个元素应始终为 NULL

modldrv 结构

static struct modldrv xxmodldrv = {
    &mod_driverops,           /* drv_modops */
    "generic driver v1.1",    /* drv_linkinfo */
    &xx_dev_ops               /* drv_dev_ops */
};

该结构更加详细地描述模块。第一个字段提供有关模块安装的信息。对于驱动程序模块,该字段应设置为 &mod_driverops。第二个字段是将由 modinfo(1M) 显示的字符串。第二个字段应包含足够的信息,以便确定生成驱动程序二进制文件的源代码版本。最后一个字段指向下节所定义的驱动程序的 dev_ops 结构。

dev_ops 结构

static struct dev_ops xx_dev_ops = {
    DEVO_REV,       /* devo_rev */
    0,              /* devo_refcnt  */
    xxgetinfo,      /* devo_getinfo: getinfo(9E) */
    nulldev,        /* devo_identify: identify(9E) */
    xxprobe,        /* devo_probe: probe(9E) */
    xxattach,       /* devo_attach: attach(9E) */
    xxdetach,       /* devo_detach: detach(9E) */
    nodev,          /* devo_reset */
    &xx_cb_ops,     /* devo_cb_ops */
    NULL,           /* devo_bus_ops */
    &xxpower        /* devo_power: power(9E) */
};

使用 dev_ops(9S) 结构,内核可以找到设备驱动程序的自动配置入口点。devo_rev 字段标识结构的修订号。该字段必须设置为 DEVO_REVdevo_refcnt 字段必须初始化为零。应使用相应驱动程序的入口点地址填充函数地址字段,但以下情况除外:

devo_cb_ops 成员应包含 cb_ops(9S) 结构的地址。devo_bus_ops 字段必须设置为 NULL

cb_ops 结构

static struct cb_ops xx_cb_ops = {
    xxopen,         /* open(9E) */
    xxclose,        /* close(9E) */
    xxstrategy,     /* strategy(9E) */
    xxprint,        /* print(9E) */
    xxdump,         /* dump(9E) */
    xxread,         /* read(9E) */
    xxwrite,        /* write(9E) */
    xxioctl,        /* ioctl(9E) */
    xxdevmap,       /* devmap(9E) */
    nodev,          /* mmap(9E) */
    xxsegmap,       /* segmap(9E) */
    xxchpoll,       /* chpoll(9E) */
    xxprop_op,      /* prop_op(9E) */
    NULL,           /* streamtab(9S) */
    D_MP | D_64BIT, /* cb_flag */
    CB_REV,         /* cb_rev */
    xxaread,        /* aread(9E) */
    xxawrite        /* awrite(9E) */
};

cb_ops(9S) 结构包含设备驱动程序的字符操作和块操作的入口点。驱动程序不支持的所有入口点应初始化为 nodev(9F)。例如,字符设备驱动程序应该将所有块字段(例如 cb_stategy)设置为 nodev(9F)。请注意,保留 mmap(9E) 入口点是为了兼容早期发行版。驱动程序应使用 devmap(9E) 入口点来进行设备内存映射。如果支持 devmap(9E),应将 mmap(9E) 设置为 nodev(9F)

streamtab 字段表明驱动程序是否基于 STREAMS。只有第 19 章中讨论的网络设备驱动程序基于 STREAMS。所有不基于 STREAMS 的驱动程序必须streamtab 字段设置为 NULL

cb_flag 成员包含以下标志:

cb_revcb_ops 结构修订号。该字段必须设置为 CB_REV

可装入驱动程序接口

设备驱动程序必须是可动态装入的。驱动程序还应是可卸载的,以帮助节省内存资源。可卸载驱动程序还应易于测试、调试和修补。

每个设备驱动程序都需要实现 _init(9E)_fini(9E)_info(9E) 入口点以支持驱动程序的装入和卸载。以下示例给出了可装入驱动程序接口的典型实现。


示例 6–1 可装入接口部分

static void *statep;                /* for soft state routines */
static struct cb_ops xx_cb_ops;     /* forward reference */
static struct dev_ops xx_ops = {
    DEVO_REV,
    0,
    xxgetinfo,
    nulldev,
    xxprobe,
    xxattach,
    xxdetach,
    xxreset,
    nodev,
    &xx_cb_ops,
    NULL,
    xxpower
};

static struct modldrv modldrv = {
    &mod_driverops,
    "xx driver v1.0",
    &xx_ops
};

static struct modlinkage modlinkage = {
    MODREV_1,
    &modldrv,
    NULL
};

int
_init(void)
{
    int error;
    ddi_soft_state_init(&statep, sizeof (struct xxstate),
        estimated_number_of_instances);
    /* further per-module initialization if necessary */
    error = mod_install(&modlinkage);
    if (error != 0) {
        /* undo any per-module initialization done earlier */
        ddi_soft_state_fini(&statep);
    }
    return (error);
}

int
_fini(void)
{
    int error;
    error = mod_remove(&modlinkage);
    if (error == 0) {
        /* release per-module resources if any were allocated */
        ddi_soft_state_fini(&statep);
    }
    return (error);
}

int
_info(struct modinfo *modinfop)
{
    return (mod_info(&modlinkage, modinfop));
}

_init() 示例

以下示例给出了典型的 _init(9E) 接口。


示例 6–2 _init() 函数

static void *xxstatep;
int
_init(void)
{
    int error;
    const int max_instance = 20;    /* estimated max device instances */

    ddi_soft_state_init(&xxstatep, sizeof (struct xxstate), max_instance);
    error = mod_install(&xxmodlinkage);
    if (error != 0) {
        /*
         * Cleanup after a failure
         */
        ddi_soft_state_fini(&xxstatep);
    }
    return (error);
}

_init() 中装入驱动程序期间,驱动程序应执行所有一次性资源分配或数据初始化。例如,在该例程中驱动程序应初始化所有对于该驱动程序为全局互斥锁的互斥锁。但是,驱动程序不应使用 _init(9E) 来分配或初始化与设备特定实例有关的任何内容。必须在 attach(9E) 中完成每个实例的初始化。例如,如果打印机的驱动程序可以同时处理多台打印机,则该驱动程序应在 attach() 中分配特定于每台打印机实例的资源。


注 –

一旦 _init(9E) 调用了 mod_install(9F),驱动程序便不应更改连接至 modlinkage(9S) 结构的任何数据结构,因为系统可能会复制或更改这些数据结构。


_fini() 示例

以下示例给出了 _fini() 例程。

int
_fini(void)
{
        int error;
    error = mod_remove(&modlinkage);
    if (error != 0) {
        return (error);
    }
    /*
     * Cleanup resources allocated in _init()
     */
    ddi_soft_state_fini(&xxstatep);
    return (0);
}

同样,在 _fini() 中,驱动程序应该释放在 _init() 中分配的所有资源。驱动程序必须将其自身从系统模块列表中删除。


注 –

将驱动程序连接至硬件实例时,可能会调用 _fini()。在本示例中,mod_remove(9F) 返回失败信息。因此,在 mod_remove() 返回成功信息之前,不应释放驱动程序资源。


_info() 示例

以下示例给出了 _info(9E) 例程。

int
_info(struct modinfo *modinfop)
{
    return (mod_info(&xxmodlinkage, modinfop));
}

调用该驱动程序是为了返回模块信息。应按如上所示实现入口点。

设备配置概念

系统基于节点名称和 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() 入口点

对于非自我识别设备,probe(9E) 入口点应确定系统上是否存在硬件设备。

对于 probe(),要确定是否存在设备实例,probe()需要执行通常 attach(9E) 也执行的许多任务。尤其是,probe() 可能需要映射设备寄存器。

探测设备寄存器是特定于设备的。驱动程序通常必须执行一系列硬件测试来确保硬件确实存在。测试条件必须足够严格以避免错误地识别设备。例如,在设备实际上不可用的情况下可能显示存在该设备,因为异常设备在行为上看起来与预期的设备相似。

测试返回以下标志:

对于给定设备实例,直到 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() 入口点

内核调用驱动程序的 attach(9E) 入口点来连接设备实例或针对已由电源管理框架暂停或关闭的设备实例恢复操作。本节仅讨论连接设备实例的操作。电源管理将在第 12 章中讨论。

调用驱动程序的 attach(9E) 入口点以连接每个绑定到驱动程序的设备实例。基于要连接的设备节点实例,并将 attach(9E)cmd 参数指定为 DDI_ATTACH,来调用此入口点。attach 入口点主要包括以下类型的处理:

驱动程序软状态管理

为了协助设备驱动程序编写人员分配状态结构,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) 时会创建次要节点。驱动程序提供次要设备号次要名称次要节点类型,以及次要节点是代表块设备还是字符设备。

驱动程序可以为设备创建任意数量的次要节点。Solaris DDI/DKI 期望某些类别的设备具有以特定格式创建的次要节点。例如,期望磁盘驱动程序为连接的每个物理磁盘实例创建 16 个次要节点。将创建八个代表块设备接口 a - h 的次要节点,另外八个次要节点代表字符设备接口 a,raw - h,raw

传递给 ddi_create_minor_node(9F)次要设备号全部由驱动程序定义。次要设备号通常是设备实例编号和次要节点标识符的编码。在前面的示例中,驱动程序会为每个次要节点创建次要设备号,方法是将设备的实例编号左移三位,再将该结果与次要节点索引进行“或”运算。次要节点索引值的范围介于 0 和 7 之间。请注意,次要节点 aa,raw 共用同一次要设备号。这些次要节点根据传递到 ddi_create_minor_node()spec_type 参数来区分。

传递给 ddi_create_minor_node(9F)次要节点类型对设备类型进行分类,如磁盘、磁带、网络接口、帧缓存器等。

下表列出了可以创建的可能的节点类型。

表 6–1 可能节点类型

常量 

说明 

DDI_NT_SERIAL

串行端口 

DDI_NT_SERIAL_DO

拨出端口 

DDI_NT_BLOCK

硬盘 

DDI_NT_BLOCK_CHAN

带有通道或目标编号的硬盘 

DDI_NT_CD

ROM 驱动器 (CD-ROM) 

DDI_NT_CD_CHAN

带有通道或目标编号的 ROM 驱动器 

DDI_NT_FD

软盘 

DDI_NT_TAPE

磁带机 

DDI_NT_NET

网络设备 

DDI_NT_DISPLAY

显示设备 

DDI_NT_MOUSE

鼠标 

DDI_NT_KEYBOARD

键盘 

DDI_NT_AUDIO

音频设备 

DDI_PSEUDO

通用的伪设备 

节点类型 DDI_NT_BLOCKDDI_NT_BLOCK_CHANDDI_NT_CDDDI_NT_CD_CHAN 会使 devfsadm(1M) 将设备实例标识为磁盘,并在 /dev/dsk/dev/rdsk 目录中创建名称。

节点类型 DDI_NT_TAPE 会使 devfsadm(1M) 将设备实例标识为磁带,并在 /dev/rmt 目录中创建名称。

节点类型 DDI_NT_SERIALDDI_NT_SERIAL_DO 会使 devfsadm(1M) 执行以下操作:

供应商提供的字符串应包括使字符串唯一的标识值,如名称或股票名称。该字符串可与 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() 入口点

内核调用驱动程序的 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() 入口点

系统调用 getinfo(9E) 以获取仅为驱动程序所知的配置信息。次要设备号到设备实例的映射完全由驱动程序控制。有时系统需要询问驱动程序特定的 dev_t 代表哪个设备。

getinfo() 函数可以采用 DDI_INFO_DEVT2INSTANCEDDI_INFO_DEVT2DEVINFO 作为其 infocmd 参数。DDI_INFO_DEVT2INSTANCE 命令请求设备的实例编号。DDI_INFO_DEVT2DEVINFO 命令请求指向设备的 dev_info 结构的指针。

如果是 DDI_INFO_DEVT2INSTANCE,则 argdev_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() 例程必须与驱动程序创建的次要节点保持同步。如果次要节点不同步,则任何热插拔操作都可能失败并导致系统混乱。


使用设备 ID

使用 Solaris DDI 接口,驱动程序可以提供设备 ID,即设备的永久唯一标识符。设备 ID 可用于识别或查找设备。设备 ID 独立于 /devices 名称或设备编号 (dev_t)。应用程序可以使用 libdevid(3LIB) 中定义的函数来读取和处理由驱动程序注册的设备 ID。

在驱动程序可以导出设备 ID 之前,驱动程序需要检验设备是否可以提供唯一 ID 或者将主机生成的唯一 ID 存储在正常情况下不可访问的区域中。例如,通用编号 (world-wide number, WWN) 是设备提供的唯一 ID。例如,设备 NVRAM 和保留扇区是不可访问区域,主机生成的唯一 ID 可以安全地存储在此区域中。

注册设备 ID

通常,驱动程序在其 attach(9E) 处理程序中初始化和注册设备 ID。如上所述,驱动程序负责注册永久设备 ID。同时,驱动程序可能需要处理可直接提供唯一 ID (WWN) 的设备和向稳定存储器写入及从稳定存储器读取虚构 ID 的设备。

注册设备提供的 ID

如果设备可以为驱动程序提供唯一的标识符,则驱动程序可以直接使用此标识符初始化设备 ID 并使用 Solaris DDI 注册此 ID。

/*
 * The device provides a guaranteed unique identifier,
 * in this case a SCSI3-WWN.  The WWN for the device has been
 * stored in the device's soft state.
 */
if (ddi_devid_init(dip, DEVID_SCSI3_WWN, un->un_wwn_len, un->un_wwn,
    &un->un_devid) != DDI_SUCCESS)
    return (DDI_FAILURE);

(void) ddi_devid_register(dip, un->un_devid);

注册虚构 ID

驱动程序还可能为不直接提供唯一 ID 的设备注册设备 ID。注册这些 ID 需要设备能够存储并检索保留区中的少量数据。随后,驱动程序可创建虚构设备 ID 并将其写入保留区中。

/*
 * the device doesn't supply a unique ID, attempt to read
 * a fabricated ID from the device's reserved data.
 */

if (xxx_read_deviceid(un, &devid_buf) == XXX_OK) {
    if (ddi_devid_valid(devid_buf) == DDI_SUCCESS) {
        devid_sz = ddi_devi_sizeof(devid_buf);
        un->un_devid = kmem_alloc(devid_sz, KM_SLEEP);
        bcopy(devid_buf, un->un_devid, devid_sz);
        ddi_devid_register(dip, un->un_devid);
        return (XXX_OK);
    }
}

/*
 * we failed to read a valid device ID from the device
 * fabricate an ID, store it on the device, and register
 * it with the DDI
 */

if (ddi_devid_init(dip, DEVID_FAB, 0, NULL, &un->un_devid)
    == DDI_FAILURE) {
    return (XXX_FAILURE);
}

if (xxx_write_deviceid(un) != XXX_OK) {
    ddi_devid_free(un->un_devid);
    un->un_devid = NULL;
    return (XXX_FAILURE);
}

ddi_devid_register(dip, un->un_devid);
return (XXX_OK);

注销设备 ID

通常,驱动程序会注销并释放处理 detach(9E) 时分配的所有设备 ID。驱动程序首先调用 ddi_devid_unregister(9F) 来注销设备实例的设备 ID。然后,驱动程序必须通过调用 ddi_devid_free(9F) 并传送已由 ddi_devid_init(9F) 返回的句柄来释放设备 ID 句柄自身。驱动程序负责管理为 WWN 或序列号数据分配的任何空间。