JavaScript is required to for searching.
跳过导航链接
退出打印视图
编写设备驱动程序     Oracle Solaris 10 1/13 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

第 1 部分针对 Oracle Solaris 平台设计设备驱动程序

1.  Oracle Solaris 设备驱动程序概述

2.  Oracle Solaris 内核和设备树

3.  多线程

4.  属性

5.  管理事件和排队任务

6.  驱动程序自动配置

7.  设备访问:程控 I/O

8.  中断处理程序

9.  直接内存访问 (Direct Memory Access, DMA)

10.  映射设备和内核内存

11.  设备上下文管理

12.  电源管理

13.  强化 Oracle Solaris 驱动程序

Oracle 故障管理体系结构 I/O 故障服务

什么是预测性自我修复?

Oracle Solaris Fault Manager

诊断、可疑列表和故障事件

响应代理

消息 ID 和字典文件

系统拓扑

错误处理

声明故障管理功能

清除故障管理资源

获取故障管理功能位掩码

报告错误

访问属性结构

DMA 属性结构

获取错误状态

清除错误

注册错误处理程序

故障管理数据和状态结构

诊断故障

标准叶设备诊断

专门的设备诊断

事件注册表

词汇表

用于 Oracle Solaris 设备驱动程序的防御性编程技术

使用单独的设备驱动程序实例

独占使用 DDI 访问句柄

检测已损坏的数据

设备管理和控制数据的损坏

已接收数据的损坏

DMA 隔离

处理有问题的中断

其他编程注意事项

线程交互

自上而下请求的威胁

自适应策略

驱动程序强化测试工具

故障注入

设置测试工具

安装测试工具

配置测试工具

测试驱动程序

创建故障

注入故障

故障注入过程

测试工具警告

使用脚本自动完成测试过程

自动化测试过程

14.  分层驱动程序接口 (Layered Driver Interface, LDI)

第 2 部分设计特定种类的设备驱动程序

15.  字符设备驱动程序

16.  块设备驱动程序

17.  SCSI 目标驱动程序

18.  SCSI 主机总线适配器驱动程序

19.  网络设备驱动程序

20.  USB 驱动程序

21.  SR-IOV 驱动程序

第 3 部分生成设备驱动程序

22.  编译、装入、打包和测试驱动程序

23.  调试、测试和调优设备驱动程序

24.  推荐的编码方法

第 4 部分附录

A.  硬件概述

B.  Solaris DDI/DKI 服务汇总

C.  使设备驱动程序支持 64 位

D.  控制台帧缓存器驱动程序

E.  pci.conf 文件

索引

Oracle 故障管理体系结构 I/O 故障服务

本节介绍如何为 I/O 设备驱动程序集成故障管理错误报告、错误处理和诊断。本节深入探讨了 I/O 故障服务框架以及如何在设备驱动程序内利用 I/O 故障服务 API。

本节讨论以下主题:

什么是预测性自我修复?

传统上,系统会将硬件和软件错误信息以系统日志消息的形式直接导出给管理员以及管理软件。错误检测、诊断、报告和处理通常会嵌入到每个驱动程序的代码中。

Oracle Solaris OS 预测性自我修复系统这样的系统首先同时最重要的是自我诊断。自我诊断意味着系统提供根据观察到的症状自动诊断问题的技术,然后将诊断结果用于触发自动响应和恢复。硬件故障或软件缺陷可与一组可能观察到的、称为错误的症状相关联。系统在观测到错误时随之生成的数据称为错误报告或 ereport

在具有自我修复功能的系统中,ereport 由系统捕获,并编码为由可扩展事件协议描述的一组名称-值对,从而形成 ereport 事件。收集 ereport 事件和其他数据是为了便于进行自我修复,还会将其分发给名为诊断引擎的软件组件,诊断引擎设计用来诊断与系统检测到的错误症状对应的底层问题。诊断引擎在后台运行,并以无提示方式使用错误遥测,直到它可以生成诊断或预测故障为止。

