LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

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增加固件路径。

posted on 2023-12-02 23:59  ArnoldLu  阅读(979)  评论(0编辑  收藏  举报

导航