编写适用于 Oracle® Solaris 11.2 的设备驱动程序

退出打印视图

更新时间: 2014 年 9 月
 
 

错误处理

本节介绍如何使用 I/O 故障服务 API 来处理驱动程序内的错误。本节讨论驱动程序应如何指示和初始化其故障管理功能、生成错误报告以及注册驱动程序的错误处理程序例程。

被指示提供 FMA 错误报告遥测的驱动程序将检测错误,并确定这些错误对驱动程序所提供服务的影响。检测到错误后,驱动程序应确定其服务受影响的时间以及程度。

    I/O 驱动程序必须立即响应检测到的错误。相应的响应包括:

  • 尝试恢复

  • 重试 I/O 事务

  • 尝试故障转移技术

  • 向调用应用程序/堆栈报告错误

  • 如果以任何其他方式均无法约束错误,则进入紧急状态

驱动程序检测到的错误以 ereport 的形式传递给故障管理守护进程。ereport 是由 FMA 事件协议定义的结构化事件。该事件协议是一组常用数据字段的规范,除了可疑故障列表外,这些字段还必须用于描述所有可能的错误和故障事件。Ereport 被收集为错误遥测流,并分发给诊断引擎。

声明故障管理功能

强化的设备驱动程序必须向 I/O 故障管理框架声明其故障管理功能。使用 ddi_fm_init(9F) 函数声明驱动程序的故障管理功能。

void ddi_fm_init(dev_info_t *dip, int *fmcap, ddi_iblock_cookie_t *ibcp)

可从驱动程序 attach(9E)detach(9E) 入口点的内核上下文中调用 ddi_fm_init() 函数。通常会从 attach() 入口点调用 ddi_fm_init() 函数。ddi_fm_init() 函数根据 fmcap 来分配和初始化资源。fmcap 参数必须设置为以下故障管理功能的按位或:

  • DDI_FM_EREPORT_CAPABLE-在检测到错误状态时,驱动程序负责并且能够生成 FMA 协议错误事件 (ereport)。

  • DDI_FM_ACCCHK_CAPABLE-完成对 I/O 事务的一次或多次访问后,驱动程序负责并且能够检查错误。

  • DDI_FM_DMACHK_CAPABLE-完成一个或多个 DMA I/O 事务后,驱动程序负责并且能够检查错误。

  • DDI_FM_ERRCB_CAPABLE-驱动程序具有错误回调功能。

强化的叶驱动程序通常设置上述所有功能。但是,如果父结点不能支持任何一项请求的功能,则关联的位会被清除并按此情况返回给驱动程序。从 ddi_fm_init (9F) 返回之前,I/O 故障服务框架会创建一组故障管理功能属性:fm-ereport-capable、fm-accchk-capable、fm-dmachk-capable 和 fm-errcb-capable。可使用 prtconf(1M) 命令来观察当前支持的故障管理功能级别。

为了使驱动程序支持故障管理功能的管理选择,请导出故障管理功能级别属性并将其设置为上面 driver.conf(4) 文件中描述的值。在使用所需功能列表调用 ddi_fm_init() 之前,必须设置并读取 fm-capable 属性。

来自 bge 驱动程序的以下示例显示了 bge_fm_init() 函数,该函数调用 ddi_fm_init(9F) 函数。可在 bge_attach() 函数中调用 bge_fm_init() 函数。