在处理足够的遥测从而得出结论之后,诊断引擎会生成名为故障事件的另一个事件。然后,会向对特定故障事件感兴趣的所有代理广播该故障事件。代理是可对特定故障事件启动恢复和做出响应的软件组件。被称为 Oracle Solaris Fault Manager 的软件组件 fmd(1M) 可在 ereport 生成器、诊断引擎和代理软件之间管理事件的多路复用。

Oracle Solaris Fault Manager

Oracle Solaris Fault Manager (fmd(1M)) 负责将传入的错误遥测事件分发给相应的诊断引擎。诊断引擎负责识别产生错误症状的基础硬件故障或软件缺陷。fmd(1M) 守护进程是故障管理器的 Oracle Solaris OS 实现。它在引导时启动,并且会装入系统中可用的所有诊断引擎和代理。Oracle Solaris Fault Manager 还为系统管理员和服务人员提供了用于观察故障管理活动的界面。

诊断、可疑列表和故障事件

进行诊断后,会以 list.suspect 事件的形式输出诊断。list.suspect 事件是由一个或多个可能的故障或缺陷事件构成的事件。有时候,诊断无法将错误原因的范围缩小至单个故障或缺陷。例如,底层问题可能是连接控制器与主系统总线的线路中断。问题也可能在于总线上的某个组件或总线本身。在这种特定情况下,list.suspect 事件将包含多个故障事件:一个是连接到总线的每个控制器的事件,另一个是总线本身的事件。

除了介绍诊断的故障外,故障事件还包含四个可应用诊断的有效负荷成员。

例如,在给定时间内针对特定内存位置收到特定数量的 ECC 可纠正错误后,CPU 和内存诊断引擎会对有故障的 DIMM 发出诊断(list.suspect 事件)。

# fmdump -v -u 38bd6f1b-a4de-4c21-db4e-ccd26fa8573c
TIME                 UUID                                 SUNW-MSG-ID
Oct 31 13:40:18.1864 38bd6f1b-a4de-4c21-db4e-ccd26fa8573c AMD-8000-8L
100%  fault.cpu.amd.icachetag

Problem in: hc:///motherboard=0/chip=0/cpu=0
Affects: cpu:///cpuid=0
FRU: hc:///motherboard=0/chip=0
Location: SLOT 2

