第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
本节介绍如何为 I/O 设备驱动程序集成故障管理错误报告、错误处理和诊断。本节深入探讨了 I/O 故障服务框架以及如何在设备驱动程序内利用 I/O 故障服务 API。
本节讨论以下主题:
什么是预测性自我修复?提供 Oracle 故障管理体系结构的背景信息和概述。
Oracle Solaris Fault Manager介绍了更多背景信息,重点介绍 Oracle Solaris Fault Manager fmd(1M) 的较高层面的概述。
错误处理是面向驱动程序开发者的主要章节。本节重点介绍用于获得高可用性的最佳编码方法,以及如何在驱动程序代码中使用 I/O 故障服务来与 FMA 交互。
传统上,系统会将硬件和软件错误信息以系统日志消息的形式直接导出给管理员以及管理软件。错误检测、诊断、报告和处理通常会嵌入到每个驱动程序的代码中。
Solaris OS 预测性自我修复系统这样的系统是最早的也是最重要的自我诊断系统。自我诊断意味着系统提供根据观察到的症状自动诊断问题的技术,然后将诊断结果用于触发自动响应和恢复。硬件故障或软件缺陷可与一组可能观察到的、称为错误的症状相关联。系统检测到错误后所生成的数据称为错误报告或 ereport。
在具有自我修复功能的系统中,ereport 由系统捕获,并编码为由可扩展事件协议描述的一组名称-值对,从而形成 ereport 事件。收集 ereport 事件和其他数据是为了便于进行自我修复,还会将其分发给名为诊断引擎的软件组件,诊断引擎设计用来诊断与系统检测到的错误症状对应的底层问题。诊断引擎在后台运行,并以无提示方式使用错误遥测,直到它可以生成诊断或预测故障为止。
在处理足够的遥测从而得出结论之后,诊断引擎会生成名为故障事件的另一个事件。然后,会向对特定故障事件感兴趣的所有代理广播该故障事件。代理是可对特定故障事件启动恢复和做出响应的软件组件。被称为 Oracle Solaris Fault Manager 的软件组件 fmd(1M) 可在 ereport 生成器、诊断引擎和代理软件之间管理事件的多路复用。
Oracle Solaris Fault Manager fmd(1M) 负责将传入错误遥测事件分发给相应的诊断引擎。诊断引擎负责识别产生错误症状的基础硬件故障或软件缺陷。fmd(1M) 守护进程是故障管理器的 Oracle Solaris OS 实现。它在引导时启动,并且会装入系统中可用的所有诊断引擎和代理。Oracle Solaris Fault Manager 还为系统管理员和服务人员提供了用于观察故障管理活动的界面。
进行诊断后,会以 list.suspect 事件的形式输出诊断。list.suspect 事件是由一个或多个可能的故障或缺陷事件构成的事件。有时候,诊断无法将错误原因的范围缩小至单个故障或缺陷。例如,底层问题可能是连接控制器与主系统总线的线路中断。问题也可能在于总线上的某个组件或总线本身。在这种特定情况下,list.suspect 事件将包含多个故障事件:一个是连接到总线的每个控制器的事件,另一个是总线本身的事件。
除了介绍诊断的故障外,故障事件还包含四个可应用诊断的有效负荷成员。
资源是被诊断为有故障的组件。fmdump(1M) 命令将此有效负荷成员显示为 "Problem in"。
自动系统恢复单元 (Automated System Recovery Unit, ASRU) 是必须禁用以防止出现更多错误症状的硬件或软件组件。fmdump(1M) 命令将此有效负荷成员显示为 "Affects"。
现场可更换单元 (Field Replaceable Unit, FRU) 是必须更换或维修以便修复底层问题的组件。
标签有效负荷是一个字符串,用于按照 FRU 在机箱或主板上的印刷形式给出其位置,例如,在 DIMM 插槽或 PCI 卡插槽旁边。fmdump 命令将此有效负荷成员显示为 "Location"。
例如,在给定时间内针对特定内存位置收到特定数量的 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 -------- ----------------------------------------------------------------------
弃用代理的主要用途是隔离(从服务中安全删除)被诊断为有故障的硬件或软件部分。
代理还可以执行其他重要操作,例如以下操作:
通过 SNMP 陷阱发送警报。这样便可将诊断转换为插入现有软件机制中的 SNMP 警报。
发布系统日志消息。特定于消息的诊断(例如,系统日志消息代理)可以提取诊断的结果,并将其转换为管理员可用来执行特定操作的系统日志消息。
其他代理操作,如更新 FRUID。响应代理可以特定于平台。
系统日志消息代理会提取诊断的输出(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://support.oracle.com/msg/SMF-8000-05 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 驱动程序必须立即响应检测到的错误。相应的响应包括:
尝试恢复
重试 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)
本节提供有关以下主题的信息:
对错误事件排队讨论如何对错误事件排队。
检测和报告与 PCI 相关的错误介绍如何报告与 PCI 相关的错误。
报告标准 I/O 控制器错误介绍如何报告标准 I/O 控制器错误。
服务影响函数讨论如何报告错误是否对设备提供的服务产生了影响。
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 类名和有效负荷,但必须在 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_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 控制器的常见错误定义了一组标准的设备 ereport。只要检测到本节中所述的错误症状之一,便应生成这些 ereport。
本节中所述的 ereport 将分发给 eft 诊断引擎以进行诊断,eft 诊断引擎使用一组常用的标准规则来诊断这些 ereport。设备驱动程序检测的其他任何错误都必须在 Sun 事件注册表中定义为 ereport 事件,并必须伴有特定于设备的诊断软件或 eft 规则。
驱动程序已检测到设备处于无效状态。
当驱动程序检测到所传送或接收的数据看起来无效时,该驱动程序应发布错误。例如,在 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); }
设备已报告自我纠正的内部错误。例如,设备的内部缓冲区中的硬件已检测到可纠正的 ECC 错误。bge 驱动程序中未使用此错误标志。
设备已报告无法纠正的内部错误。例如,设备的内部缓冲区中的硬件已检测到不可纠正的 ECC 错误。
bge 驱动程序中未使用此错误标志。
驱动程序检测到数据传输已意外停顿。
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);
设备未对驱动程序命令进行响应。
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); }
设备引发了过多的连续性无效中断。
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_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() 函数。