Linux下固件加载器Firmware Loader
某些Linux外设需要固件才能正常工作,或者调试过程中需要更换固件。为解决设备驱动程序从内核态或者用户态加载固件到外设中,Linux提供了Firmware Loader子系统。
如果固件比较稳定,可以通过builtin方式加载。如果需要经常变动,可放入文件系统指定目录中。如果都无法找到需要根据uevent做异常处理。
1 defconfig配置Firmware
Linux下配置Firmware Loader:
Device Drivers
->Generic Driver Options
->Firmware loader
->Frimware loading facility
->Build named firmware blobs into the kernel binary--指定需要内嵌到kernel中的Firmware文件名。
->Firmware blobs root directory--指定需要内嵌到Kernel中的Firmware目录。
然后drivers/base/firmware_loader/builtin/Makefile会根据上述两个选项为每个Firmware文件生成xxxx.gen.S并编译。
# SPDX-License-Identifier: GPL-2.0 # Create $(fwdir) from $(CONFIG_EXTRA_FIRMWARE_DIR) -- if it doesn't have a # leading /, it's relative to $(srctree). fwdir := $(subst $(quote),,$(CONFIG_EXTRA_FIRMWARE_DIR)) fwdir := $(addprefix $(srctree)/,$(filter-out /%,$(fwdir)))$(filter /%,$(fwdir)) obj-y := $(addsuffix .gen.o, $(subst $(quote),,$(CONFIG_EXTRA_FIRMWARE)))--将CONFIG_EXTRA_FIRMWARE文件编译生成xxxx.gen.o文件。 FWNAME = $(patsubst $(obj)/%.gen.S,%,$@) comma := , FWSTR = $(subst $(comma),_,$(subst /,_,$(subst .,_,$(subst -,_,$(FWNAME))))) ASM_WORD = $(if $(CONFIG_64BIT),.quad,.long) ASM_ALIGN = $(if $(CONFIG_64BIT),3,2) PROGBITS = $(if $(CONFIG_ARM),%,@)progbits filechk_fwbin = \ echo "/* Generated by $(src)/Makefile */" ;\ echo " .section .rodata" ;\--rodata段。 echo " .p2align 4" ;\ echo "_fw_$(FWSTR)_bin:" ;\ echo " .incbin \"$(fwdir)/$(FWNAME)\"" ;\--将Firmware文件内嵌到xxx.gen.s中。 echo "_fw_end:" ;\ echo " .section .rodata.str,\"aMS\",$(PROGBITS),1" ;\--rodata.str段。 echo " .p2align $(ASM_ALIGN)" ;\ echo "_fw_$(FWSTR)_name:" ;\ echo " .string \"$(FWNAME)\"" ;\--Firmware名称。 echo " .section .builtin_fw,\"a\",$(PROGBITS)" ;\--builtin_fw段。 echo " .p2align $(ASM_ALIGN)" ;\ echo " $(ASM_WORD) _fw_$(FWSTR)_name" ;\--这是一个struct builtin_fw结构体:名称、内存地址、大小。 echo " $(ASM_WORD) _fw_$(FWSTR)_bin" ;\ echo " $(ASM_WORD) _fw_end - _fw_$(FWSTR)_bin" $(obj)/%.gen.S: FORCE $(call filechk,fwbin)--生成xxx.gen.s文件。 # The .o files depend on the binaries directly; the .S files don't. $(addprefix $(obj)/, $(obj-y)): $(obj)/%.gen.o: $(fwdir)/% targets := $(patsubst $(obj)/%,%, \ $(shell find $(obj) -name \*.gen.S 2>/dev/null))
2 Linux Firmware Loader数据结构和API
2.1 数据结构
struct firmware用于记录申请到的固件。struct biultin_fw用于记录内嵌到Kernel的固件。
struct firmware {
size_t size;--固件大小。
const u8 *data;--固件起始地址。
struct page **pages;--保存固件内容的page页表。
/* firmware loader private fields */
void *priv;
};
struct builtin_fw {--内嵌到Kernel中的固件。
char *name;
void *data;
unsigned long size;
};
Firmware Loader的操作主要是申请固件的request_firmware_*和释放固件的release_firmware函数。
int request_firmware(const struct firmware **fw, const char *name,
struct device *device);
int firmware_request_nowarn(const struct firmware **fw, const char *name,
struct device *device);--类似于request_firmware(),但是当文件找不到时不输出警告信息。
int request_firmware_nowait(
struct module *module, bool uevent,
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context));--类似于request_firmware(),但是不等待文件读取完成。而是通过回调函数来完成后续操作。
int request_firmware_direct(const struct firmware **fw, const char *name,
struct device *device);
int request_firmware_into_buf(const struct firmware **firmware_p,
const char *name, struct device *device, void *buf, size_t size);
void release_firmware(const struct firmware *fw);
2.2 request_firmware解读
不同的request_firmware()函数变种差异主要在flag不同:
enum fw_opt { FW_OPT_UEVENT = BIT(0),--Firmware文件找不到时通过uevent通知用户空间负责加载Firmware。 FW_OPT_NOWAIT = BIT(1),--Firmware请求时异步的。 FW_OPT_USERHELPER = BIT(2),--类似于uevent的回调机制。 FW_OPT_NO_WARN = BIT(3),--不输出警告信息。 FW_OPT_NOCACHE = BIT(4),--Firmware缓存可以避免在suspend-resume后频繁读取存储设备。但是如果Firmware过大,也不适合保留缓存。 FW_OPT_NOFALLBACK = BIT(5),--不借助回调机制让用户加载Firmware。 };
结合上面的fw_opt,request_firmware变种是对_request_firmware的包装:
request_firmware--FW_OPT_UEVENT
firmware_request_nowarn--FW_OPT_UEVENT | FW_OPT_NO_WARN
request_firmware_direct--FW_OPT_UEVENT | FW_OPT_NO_WARN |FW_OPT_NOFALLBACK
request_firmware_into_buf--FW_OPT_UEVENT | FW_OPT_NOCACHE
->_request_firmware
->_request_firmware_prepare
->fw_get_builtin_firmware--遍历__start_builtin_fw到__end_builtin_fw之间的struct builtin_fw,并将Firmware内容拷贝到指定buf中。
->alloc_lookup_fw_priv--如果builtin中没找到,则尝试到fw_cache去寻找。没有的话则创建一个struct fw_priv。
->fw_get_filesystem_firmware--根据fw_priv->fw_name和suffix在fw_path目录列表中读取文件。
->kernel_read_file_from_path
->firmware_fallback_sysfs--在builtin和fs读取都失败后,告诉用户空间进行处理。
->fw_load_from_user_helper
->firmware_loading_timeout--设置超时时间。
->fw_create_instance--创建一个struct fw_sysfs,后续用户空间可以通过此sysfs加载Firmware。sysfs包含两个节点:struct bin_attribute类型的data,用于保存Firmware;struct attribute类型的loading,显示或设置loading状态。
->fw_load_sysfs_fallback--可以通过UEVENT通知用户空间去加载Firmware。超时或成功后sysfs消失。
->assign_fw
综合来看Firmware Loader提供给的加载固件的方式有:
- 在Kernel编译阶段固化到到builtin_fw段。
- 在fw_path目录列表中寻找固件并加载。
- 发送uevent事件到用户空间,用户空间解析uevent事件然后将固件加载到指定sysfs。
fw_path默认列表为:
static const char * const fw_path[] = { fw_path_para, "/lib/firmware/updates/" UTS_RELEASE, "/lib/firmware/updates", "/lib/firmware/" UTS_RELEASE, "/lib/firmware" };
更多参考《linux驱动- firmware子系统》《linux固件升级接口使用总结》。
3 其他配置
- 通过在cmdline中增加firmware_class.path=<path>来增加固件路径。
- 修改firmware_class模块参数/sys/module/firmware_class/parameters/path增加固件路径。
联系方式:arnoldlu@qq.com