在此示例中,fmd(1M) 识别到资源中的一个问题,具体而言是 CPU (hc:///motherboard=0/chip=0/cpu=0 )。为了抑制出现更多错误症状并防止发生无法纠正的错误,对一个 ASRU (cpu:///cpuid=0) 进行了标识,以便弃用 (retirement)。需要更换的组件是 FRU (hc:///motherboard=0/chip=0)。

响应代理

代理是可为了响应诊断或修复而执行操作的软件组件。例如,CPU 和内存弃用代理设计为对包含 fault.cpu.* 事件的 list.suspect 执行操作。cpumem-retire 代理将尝试在服务中使 CPU 脱机或弃用物理内存页。如果该代理成功,则会在故障管理器的 ASRU 缓存中为成功弃用的页或 CPU 添加一项。以下示例所示的 fmadm(1M) 实用程序显示了被诊断为有故障的内存等级的一项。系统无法使其脱机、将其弃用或禁用的 ASRU 也会在 ASRU 缓存中存在一项,但会将其视为被降级。降级意味着与 ASRU 关联的资源有故障,但无法从服务中将该 ASRU 删除。目前,Oracle Solaris 代理软件不能对 I/O ASRU(设备实例)执行操作。缓存中所有有故障的 I/O 资源项都处于降级状态。

# fmadm faulty
   STATE RESOURCE / UUID
-------- ----------------------------------------------------------------------
degraded mem:///motherboard=0/chip=1/memory-controller=0/dimm=3/rank=0
         ccae89df-2217-4f5c-add4-d920f78b4faf
-------- ----------------------------------------------------------------------

弃用代理的主要用途是隔离(从服务中安全删除)被诊断为有故障的硬件或软件部分。

代理还可以执行其他重要操作,例如以下操作:

消息 ID 和字典文件

系统日志消息代理会提取诊断的输出(list.suspect 事件),并将特定消息写入控制台或 /var/adm/messages。控制台消息通常可能会难以理解。FMA 提供一个每当将 list.suspect 事件发送至系统日志消息时都会生成的已定义故障消息结构,从而对此问题进行了修正。

系统日志代理会生成一个消息标识符 (message identifier, MSG ID)。事件注册表生成字典文件(.dict 文件),这些文件可将 list.suspect 事件映射到将用来标识和查看关联知识文章的结构化消息标识符。消息文件(.po 文件)则将消息 ID 映射到诊断引擎可以生成的每个可能的可疑故障列表中的本地化消息。下面是一个测试系统中发出的故障消息示例。

SUNW-MSG-ID: AMD-8000-7U, TYPE: Fault, VER: 1, SEVERITY: Major
EVENT-TIME: Fri Jul 28 04:26:51 PDT 2006
PLATFORM: Sun Fire V40z, CSN: XG051535088, HOSTNAME: parity
SOURCE: eft, REV: 1.16
EVENT-ID: add96f65-5473-69e6-dbe1-8b3d00d5c47b
DESC: The number of errors associated with this CPU has exceeded 
acceptable levels. Refer to http://sun.com/msg/AMD-8000-7U for 
more information.
AUTO-RESPONSE: An attempt will be made to remove this CPU from service.
IMPACT: Performance of this system may be affected.
REC-ACTION: Schedule a repair procedure to replace the affected CPU. 
Use fmdump -v -u <EVENT_ID> to identify the module.

系统拓扑

为了确定可能发生故障的位置,诊断引擎需要表示出给定软件或硬件系统的拓扑。fmd(1M) 守护进程为诊断引擎提供了一个可在诊断期间使用的拓扑快照句柄。拓扑信息用来表示在每个故障事件中找到的资源、ASRU 和 FRU。拓扑也可以用来存储平台标签、FRUID 和序列号标识。

故障事件中的资源有效负荷成员始终由平台机箱周围组件的物理路径位置来表示。例如,从主系统总线桥接至 PCI 本地总线的 PCI 控制器功能由其 hc 模式路径名来表示:

hc:///motherboard=0/hostbridge=1/pcibus=0/pcidev=13/pcifn=0

故障事件中的 ASRU 有效负荷成员通常由绑定到硬件控制器、设备或功能的 Oracle Solaris 设备树实例名称来表示。对于可能由专门为 I/O 设备设计的弃用代理的将来实现执行的操作,FMA 使用 dev 模式以其本地格式表示 ASRU:

dev:////pci@1e,600000/ide@d

故障事件中的 FRU 有效负荷表示形式随距离被诊断为有故障的 I/O 资源最近的可更换组件而异。例如,中断的嵌入式 PCI 控制器的故障事件可能会将系统的主板命名为需要更换的 FRU:

hc:///motherboard=0

标签有效负荷是一个字符串,用于按照 FRU 在机箱或主板上的印刷形式给出其位置,例如,在 DIMM 插槽或 PCI 卡插槽旁边。

Label: SLOT 2

错误处理

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

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

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_init (9F) 返回之前,I/O 故障服务框架会创建一组故障管理功能属性:fm-ereport-capablefm-accchk-capablefm-dmachk-capablefm-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 指定。

报告标准 I/O 控制器错误中介绍的用于 I/O 控制器的 ereport 类名和有效负荷可适用于 error_class。可以定义其他 ereport 类名和有效负荷,但必须在 Sun 事件注册表中进行注册,并伴有特定于驱动程序的诊断引擎软件或 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) 函数的包装。请参见对错误事件排队中的 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:

在以下 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() 函数。


访问属性结构

DDI_FM_ACCCHK_CAPABLE 设备驱动程序必须设置其访问属性,以指示它能够处理寄存器读取或写入期间发生的程控 I/O (programmed I/O, PIO) 访问错误。应将 ddi_device_acc_attr(9S) 结构中的 devacc_attr_access 字段设置为系统的指示器,驱动程序可以对其进行检查并处理数据路径错误。ddi_device_acc_attr 结构包含以下成员:

