后备机制¶
支持后备机制是为了克服直接在根文件系统上查找文件失败的情况,或者由于实际原因固件无法安装在根文件系统上的情况。 与支持固件后备机制相关的内核配置选项是
CONFIG_FW_LOADER_USER_HELPER:启用构建固件后备机制。 今天大多数发行版都启用了此选项。 如果启用但禁用了 CONFIG_FW_LOADER_USER_HELPER_FALLBACK,则只有自定义后备机制可用,并且仅用于
request_firmware_nowait()
调用。CONFIG_FW_LOADER_USER_HELPER_FALLBACK:强制启用每个请求,以在除
request_firmware_direct()
之外的所有固件 API 调用上启用 kobject uevent 后备机制。 今天大多数发行版都禁用了此选项。 调用request_firmware_nowait()
允许一种替代后备机制:如果启用了此 kconfig 选项,并且您对request_firmware_nowait()
的第二个参数 uevent 设置为 false,则您是在通知内核您有自定义后备机制,它将手动加载固件。 阅读下文了解更多详细信息。
请注意,这意味着在具有以下配置时
CONFIG_FW_LOADER_USER_HELPER=y CONFIG_FW_LOADER_USER_HELPER_FALLBACK=n
kobject uevent 后备机制将永远不会生效,即使对于 request_firmware_nowait()
在 uevent 设置为 true 时也是如此。
证明固件后备机制的合理性¶
直接文件系统查找可能会因多种原因而失败。 值得对这些已知原因进行逐条列举和记录,因为它证明了后备机制的必要性
启动时与访问根文件系统发生竞争。
从挂起恢复时发生竞争。 这可以通过固件缓存来解决,但仅当您使用 uevents 时才支持固件缓存,并且不支持
request_firmware_into_buf()
。无法通过典型方式访问固件
无法将其安装到根文件系统中
固件提供非常独特的设备特定数据,这些数据是根据本地信息收集的,专为该单元定制。 一个示例是移动设备 WiFi 芯片组的校准数据。 此校准数据并非所有单元通用,而是针对每个单元定制的。 此类信息可以安装在与根文件系统提供的闪存分区不同的闪存分区上。
后备机制的类型¶
实际上,有两种可用的后备机制,它们使用一个共享的 sysfs 接口作为加载工具
Kobject uevent 后备机制
自定义后备机制
首先,让我们记录共享的 sysfs 加载工具。
固件 sysfs 加载工具¶
为了帮助设备驱动程序使用后备机制上传固件,固件基础设施创建了一个 sysfs 接口,以允许用户空间加载固件并指示固件何时准备就绪。 sysfs 目录通过 fw_create_instance() 创建。 此调用创建一个以请求的固件命名的新 struct device
,并通过将用于发出请求的设备作为设备的父设备来将其建立在设备层次结构中。 sysfs 目录的文件属性通过新设备的类 (firmware_class) 和组 (fw_dev_attr_groups) 定义和控制。 这实际上是原始 firmware_class 模块名称的由来,因为最初唯一可用的固件加载机制是我们现在用作后备机制的机制,该机制注册一个 struct class
firmware_class。 因为公开的属性是模块名称的一部分,所以模块名称 firmware_class 将来无法重命名,以确保与旧用户空间的向后兼容性。
为了使用 sysfs 接口加载固件,我们公开了一个加载指示器,并将文件上传到固件中
/sys/$DEVPATH/loading
/sys/$DEVPATH/data
要上传固件,您需要将 1 回显到 loading 文件中,以指示您正在加载固件。 然后,您将固件写入 data 文件中,并通过将 0 回显到 loading 文件中来通知内核固件已准备就绪。
仅当直接固件加载失败且为您的固件请求启用了后备机制时,才会创建用于帮助使用 sysfs 加载固件的固件设备,这由 firmware_fallback_sysfs()
设置。 重要的是要重申,如果直接文件系统查找成功,则不会创建任何设备。
使用
echo 1 > /sys/$DEVPATH/loading
将立即清除任何先前的部分加载,并使固件 API 返回错误。 加载固件时,firmware_class 会以 PAGE_SIZE 的增量增长一个用于固件的缓冲区,以保存传入的映像。
firmware_data_read() 和 firmware_loading_show() 仅用于 test_firmware 驱动程序进行测试,它们在正常使用中不会被调用,也不期望被用户空间定期使用。
firmware_fallback_sysfs¶
-
int
firmware_fallback_sysfs
(struct
firmware *fw, constchar
*name,struct
device *device, u32 opt_flags, int ret)¶ 使用后备机制查找固件
参数
struct
firmware *fw指向固件映像的指针
const
char
*name要查找的固件文件的名称
struct
device *device正在加载固件的设备
u32 opt_flags
控制固件加载行为的选项,如
enum fw_opt
所定义int ret
触发后备机制的直接查找的返回值
描述
如果直接查找固件失败,则会调用此函数,它通过公开 sysfs 加载接口来启用用户空间后备机制。 用户空间负责通过 sysfs 加载接口加载固件。 可以通过将 proc sysctl 值 ignore_sysfs_fallback 设置为 true,在系统上完全禁用此 sysfs 后备机制。 如果这是 false,我们会检查内部 API 调用者是否设置了 FW_OPT_NOFALLBACK_SYSFS 标志,如果是,则也会禁用后备机制。 系统可能希望始终强制执行 sysfs 后备机制,它可以通过将 ignore_sysfs_fallback 设置为 false,并将 force_sysfs_fallback 设置为 true 来实现。 启用 force_sysfs_fallback 在功能上等同于构建具有 CONFIG_FW_LOADER_USER_HELPER_FALLBACK 的内核。
固件 kobject uevent 后备机制¶
由于为 sysfs 接口创建了一个设备来帮助加载固件作为后备机制,因此可以通过依赖 kobject uevents 来通知用户空间设备的添加。 将设备添加到设备层次结构意味着固件加载的后备机制已启动。 有关实现的详细信息,请参阅 fw_load_sysfs_fallback(),特别是关于 dev_set_uevent_suppress() 和 kobject_uevent()
的使用。
内核的 kobject uevent 机制在 lib/kobject_uevent.c 中实现,它向用户空间发出 uevents。 作为 kobject uevents 的补充,Linux 发行版还可以启用 CONFIG_UEVENT_HELPER_PATH,它利用核心内核的用户模式助手 (UMH) 功能来调用用户空间助手来处理 kobject uevents。 但实际上,没有标准发行版曾经使用过 CONFIG_UEVENT_HELPER_PATH。 如果启用了 CONFIG_UEVENT_HELPER_PATH,则每次在内核中为触发的每个 kobject uevent 调用 kobject_uevent_env()
时,都会调用此二进制文件。
用户空间中支持不同的实现来利用此后备机制。 当固件加载只能使用 sysfs 机制时,用户空间组件“hotplug”提供了监视 kobject 事件的功能。 从历史上看,这已被 systemd 的 udev 取代,但是从 systemd commit be2ea723b1d0(“udev:删除用户空间固件加载支持”)开始,固件加载支持已从 udev 中删除,截至 2014 年 8 月的 v217。 这意味着今天大多数 Linux 发行版都没有使用或利用 kobject uevents 提供的固件后备机制。 事实上,由于今天大多数发行版都禁用了 CONFIG_FW_LOADER_USER_HELPER_FALLBACK,因此情况尤其恶化。
有关 kobject 事件变量设置的详细信息,请参阅 do_firmware_uevent()。 当前使用“kobject add”事件传递给用户空间的变量是
FIRMWARE=固件名称
TIMEOUT=超时值
ASYNC=API 请求是否为异步
默认情况下,DEVPATH 由内部内核 kobject 基础架构设置。 以下是一个简单的 kobject uevent 脚本示例
# Both $DEVPATH and $FIRMWARE are already provided in the environment.
MY_FW_DIR=/lib/firmware/
echo 1 > /sys/$DEVPATH/loading
cat $MY_FW_DIR/$FIRMWARE > /sys/$DEVPATH/data
echo 0 > /sys/$DEVPATH/loading
固件自定义后备机制¶
request_firmware_nowait()
调用的用户还有另一个选择:依赖 sysfs 后备机制,但请求不要向用户空间发出 kobject uevents。 这背后的最初逻辑是,除了 udev 之外的实用程序可能需要在非传统路径中查找固件——在“直接文件系统查找”部分记录的列表之外的路径。 此选项不适用于任何其他 API 调用,因为始终为它们强制执行 uevents。
由于仅当在您的内核中启用了后备机制时,uevents 才是有意义的,因此使用未在其内核中启用后备机制的内核启用 uevents 似乎很奇怪。 不幸的是,我们还依赖于可以通过 request_firmware_nowait()
禁用的 uevent 标志,以设置固件请求的固件缓存。 如上文所述,只有为 API 调用启用了 uevent 时才会设置固件缓存。 尽管这可以禁用 request_firmware_nowait()
调用的固件缓存,但是此 API 的用户不应将其用于禁用缓存的目的,因为这并不是该标志的最初目的。 不设置 uevent 标志意味着您想要选择加入固件后备机制,但您想要禁止 kobject uevents,因为您有一个自定义解决方案,该解决方案将以某种方式监视您的设备添加到设备层次结构中,并通过自定义路径为您加载固件。
固件后备超时¶
固件后备机制具有超时。 如果在超时值之前未将固件加载到 sysfs 接口上,则会将错误发送到驱动程序。 默认情况下,如果需要 uevents,则超时设置为 60 秒,否则使用 MAX_JIFFY_OFFSET(最大可能的超时)。 将 MAX_JIFFY_OFFSET 用于非 uevents 背后的逻辑是,自定义解决方案将有尽可能多的时间来加载固件。
您可以通过将所需的超时回显到以下文件中来自定义固件超时
/sys/class/firmware/timeout
如果您回显 0 到其中,则将使用 MAX_JIFFY_OFFSET。 超时的数据类型为 int。
EFI 嵌入式固件后备机制¶
在某些设备上,系统的 EFI 代码/ROM 可能包含一些系统集成外围设备的嵌入式固件副本,并且外围设备的 Linux 设备驱动程序需要访问此固件。
需要此类固件的设备驱动程序可以使用 firmware_request_platform()
函数来实现此目的,请注意,这是与其他后备机制不同的单独后备机制,并且不使用 sysfs 接口。
需要此固件的设备驱动程序可以使用 efi_embedded_fw_desc 结构来描述它需要的固件
-
struct
efi_embedded_fw_desc
¶ EFI 嵌入式 fw 代码使用此结构搜索嵌入式固件。
定义:
struct efi_embedded_fw_desc {
const char *name;
u8 prefix[EFI_EMBEDDED_FW_PREFIX_LEN];
u32 length;
u8 sha256[32];
};
成员
name
找到时用于注册固件的名称
prefix
固件的前 8 个字节
length
固件的长度(以字节为单位),包括前缀
sha256
固件的 SHA256
EFI 嵌入式 fw 代码的工作方式是扫描所有 EFI_BOOT_SERVICES_CODE 内存段中与前缀匹配的八字节序列; 如果找到前缀,则会对 length 字节执行 sha256,如果匹配,则创建 length 字节的副本,并将其添加到其找到的固件列表中。
为了避免在所有系统上执行此有些昂贵的扫描,使用了 dmi 匹配。 驱动程序应导出一个 dmi_system_id 数组,每个条目的 driver_data 指向 efi_embedded_fw_desc。
要将此数组注册到 efi-embedded-fw 代码,驱动程序需要
始终内置到内核中,或者将 dmi_system_id 数组存储在始终内置的单独目标文件中。
将 dmi_system_id 数组的 extern 声明添加到 include/linux/efi_embedded_fw.h。
将 dmi_system_id 数组添加到 drivers/firmware/efi/embedded-firmware.c 中的 embedded_fw_table 中,并用 #ifdef 包裹,以测试驱动程序是否正在内置。
将 “select EFI_EMBEDDED_FIRMWARE if EFI_STUB” 添加到其 Kconfig 条目中。
firmware_request_platform()
函数将始终首先尝试直接从磁盘加载具有指定名称的固件,因此始终可以通过将文件放置在 /lib/firmware 下来覆盖 EFI 嵌入式 fw。
请注意
扫描 EFI 嵌入式固件的代码在 start_kernel() 的末尾附近运行,就在调用 rest_init() 之前。 对于使用 subsys_initcall() 注册自身的普通驱动程序和子系统,这无关紧要。 这意味着较早运行的代码无法使用 EFI 嵌入式固件。
目前,EFI 嵌入式 fw 代码假设固件始终以 8 字节的倍数的偏移量开始,如果您的案例并非如此,请提交补丁来修复此问题。
目前,EFI 嵌入式 fw 代码仅适用于 x86,因为其他 arch 在 EFI 嵌入式 fw 代码有机会扫描它之前释放了 EFI_BOOT_SERVICES_CODE。
当前对 EFI_BOOT_SERVICES_CODE 的蛮力扫描是一种临时的蛮力解决方案。 有人讨论过使用 UEFI 平台初始化 (PI) 规范的固件卷协议。 这已被拒绝,因为 FV 协议依赖于 PI 规范的 内部 接口,并且:1. PI 规范根本没有定义外围设备固件 2. PI 规范的内部接口不保证任何向后兼容性。 FV 中的任何实现细节都可能会更改,并且可能会因系统而异。 支持 FV 协议将很困难,因为它是有意模糊的。
如何检查和提取嵌入式固件的示例¶
要检查,例如 Silead 触摸屏控制器嵌入式固件,请执行以下操作
使用内核命令行上的 efi=debug 启动系统
将 /sys/kernel/debug/efi/boot_services_code? 复制到您的主目录
在十六进制编辑器中打开 boot_services_code? 文件,搜索 Silead 固件的 magic 前缀:F0 00 00 00 02 00 00 00,这为您提供了 boot_services_code? 文件中固件的起始地址。
固件具有特定模式,它以 8 字节的页地址开头,通常是第一页的 F0 00 00 00 02 00 00 00,后跟 32 位字地址 + 32 位值对。 字地址对中的每个字地址递增 4 个字节(1 个字),直到一页完成。 完整页后跟新的页地址,后跟更多字 + 值对。 这导致了一个非常独特的模式。 向下滚动直到此模式停止,这将为您提供 boot_services_code? 文件中固件的结尾。
“dd if=boot_services_code? of=firmware bs=1 skip=<begin-addr> count=<len>” 将为您提取固件。 在十六进制编辑器中检查固件文件,以确保您获得了正确的 dd 参数。
将其复制到预期名称下的 /lib/firmware 以进行测试。
如果提取的固件有效,您可以使用找到的信息来填充 efi_embedded_fw_desc 结构来描述它,运行 “sha256sum firmware” 以获取要放入 sha256 字段的 sha256sum。