static void
bge_fm_init(bge_t *bgep)
{
        ddi_iblock_cookie_t iblk;

        /* Only register with IO Fault Services if we have some capability */
        if (bgep->fm_capabilities) {
                bge_reg_accattr.devacc_attr_access = DDI_FLAGERR_ACC;
                dma_attr.dma_attr_flags = DDI_DMA_FLAGERR;
                /* 
                 * Register capabilities with IO Fault Services
                 */
                ddi_fm_init(bgep->devinfo, &bgep->fm_capabilities, &iblk);
                /*
                 * Initialize pci ereport capabilities if ereport capable
                 */
                if (DDI_FM_EREPORT_CAP(bgep->fm_capabilities) ||
                    DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        pci_ereport_setup(bgep->devinfo);
                /*
                 * Register error callback if error callback capable
                 */
                if (DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        ddi_fm_handler_register(bgep->devinfo,
                        bge_fm_error_cb, (void*) bgep);
        } else {
                /*
                 * These fields have to be cleared of FMA if there are no
                 * FMA capabilities at runtime.
                 */
                bge_reg_accattr.devacc_attr_access = DDI_DEFAULT_ACC;
                dma_attr.dma_attr_flags = 0;
        }
}

清除故障管理资源

ddi_fm_fini(9F) 函数清除为支持 dip 的故障管理而分配的资源。

void ddi_fm_fini(dev_info_t *dip)

可从驱动程序 attach(9E)detach(9E) 入口点的内核上下文中调用 ddi_fm_fini() 函数。

来自 bge 驱动程序的以下示例显示了 bge_fm_fini() 函数,该函数调用 ddi_fm_fini(9F) 函数。可在 bge_unattach() 函数中调用 bge_fm_fini() 函数,而在 bge_attach()bge_detach() 函数中调用 bge_unattach 函数。

static void
bge_fm_fini(bge_t *bgep)
{
        /* Only unregister FMA capabilities if we registered some */
        if (bgep->fm_capabilities) {
                /*
                 * Release any resources allocated by pci_ereport_setup()
                 */
                if (DDI_FM_EREPORT_CAP(bgep->fm_capabilities) ||
                    DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        pci_ereport_teardown(bgep->devinfo);
                /*
                 * Un-register error callback if error callback capable
                 */
                if (DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        ddi_fm_handler_unregister(bgep->devinfo);
                /*
                 * Unregister from IO Fault Services
                 */
                ddi_fm_fini(bgep->devinfo);
        }
}

获取故障管理功能位掩码

ddi_fm_capable(9F) 函数返回当前为 dip 设置的功能位掩码。

void ddi_fm_capable(dev_info_t *dip)

报告错误

对错误事件排队

ddi_fm_ereport_post(9F) 函数对 ereport 事件排队,以便传送给故障管理器守护进程 fmd(1M)

void ddi_fm_ereport_post(dev_info_t *dip, 
                         const char *error_class, 
                         uint64_t ena, 
                         int sflag, ...)

sflag 参数指示调用方是否愿意等待系统内存和事件通道资源变为可用。

ENA 指示此错误报告的错误编号关联 (Error Numeric Association, ENA)。ENA 可能已初始化,并且是从其他错误检测软件模块(如总线结点驱动程序)中获得的。如果 ENA 设置为 0,它将被 ddi_fm_ereport_post() 初始化。

名称-值对 (nvpair) 变量参数列表包含非数组 data_type_t 类型的一个或多个名称、类型、值指针 nvpair 元组,或者包含 data_type_t 数组类型的一个或多个名称、类型、元素数、值指针元组。nvpair 元组补足诊断所需要的 ereport 事件有效负荷。参数列表的结尾由 NULL 指定。

Reporting Standard I/O Controller Errors中介绍的用于 I/O 控制器的 ereport 类名和有效负荷可适用于 error_class。可以定义其他 ereport 类名和有效负荷,但必须在 Oracle 事件注册表中进行注册,并伴有特定于驱动程序的诊断引擎软件或 Eversholt 故障树 (Eversholt fault tree, eft) 规则。

void
bge_fm_ereport(bge_t *bgep, char *detail)
{
        uint64_t ena;
        char buf[FM_MAX_CLASS];
        (void) snprintf(buf, FM_MAX_CLASS, "%s.%s", DDI_FM_DEVICE, detail);
        ena = fm_ena_generate(0, FM_ENA_FMT1);
        if (DDI_FM_EREPORT_CAP(bgep->fm_capabilities)) {
                ddi_fm_ereport_post(bgep->devinfo, buf, ena, DDI_NOSLEEP,
                    FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0, NULL);
        }
}
检测和报告与 PCI 相关的错误

使用 pci_ereport_post(9F) 时,会自动检测和报告与 PCI(包括 PCI、PCI-X 和 PCI-E)相关的错误。

void pci_ereport_post(dev_info_t *dip, ddi_fm_error_t *derr, uint16_t *xx_status)

驱动程序不需要为 PCI 本地总线配置状态寄存器中发生的错误生成特定于驱动程序的 ereport。pci_ereport_post() 函数可以报告数据奇偶校验错误、主机中止、目标中止、发出信号的系统错误等。

如果驱动程序要使用 pci_ereport_post(),则之前必须已在驱动程序的 attach(9E) 例程中调用了 pci_ereport_setup(9F),并且之后必须在驱动程序的 detach(9E) 例程中调用 pci_ereport_teardown(9F)

下面的 bge 代码样例显示了从驱动程序的错误处理程序中调用 pci_ereport_post() 函数的 bge 驱动程序。

/*
 * The I/O fault service error handling callback function
 */
/*ARGSUSED*/
static int
bge_fm_error_cb(dev_info_t *dip, ddi_fm_error_t *err, const void *impl_data)
{
     /*
      * as the driver can always deal with an error 
      * in any dma or access handle, we can just return 
      * the fme_status value.
      */
     pci_ereport_post(dip, err, NULL);
     return (err->fme_status);
}
报告标准 I/O 控制器错误

针对 I/O 控制器的常见错误定义了一组标准的设备 ereport。只要检测到本节中所述的错误症状之一,便应生成这些 ereport。

本节中所述的 ereport 将分发给 eft 诊断引擎以进行诊断,eft 诊断引擎使用一组常用的标准规则来诊断这些 ereport。设备驱动程序检测的其他任何错误都必须在 Sun 事件注册表中定义为 ereport 事件,并必须伴有特定于设备的诊断软件或 eft 规则。

DDI_FM_DEVICE_INVAL_STATE

驱动程序已检测到设备处于无效状态。

当驱动程序检测到所传送或接收的数据看起来无效时,该驱动程序应发布错误。例如,在 bge 代码中,当 bge_chip_reset()bge_receive_ring() 例程检测到无效数据时,这些例程将生成 ereport.io.device.inval_state 错误。

/*
 * The SEND INDEX registers should be reset to zero by the
 * global chip reset; if they're not, there'll be trouble
 * later on.
 */
sx0 = bge_reg_get32(bgep, NIC_DIAG_SEND_INDEX_REG(0));
if (sx0 != 0) {
    BGE_REPORT((bgep, "SEND INDEX - device didn't RESET"));
    bge_fm_ereport(bgep, DDI_FM_DEVICE_INVAL_STATE);
    return (DDI_FAILURE);
}
/* ... */
/*
 * Sync (all) the receive ring descriptors
 * before accepting the packets they describe
 */
DMA_SYNC(rrp->desc, DDI_DMA_SYNC_FORKERNEL);
if (*rrp->prod_index_p >= rrp->desc.nslots) {
    bgep->bge_chip_state = BGE_CHIP_ERROR;
    bge_fm_ereport(bgep, DDI_FM_DEVICE_INVAL_STATE);
    return (NULL);
}
DDI_FM_DEVICE_INTERN_CORR

设备已报告自我纠正的内部错误。例如,设备的内部缓冲区中的硬件已检测到可纠正的 ECC 错误。bge 驱动程序中未使用此错误标志。

DDI_FM_DEVICE_INTERN_UNCORR

设备已报告无法纠正的内部错误。例如,设备的内部缓冲区中的硬件已检测到不可纠正的 ECC 错误。

bge 驱动程序中未使用此错误标志。

DDI_FM_DEVICE_STALL

驱动程序检测到数据传输已意外停顿。

bge_factotum_stall_check() 例程提供了停顿检测的示例。

dogval = bge_atomic_shl32(&bgep->watchdog, 1);
if (dogval < bge_watchdog_count)
    return (B_FALSE);

BGE_REPORT((bgep, "Tx stall detected, 
watchdog code 0x%x", dogval));
bge_fm_ereport(bgep, DDI_FM_DEVICE_STALL);
return (B_TRUE);
DDI_FM_DEVICE_NO_RESPONSE

设备未对驱动程序命令进行响应。

bge_chip_poll_engine(bge_t *bgep, bge_regno_t regno,
        uint32_t mask, uint32_t val)
{
        uint32_t regval;
        uint32_t n;

        for (n = 200; n; --n) {
                regval = bge_reg_get32(bgep, regno);
                if ((regval & mask) == val)
                        return (B_TRUE);
                drv_usecwait(100);
        }
        bge_fm_ereport(bgep, DDI_FM_DEVICE_NO_RESPONSE);
        return (B_FALSE);
}
DDI_FM_DEVICE_BADINT_LIMIT

设备引发了过多的连续性无效中断。

bge() 驱动程序内的 bge_intr 例程提供了有问题的中断检测的示例。bge_fm_ereport() 函数是 ddi_fm_ereport_post(9F) 函数的包装。请参见Queueing an Error Event中的 bge_fm_ereport() 示例。

if (bgep->missed_dmas >= bge_dma_miss_limit) {
    /*
     * If this happens multiple times in a row,
     * it means DMA is just not working.  Maybe
     * the chip has failed, or maybe there's a
     * problem on the PCI bus or in the host-PCI
     * bridge (Tomatillo).
     *
     * At all events, we want to stop further
     * interrupts and let the recovery code take
     * over to see whether anything can be done
     * about it ...
     */
    bge_fm_ereport(bgep,
        DDI_FM_DEVICE_BADINT_LIMIT);
    goto chip_stop;
}
服务影响函数

具有故障管理功能的驱动程序必须指示错误是否影响了设备所提供的服务。检测到错误并在必要时关闭服务之后,驱动程序应调用 ddi_fm_service_impact(9F) 例程来反映设备实例的当前服务状态。诊断和恢复软件可以使用该服务状态来帮助确定问题或对问题做出反应。

当驱动程序本身检测到错误时以及框架检测到错误并将访问或 DMA 句柄标记为有故障时,均应调用 ddi_fm_service_impact() 例程。

void ddi_fm_service_impact(dev_info_t *dip, int svc_impact)

ddi_fm_service_impact() 接受以下服务影响值 (svc_impact):

DDI_SERVICE_LOST

由于设备故障或软件缺陷,设备提供的服务不可用。

DDI_SERVICE_DEGRADED

驱动程序无法提供正常服务,但驱动程序可以提供部分服务或降级的服务。例如,驱动程序可能必须重复尝试执行操作才能取得成功,或者它至少要以配置的速度运行。

DDI_SERVICE_UNAFFECTED

驱动程序已检测到错误,但设备实例提供的服务不会受到影响。

DDI_SERVICE_RESTORED

设备提供的所有服务都已恢复。

调用 ddi_fm_service_impact() 时会根据服务影响例程的服务影响参数代表驱动程序生成以下 ereport:

  • ereport.io.service.lost

  • ereport.io.service.degraded

  • ereport.io.service.unaffected

  • ereport.io.service.restored

在以下 bge 代码中,驱动程序确定由于出现错误,它无法成功地重新开始传送或接收数据包。设备的服务状态转换为 DDI_SERVICE_LOST。

/*
 * All OK, reinitialize hardware and kick off GLD scheduling
 */
mutex_enter(bgep->genlock);
if (bge_restart(bgep, B_TRUE) != DDI_SUCCESS) {
    (void) bge_check_acc_handle(bgep, bgep->cfg_handle);
    (void) bge_check_acc_handle(bgep, bgep->io_handle);
    ddi_fm_service_impact(bgep->devinfo, DDI_SERVICE_LOST);
    mutex_exit(bgep->genlock);
    return (DDI_FAILURE);
}

注 - 不应从已注册的回调例程中调用 ddi_fm_service_impact() 函数。