ushort_t devacc_attr_version;
uchar_t devacc_attr_endian_flags;
uchar_t devacc_attr_dataorder;
uchar_t devacc_attr_access;             /* access error protection */

在到设备或来自设备的数据路径中检测到的错误可由设备驱动程序的一个或多个父结点来处理。

devacc_attr_version 字段必须至少设置为 DDI_DEVICE_ATTR_V1。如果 devacc_attr_version 字段未设置为 DDI_DEVICE_ATTR_V1,则将忽略 devacc_attr_access 字段。

可将 devacc_attr_access 字段设置为以下值:

DDI_DEFAULT_ACC

此标志指示当出现错误时系统将采取缺省操作(如果合适,则进入紧急状态)。DDI_FM_ACCCHK_CAPABLE 驱动程序不能使用此属性。

DDI_FLAGERR_ACC

此标志指示系统将尝试处理与访问句柄关联的错误并从该错误中恢复。驱动程序应使用用于 Oracle Solaris 设备驱动程序的防御性编程技术中介绍的技术,同时应使用 ddi_fm_acc_err_get(9F) 定期检查错误,之后驱动程序才能允许将数据回传给调用应用程序。

DDI_FLAGERR_ACC 标志可提供:

  • 通过驱动程序回调收到的错误通知

  • 可通过 ddi_fm_acc_err_get (9F) 检测到的一个错误状态

DDI_CAUTIOUS_ACC

DDI_CAUTIOUS_ACC 标志可为驱动程序进行的每个程控 I/O 访问提供高级别的保护。


注 - 使用此标志将对驱动程序的性能造成重大影响。


DDI_CAUTIOUS_ACC 标志指示访问驱动程序可以预见错误。系统尝试尽可能正常地处理与此句柄关联的错误并从该错误中恢复。最终不会生成错误报告,但句柄的 fme_status 标志将设置为 DDI_FM_NONFATAL。此标志具有与 ddi_peek(9F)ddi_poke(9F) 等效的功能。

