
在 openUBMC 开发中FRU 数据管理是硬件管理模块中最核心的组件之一。本文基于我在 openUBMC 社区的开发经验对该组件的架构设计和实现细节进行深度解析。一、文章目标理解 FRU 数据管理在硬件管理系统中的定位与核心价值掌握 frudata 组件的架构设计与关键模块实现逻辑剖析多存储类型适配、并发控制、数据同步等技术细节结合代码实例学习工业级硬件管理组件的设计最佳实践二、背景和基础知识2.1 FRU 是什么定义现场可更换单元Field Replaceable Unit存储硬件设备信息序列号、厂商、版本等的标准化载体。核心作用提供硬件身份标识支持设备热插拔、远程管理、故障定位如 BMC 系统通过 FRU 数据识别硬件状态。2.2 frudata 组件在 openUBMC 中的定位所属领域硬件管理模块负责 FRU 数据的硬件读写、系统同步、存储适配。上下游交互上游对接 BMC 管理接口DBus提供 FRU 数据查询/更新服务。下游适配多种存储介质EEPROM、文件、非标硬件通过 IPMI 协议操作电子标签。三、整体架构和模块划分3.1 架构图frudata组件 ├─ 存储适配层硬件无关核心 │ ├─ EEPROM存储TianChi规范 │ │ ▶ 实现天池偏移量计算calculate_tianchi_offset、写保护寄存器操作 │ ├─ EEPROM存储EepromV2规范 │ │ ▶ 支持V2头标签校验validate_v2_header、分段读取512字节块操作 │ ├─ 文件存储模拟模式 │ │ ▶ 数据写入/var/fru/fru0.data校验路径合法性file_utils.check_path_safety │ └─ 非标硬件MCU/只读设备 │ ▶ 禁用写入操作write_fru_area直接报错仅同步DBus状态 ├─ 硬件接口层协议封装 │ ├─ IPMI模块 │ │ ▶ 映射IPMI命令到业务逻辑WriteElabelData→fru_ipmi.write_elabel_data │ └─ frudata_intfC扩展 │ ▶ 提供16个Lua接口如ipmi_read_elabel解析硬件返回的二进制数据为可读字符串 ├─ 数据管理层核心控制 │ ├─ manage模块 │ │ ▶ EEPROM头修复检测到FRU_HEADER_TAG异常时自动写入FRU标识 │ │ ▶ DBus同步通过update_fru_data_to_dbus实时更新BoardManufacturer等属性 │ ├─ 并发控制 │ │ ▶ 全局队列cs保证写入串行化避免EEPROM操作冲突典型耗时5ms/次 │ └─ fru_persistence │ ▶ 持久化策略将PropertyName和Value序列化为JSON存入BMC本地数据库kepler.db └─ 公共模块 ├─ fru_singleton │ ▶ 全局管理FRU实例标记MCU类型FRUis_mcu_fru[FruId] true └─ 日志模块 ▶ 分级日志notice记录正常操作error包含硬件地址如FruId2写入失败3.2 核心模块功能速览模块核心功能技术亮点frudata_object定义基类接口init/write_fru_area通过元表实现多态子类通过setmetatable(subclass, frudata_object)继承重写差异化方法如 frudata_file:write_fru_areamanage硬件数据读写、默认值初始化、DBus 同步主板初始化时自动填充DEFAULT_MANUopenUBMC避免空值导致的系统异常ipmiIPMI 命令与业务逻辑绑定解耦协议层与业务层支持 IPMIv1/v2 兼容命令响应时间 10mssn_replace序列号变化监控硬件替换检测通过skynet.sleep(0)等待硬件枚举完成避免快速插拔误判典型场景硬盘 50ms 内多次插拔四、技术细节与实现亮点4.1 多存储类型适配代码级解析4.1.1 基类与子类关系Lua 元表机制通过 frudata_object 抽象基类定义通用接口init/write_fru_area/update_to_dbus子类如 frudata_eepromv2/frudata_file实现差异化逻辑。local frudata_object {} frudata_object.__index frudata_object function frudata_object:new(fruinfo_manage, obj) return setmetatable({ fruinfo_manage fruinfo_manage, obj obj }, frudata_object) end -- 天池子类实现frudata_tianchi.lua function frudata_object:write_fru_area(fru_id, _) -- 天池数据进行重新读取区别于MCU local data frudata_intf.get_fru_area(fru_id) manage:write_fru_area_to_eep(fru_id, data, self.obj, self.fruinfo_manage) end -- 只读存储子类实现 function frudata_object:write_fru_area(fru_id, data) log:error(StorageType is non standard, not supported for writing) end4.1.2 存储格式解析差异存储类型头标签校验逻辑偏移量计算方式典型场景TianChi EEPROM校验魔数0x4652550FRU ASCII 码头中第 14-15 字节组合offset (high8) low* 8主板 FRU 初始化写入 BMC版本号到FRU_BOARD_FRUFILEID文件存储无模拟场景无需硬件校验固定偏移如/var/fru/fru0.data直接按行解析测试环境快速验证避免硬件依赖非标硬件跳过校验只读模式直接返回错误log:error(不支持写入)旧型号硬件兼容仅同步 DBus 显示状态4.2 并发控制与硬件保护关键代码4.2.1 串行化队列实现-- 全局写入队列避免EEPROM并发操作 local cs skynet.queue() function manage:write_eeprom(data) cs(function() -- 队列任务保证顺序执行 self.obj.EepromWp common.EEPROM_WRITE_PROTECT.CLOSE -- 关闭写保护硬件级安全 local ret self.obj.FruDev:Write(ctx, offset, data) self.obj.EepromWp common.EEPROM_WRITE_PROTECT.OPEN -- 恢复写保护 if ret ~ COMP_CODE_SUCCESS then log:error(写入失败FruId%d, self.obj.FruId) end end) end写保护机制写入前关闭 EEPROM 写保护obj.EepromWp common.EEPROM_WRITE_PROTECT.CLOSE完成后恢复避免硬件误操作。4.2.2 写保护机制原理硬件级保护通过 I2C 接口操作 EEPROM 的写保护寄存器如Accessor_EXUWP.Value0允许写入1禁止写入。异常处理若写入过程中 BMC 断电EEPROM 自动恢复写保护状态避免未完成数据残留。4.3 数据一致性与容错处理默认值策略对未读取到或非法数据设置合理默认值如厂商名称默认 common.DEFAULT_MANU制造日期默认 1996/01/01避免系统层数据缺失。-- manage模块设置默认厂商名称避免空值 function manage:init_defaults() if not self.obj.BoardManufacturer then self.obj.BoardManufacturer common.DEFAULT_MANU -- openUBMC end if not self.obj.MfgDate then self.obj.MfgDate 1996/01/01 -- 未读取到时间时使用IPMI规范默认值 end end校验逻辑格式校验通过common.validate_byte过滤非法字符如电子标签不允许0x00以外的控制字符。头校验读取 EEPROM 前 16 字节若魔数错误则触发自动修复obj.FruDev:Write(0, FRU)。长度校验写入前检查数据长度如ProductSerialNumber最大 64 字节超过则截断并日志警告。4.4 IPMI 协议与业务解耦IPMI模块设计// C扩展接口frudata_intf.c static int l_ipmi_write_elabel(lua_State *L) { gint32 ret; size_t len 0; DFT_RW_ELABEL_REQ_S write_req {0}; write_req.fru_id (guint8)luaL_checkinteger(L, 1); // 参数1fru_id write_req.area (guint8)luaL_checkinteger(L, 2); // 参数2fru_area write_req.field (guint8)luaL_checkinteger(L, 3); // 参数3fru_field write_req.offset (guint16)luaL_checkinteger(L, 4); // 参数4写入数据偏移位置 write_req.len (guint8)luaL_checkinteger(L, 5); // 参数5写入数据长度 gchar *data (gchar *)luaL_checklstring(L, 6, len); // 参数6写入数据 debug_log(DLOG_NOTICE, [frudata] fru(%d) ipmi write area(%u) filed(%u) data(%s), write_req.fru_id, write_req.area, write_req.field, data); ret ipmi_dft_write_elabel(write_req, data); if (ret ! COMP_CODE_SUCCESS) { debug_log(DLOG_ERROR, %s: ipmi_dft_write_elabel fail, ret %d, __FUNCTION__, ret); } lua_pushinteger(L, ret); return 1; }解耦价值硬件升级透明当从 IPMIv1 升级到 v2 时只需修改ipmi_dft_write_elabel实现上层 Lua 代码如frudata_service无需改动。协议无关性支持非 IPMI 硬件时只需新增usb_fru_write接口保持业务层write_fru_area接口不变。五、典型场景与代码实战5.1 场景一主板 FRU 初始化流程FRU 初始化是硬件上电后frudata 组件解析硬件存储数据、加载默认配置并同步到系统的核心流程根据存储类型标准 EEPROM / 非标硬件 / 文件存储执行差异化逻辑。5.1.1 标准存储类型EepromV2/TianChi初始化核心流程硬件识别→头解析→数据加载→校验修复→系统同步系统上电 → 硬件自发现获取FruId/StorageType → 读取EEPROM头前17字节→ 按规范解析偏移量/UID → 校验头标签/修复异常 → 读取Fru域/System域数据 → 初始化默认值 → 同步到DBus关键步骤与代码映射硬件识别与头读取local ctx context.get_context_or_default() local fru_id obj.FruId local eep_header obj.FruDev:Read(ctx, 0, 17) -- 通用头读取所有标准存储类型通过obj.FruDev调用 C 扩展接口支持 I2C/SPI 总线读取 EEPROM 头数据。分规范解析偏移量if obj.StorageType EepromV2 then self:fru_check_and_fix_eeprom_fru_head(obj, eep_header, ctx) -- IPMI标准头校验修复异常头标签 elseif obj.StorageType TianChi then local fru_offset, uid self:tc_fru_get_offset_and_uid(obj, eep_header, ctx) -- 天池规范偏移量/UID解析 endEepromV2校验前 3 字节是否为 FRU异常时写入标准头标签obj.FruDev:Write(0, FRU)。TianChi从 9-10 字节解析 16 位偏移量单位8 字节块转换为实际字节偏移offset (high8) low* 8。System 域特殊处理仅 FruId0if fru_id 0 then local sys_offset ((byte(eep_header, 17)8)byte(eep_header, 16)) * 8 -- EepromV2 System域偏移计算 local system_data obj.FruDev:Read(ctx, sys_offset, common.SYSTEM_DATA_LEN) -- 读取整机配置数据 end挂耳FruId0专属逻辑存储系统制造商、产品名等全局配置。Fru 域数据读取与默认值填充5.1.2 非标硬件MCU / 自定义存储初始化核心流程默认值写入→数据库同步→专有协议更新MCU 类型流程硬件识别StorageType MCU→ 底层接口初始化 → 写入 BMC 版本/默认厂商 → 从数据库加载历史配置 → 通过专有协议更新硬件可写入 EEPROM→ 完成初始化不可写非标硬件流程硬件识别StorageType 不可写非标硬件→ 底层接口初始化 → 写入 BMC 版本/默认厂商 → 从数据库加载历史配置 → 同步配置到系统不进行硬件写入→ 标记为只读模式关键步骤与代码映射1. 底层初始化与默认值写入frudata_intf.frudata_init(fruid, , , 1, 1, 0) -- C扩展初始化建立MCU通信通道 self:write_prop(obj, BoardFRUFileID, bmc_version) -- 写入BMC版本作为默认固件版本 obj.BoardManufacturer common.DEFAULT_MANU -- 默认厂商为openUBMCwrite_prop 可能通过 UART 发送指令将默认值写入 MCU 寄存器。2. 持久化数据同步local prop_name, values fru_persistence:get(fruid) -- 从数据库获取历史配置 self:update_non_standard_fru(fruid, prop_name, values, obj) -- 转换为MCU专有协议格式示例将资产标签ASSET-001转换为 MCU 可识别的字节序列如 0x01 0x02 ... 0x30。3. 差异化操作// MCU类型的硬件 function frudata_object:write_fru_area(fru_id, data) manage:write_fru_area_to_eep(fru_id, data, self.obj, self.fruinfo_manage) end // 非标只读硬件 function frudata_object:write_fru_area(fru_id, data) log:error(StorageType is non standrad, not supported for writing) end5.1.3 文件存储模拟场景初始化核心流程路径校验→文件读取→格式解析→数据同步local fru_path string.format(/var/fru/fru%d.data, fru_id) if not file_utils.check_path_safety(fru_path) then error(Invalid fru path) end local fru_file file_utils.read_file(fru_path) -- 读取文件内容如JSON格式 local data cjson.decode(fru_file) -- 解析为Lua表 obj.BoardSerialNumber data.serial_number -- 填充FRU对象属性5.1.4 设计说明1. 多态适配核心通过 StorageType 字段分支将不同存储类型的初始化逻辑隔离在子类frudata_eepromv2/frudata_tianchi/frudata_non_standard中上层 manage 模块仅调用统一接口init/read_fru_head符合 OOP 多态原则。2. 可靠性保障头修复机制EepromV2 规范中检测到非法头标签时自动写入标准 FRU确保硬件存储合规。串行化控制所有 EEPROM 写入操作通过 skynet.queue 排队避免总线冲突典型场景双进程同时写入同一 EEPROM。3. 数据一致性策略默认值填充对未读取到的字段如 MfgDate使用 IPMI 规范默认值1996/01/01避免系统层显示异常。持久化优先非标硬件无本地存储时依赖数据库 kepler.db 作为唯一数据源确保配置跨重启有效。4. 硬件无关性设计C 扩展模块 frudata_intf 封装底层硬件操作如 Read/WriteLua 层仅通过 obj.FruDev 调用屏蔽 I2C/SPI/UART 等总线差异。5.2 场景二硬件替换检测sn_replace 模块核心逻辑1. 数据库查询从 t_persistence_info 表获取历史序列号 PreviousSN。2. 变化检测if db_obj.PreviousSN ~ current_SN then self.component_object.ReplaceFlag 0 -- 清除旧状态防抖动 skynet.sleep(0) -- 等待硬件枚举完成约10ms self.component_object.ReplaceFlag 1 -- 标记替换事件 db_obj.PreviousSN current_SN -- 持久化新值 event_manager:trigger(FRU_REPLACE, obj.FruId) -- 触发上层告警 end3. 业务影响BMC 管理界面实时显示硬盘已更换并更新设备列表中的序列号。