某些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_UEVENTfirmware_request_nowarn--FW_OPT_UEVENT | FW_OPT_NO_WARNrequest_firmware_direct--FW_OPT_UEVENT | FW_OPT_NO_WARN |FW_OPT_NOFALLBACKrequest_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 ->kernel_read_file--如果buf已经分配则直接写入;如果没有分配,则通过vmalloc()分配。 ->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=
修改firmware_class模块参数/sys/module/firmware_class/parameters/path增加固件路径。