使用 DDI_CAUTIOUS_ACC 可提供:

  • 对总线的独占访问

  • 陷阱 (On trap) 保护-(ddi_peek()ddi_poke()

  • 通过使用 ddi_fm_handler_register(9F) 注册的驱动程序回调获得的错误通知

  • 可通过 ddi_fm_acc_err_get (9F) 检测到的一个错误状态

通常,驱动程序应在代码路径中的适当接合点处检查数据路径错误,以确保数据一致并确保 I/O 软件堆栈中显示正确的错误状态。

DDI_FM_ACCCHK_CAPABLE 设备驱动程序必须将其 devacc_attr_access 字段设置为 DDI_FLAGERR_ACC 或 DDI_CAUTIOUS_ACC。

DMA 属性结构

与访问句柄设置一样,DDI_FM_DMACHK_CAPABLE 设备驱动程序必须将其 ddi_dma_attr(9S) 结构的 dma_attr_flag 字段设置为 DDI_DMA_FLAGERR 标志。系统将尝试从与设置了 DDI_DMA_FLAGERR 的句柄关联的错误中恢复。ddi_dma_attr 结构包含以下成员:

uint_t          dma_attr_version;       /* version number */
uint64_t        dma_attr_addr_lo;       /* low DMA address range */
uint64_t        dma_attr_addr_hi;       /* high DMA address range */
uint64_t        dma_attr_count_max;     /* DMA counter register */
uint64_t        dma_attr_align;         /* DMA address alignment */
uint_t          dma_attr_burstsizes;    /* DMA burstsizes */
uint32_t        dma_attr_minxfer;       /* min effective DMA size */
uint64_t        dma_attr_maxxfer;       /* max DMA xfer size */
uint64_t        dma_attr_seg;           /* segment boundary */
int             dma_attr_sgllen;        /* s/g length */
uint32_t        dma_attr_granular;      /* granularity of device */
uint_t          dma_attr_flags;         /* Bus specific DMA flags */

设置 DDI_DMA_FLAGERR 标志的驱动程序应使用用于 Oracle Solaris 设备驱动程序的防御性编程技术中介绍的技术,并且应该在 DMA 事务完成时或者在代码路径的重要点处使用 ddi_fm_dma_err_get(9F) 来检查数据路径错误。这样可以确保数据一致并且 I/O 软件堆栈中显示正确的错误状态。

使用 DDI_DMA_FLAGERR 可提供:

获取错误状态

如果发生的故障影响到通过句柄映射的资源,则会更新错误状态结构,以反映在总线或 I/O 数据路径中的其他设备驱动程序在处理错误期间捕获的错误信息。

void ddi_fm_dma_err_get(ddi_dma_handle_t handle, ddi_fm_error_t *de, int version)

void ddi_fm_acc_err_get(ddi_acc_handle_t handle, ddi_fm_error_t *de, int version)

ddi_fm_dma_err_get(9F) 和 ddi_fm_acc_err_get(9F) 函数分别返回 DMA 和访问句柄的错误状态。应将版本字段设置为 DDI_FME_VERSION。

访问句柄错误意味着已检测到一种错误,该错误影响到达使用该访问句柄的设备或来自该设备的 PIO 事务。该驱动程序接收到的任何数据(例如,通过最新的 ddi_get8(9F) 调用)均应被视为可能已损坏。发送到设备的任何数据(例如,通过最新的 ddi_put32(9F) 调用)也都可能已损坏,或根本未被接收。不过,底层故障可能是瞬态的,因此驱动程序可以通过调用 ddi_fm_acc_err_clear(9F)、将设备重置为已知状态、重试任何可能已失败的事务来尝试进行恢复。

如果指示 DMA 句柄出现错误,则意味着检测到错误已经(或将要)影响设备和当前绑定到句柄(如果句柄当前未绑定,则为最近绑定)的内存之间的 DMA 事务。可能的原因包括 DMA 数据路径中的组件出现故障,或设备尝试进行无效的 DMA 访问。驱动程序通过重试和重新分配内存可能能够继续。应将当前(或以前)绑定到句柄的内存的内容视为不确定的,并应将其释放回系统。一旦绑定或重新绑定句柄,与当前事务关联的故障指示便会丢失,但由于故障可能持续存在,因此将来的 DMA 操作可能不会成功。

清除错误

在句柄检测到错误后,驱动程序希望在无需释放和重新分配句柄的前提下重试请求时,应调用 ddi_fm_acc_err_clear()ddi_fm_dma_err_clear(9F) 例程。

void ddi_fm_acc_err_clear(ddi_acc_handle_t handle, int version)

void ddi_fm_dma_err_clear(ddi_dma_handle_t handle, int version)

注册错误处理程序

当操作系统通过陷阱或错误中断检测到错误时,错误处理活动可能会开始。如果负责处理错误的软件(错误处理程序)无法立即隔离出现故障的 I/O 操作中涉及的设备,它必须尝试在设备树内查找可以执行错误隔离的软件模块。Oracle Solaris 设备树提供了向子级传播结点驱动程序错误处理活动的结构化方法,这些子级可能对错误有更详细的了解,并可捕获错误状态和隔离问题设备。

驱动程序可以使用 I/O 故障服务框架注册错误处理程序回调。错误处理程序应特定于错误的类型以及进行错误检测的子系统。调用驱动程序的错误处理程序例程时,驱动程序必须检查与设备事务关联的任何未解决的错误并生成 ereport 事件。驱动程序还必须在其 ddi_fm_error(9S) 结构中返回错误处理程序状态。例如,如果已经确定系统的完整性受到威胁,则错误处理程序可能采取的最合适的操作是使系统进入紧急状态。

当错误可能与特定的设备实例关联时,父结点驱动程序会调用回调。注册错误处理程序的设备驱动程序必须为 DDI_FM_ERRCB_CAPABLE。

void ddi_fm_handler_register(dev_info_t *dip, ddi_err_func_t handler, void *impl_data)

ddi_fm_handler_register(9F) 例程向 I/O 故障服务框架注册错误处理程序回调。应在驱动程序故障管理初始化 (ddi_fm_init()) 之后在驱动程序的 attach(9E) 入口点中调用 ddi_fm_handler_register() 函数,以便进行回调注册。

错误处理程序回调函数必须执行以下操作:

驱动程序错误处理程序会接收以下内容:

ddi_fm_handler_register()ddi_fm_handler_unregister(9F) 例程必须在驱动程序的 attach(9E) 或 detach(9E) 入口点中从内核上下文进行调用。可以从内核、中断或高级别中断上下文中调用注册的错误处理程序回调。因此,错误处理程序:

设备驱动程序负责:

可在错误处理程序函数内执行这些操作。不过,因为对锁定有限制,并且错误处理程序函数不是始终知道发生故障时驱动程序所执行操作的上下文,所以,更常见的是,这些操作在如上所述的驱动程序正常路径内在对 ddi_fm_acc_err_get(9F) 和 ddi_fm_dma_err_get(9F) 的内联调用之后执行。

/*
 * 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);
}

故障管理数据和状态结构

驱动程序错误处理回调会被传递一个指向数据结构的指针,该数据结构包含常见的故障管理数据和错误处理状态。

数据结构 ddi_fm_error 包含用于当前错误的 FMA 协议 ENA、错误处理程序回调的状态、错误预期标志以及与父结点检测到的错误关联的任何潜在访问或 DMA 句柄。

fme_ena

此字段通过调用父结点来进行初始化,并可能在达到驱动程序的已注册回调例程之前随着错误处理传播链不断增大。如果驱动程序检测到自身的相关错误,它应在调用 ddi_fm_ereport_post() 之前使此 ENA 增大。

fme_acc_handlefme_dma_handle

如果父级能够将在其级别上检测到的错误与设备驱动程序映射或绑定的句柄相关联,则这些字段中包含有效的访问或 DMA 句柄。

fme_flag

如果调用父级确定错误是由于 DDI_CAUTIOUS_ACC 受保护的操作引起的,fme_flag 将设置为 DDI_FM_ERR_EXPECTED。在这种情况下,fme_acc_handle 有效,并且驱动程序应只检查并报告不与 DDI_CAUTIOUS_ACC 受保护操作关联的错误。否则,fme_flag 将设置为 DDI_FM_ERR_UNEXPECTED,并且驱动程序必须执行完整的错误处理任务。

fme_status

从其错误处理程序回调返回后,驱动程序必须立即将 fme_status 设置为以下值之一:

  • DDI_FM_OK-未检测到任何错误,此设备实例的操作状态一直保持不变。

  • DDI_FM_FATAL-出现错误,并且驱动程序将其视为对系统致命的错误。例如,对 pci_ereport_post(9F) 的调用可能已检测到一个系统致命错误。在这种情况下,驱动程序应报告驱动程序的上下文中可能存在的任何其他错误信息。

  • DDI_FM_NONFATAL-驱动程序已检测到错误,但不将其视为对系统致命的错误。驱动程序已确定错误,并且已隔离该错误或者确认将隔离该错误。

  • DDI_FM_UNKNOWN-已检测到错误,但驱动程序无法隔离设备或确定错误对系统操作状态的影响。

诊断故障

故障管理守护进程 fmd(1M) 为诊断引擎 (diagnosis engine, DE) 插件模块的开发提供编程接口。可通过编写 DE 来使用和诊断任何错误遥测或特定错误遥测。eft DE 设计为根据以 Eversholt 语言指定的诊断规则来诊断任意数量的 ereport 类。

标准叶设备诊断

大多数 I/O 子系统都使用 eft DE 和规则集来诊断与设备和设备驱动程序相关的问题。已为 PCI 叶设备指定了一组报告标准 I/O 控制器错误中列出的标准 ereport。除了这些 ereport 外,同时还提供了提取遥测并确定关联设备故障的 eft 诊断规则。生成这些 ereport 的驱动程序不需要交付其他任何诊断软件或 eft 规则。

检测和生成这些 ereport 时将产生以下故障事件:

fault.io.pci.bus-linkerr

PCI 总线上的硬件故障

fault.io.pci.device-interr

设备内的硬件故障

fault.io.pci.device-invreq

设备中的硬件故障或驱动程序的缺陷,导致设备发送无效请求

fault.io.pci.device-noresp

设备中的硬件故障,导致驱动程序不对有效请求做出响应

fault.io.pciex.bus-linkerr

链路中的硬件故障

fault.io.pciex.bus-noresp

链路关闭,导致设备无法对有效请求做出响应

fault.io.pciex.device-interr

设备内的硬件故障

fault.io.pciex.device-invreq

设备中的硬件故障或驱动程序的缺陷,导致设备发送无效请求

fault.io.pciex.device-noresp

设备中的硬件故障,导致设备无法对有效请求做出响应

专门的设备诊断

要生成其他 ereport 或提供更专门的诊断软件或 eft 规则的驱动程序开发者可以通过编写基于 C 的 DE 或 eft 诊断规则集来实现此目标。

事件注册表

Sun 事件注册表是所有类名、ereport、故障、缺陷、混乱和可疑列表 (list.suspect) 事件的中央信息库。该事件注册表中还包含所有事件成员有效负荷的当前定义以及重要的非有效负荷信息,例如内部文档、可疑列表、字典和知识文章。例如,ereport.iofault.io 是对 I/O 驱动程序开发者特别重要的两个基本类名。

FMA 事件协议定义随每个注册事件提供的有效负荷成员的基本集合。开发者还可以定义其他事件,以帮助诊断引擎(或 eft 规则)将可疑列表范围缩小至特定故障。

词汇表

本节使用以下术语:

Agent(代理)

用于描述订阅 fault.* 或 list.* 事件的故障管理器模块的通用术语。代理用于弃用有故障的资源、将诊断结果告知管理员并桥接至更高级别的管理框架。

ASRU(Automated System Reconfiguration Unit,自动系统重新配置单元)

ASRU 是可由软件或硬件禁用以便隔离系统中的问题并抑制生成更多错误报告的资源。

DE(Diagnosis Engine,诊断引擎)

一个故障管理模块,其用途是通过订阅传入错误事件的一个或多个类并使用这些事件来解决与系统中每个问题关联的案例来诊断问题。

ENA(Error Numeric Association,错误编号关联)

错误编号关联 (Error Numeric Association, ENA) 是一个编码的整数,用于唯一标识给定故障区域和时间段内的错误报告。ENA 还指示错误与以前的错误之间的关系,以作为辅助影响。

Error(错误)

意外的情况、结果、信号或数据。错误是问题在系统中的症状。每个问题通常都会产生许多不同种类的错误。

ereport(Error Report,错误报告)

随特定错误捕获的数据。错误报告格式通过创建命名错误报告的类和通过定义使用 Sun 事件注册表的模式提前定义。

ereport event(Error Event,错误事件)

表示错误报告实例的数据结构。错误事件表示为名称-值对列表。

Fault(故障)

硬件组件的故障行为。

Fault Boundary(故障边界)

可为其枚举一组特定故障的硬件或软件元素的逻辑分区。

Fault Event(故障事件)

在协议中编码的故障诊断的实例。

Fault Manager(故障管理器)

负责通过一个或多个诊断引擎进行故障诊断以及状态管理的软件组件。

FMRI(Fault Managed Resource Identifier,故障管理资源标识符)

FMRI 是类似于 URL 的标识符,它在故障管理系统中充当特定资源的规范名称。每个 FMRI 中都包括一个标识资源类型的模式,以及特定于该模式的一个或多个值。FMRI 可以表示为类似于 URL 的字符串或名称-值对列表数据结构。

FRU(Field Replaceable Unit,现场可更换单元)

FRU 是可在现场由客户或服务提供商更换的资源。可为硬件(例如,系统板)或软件(例如,软件包或修补程序)定义 FRU。