linux firmware 实现原理

1.前言

  随着手机外围器件的集成度和复杂度越来越高, 单纯的设置相关寄存器已经无法使得器件可以正常的工作. 在一般情况下,需要将一个特定的fw下载到器件中, 确保器件可以正常稳定的运行, 比如:camera ois,camera actuator, TP等等。 一般情况下, 有以下三种方案:

  • 直接将fw data转化为特定的数组,编码在驱动代码中。
  • 将fw data烧录到一个分区中,需要的时候从分区中load进来。
  • 将fw打包到某个镜像中,如vendor,system等等,需要的时候从用户空间中load到kernel空间中。

三种方案特点:

  • 方案1:直接将其硬编码在驱动代码中, 会造成kernel镜像size变大, 有可能造成镜像超限, 导致kernel启动失败; 并且调试也不方便, 每次修改fw都需要重新编译内核.

  • 方案2: 需要预留好空间, 某些时候可能无法满足; 并且一般重新烧录fw, 一般机器需要进入特定的模式, 不利于在线调试.

  • 方案3: 能够有效的避免前两种方案的不足, 在驱动中应用比较广泛, 也是本文叙述的主题.

2.The firmware API

  The firmware API enables kernel code to request files required for functionality from userspace, the uses vary:

  • Microcode for CPU errata
  • Device driver firmware, required to be loaded onto device microcontrollers
  • Device driver information data (calibration data, EEPROM overrides), some of which can be completely optional.

2.1.Types of firmware requests

  There are two types of calls:

  • Synchronous
  • Asynchronous

  Which one you use vary depending on your requirements, the rule of thumb however is you should strive to use the asynchronous APIs unless you also are already using asynchronous initialization mechanisms which will not stall or delay boot. Even if loading firmware does not take a lot of time processing firmware might, and this can still delay boot or initialization, as such mechanisms such as asynchronous probe can help supplement drivers.

Synchronous firmware requests

  Synchronous firmware requests will wait until the firmware is found or until an error is returned.

int request_firmware(const struct firmware ** firmware_p, const char * name, struct device * device)

Asynchronous firmware requests

  Asynchronous firmware requests allow driver code to not have to wait until the firmware or an error is returned. Function callbacks are provided so that when the firmware or an error is found the driver is informed through the callback. request_firmware_nowait() cannot be called in atomic contexts.

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)

2.2.Firmware search paths

  The following search paths are used to look for firmware on your root filesystem.

  • fw_path_para - module parameter - default is empty so this is ignored
  • /lib/firmware/updates/UTS_RELEASE/
  • /lib/firmware/updates/
  • /lib/firmware/UTS_RELEASE/
  • /lib/firmware/

  The module parameter ‘’path’’ can be passed to the firmware_class module to activate the first optional custom fw_path_para. The custom path can only be up to 256 characters long. The kernel parameter passed would be:

  • ‘firmware_class.path=$CUSTOMIZED_PATH’

  There is an alternative to customize the path at run time after bootup, you can use the file:

  • /sys/module/firmware_class/parameters/path

  You would echo into it your custom path and firmware requested will be searched for there first.

  drivers/base/firmware_loader/main.c:
  277 /* direct firmware loading support */
   278 static char fw_path_para[256];
   279 static const char * const fw_path[] = {
   280     fw_path_para,
   281     "/lib/firmware/updates/" UTS_RELEASE,
   282     "/lib/firmware/updates",
   283     "/lib/firmware/" UTS_RELEASE,
   284     "/lib/firmware"
   285 };
   286 
   287 /*
   288  * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH'
   289  * from kernel command line because firmware_class is generally built in
   290  * kernel instead of module.                                                                          
   291  */
   292 module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644);
   293 MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path
   294 

2.3.Built-in firmware

  Firmware can be built-in to the kernel, this means building the firmware into vmlinux directly, to enable avoiding having to look for firmware from the filesystem. Instead, firmware can be looked for inside the kernel directly. You can enable built-in firmware using the kernel configuration options(drivers/base/Kconfig):

  • CONFIG_EXTRA_FIRMWARE
  • CONFIG_EXTRA_FIRMWARE_DIR

2.3.1.Enable the build-in firmware

  Enable “CONFIG_FIRMWARE_IN_KERNEL”, “CONFIG_EXTRA_FIRMWARE_DIR” and “CONFIG_EXTRA_FIRMWARE” to build firmware into kernel image:

CONFIG_FIRMWARE_IN_KERNEL=y
CONFIG_EXTRA_FIRMWARE_DIR="firmware"  // this means $(source_dir)/firmware
CONFIG_EXTRA_FIRMWARE="fw_sst_0f28.bin"

If several firmwares are required, add them as:

CONFIG_EXTRA_FIRMWARE=“fw_sst_0f28.bin abc.bin efg.bin”

2.3.2.Advantages and disadvantages

  There are a few reasons why you might want to consider building your firmware into the kernel with CONFIG_EXTRA_FIRMWARE:

  • Speed
  • Firmware is needed for accessing the boot device, and the user doesn’t want to stuff the firmware into the boot initramfs.

  Even if you have these needs there are a few reasons why you may not be able to make use of built-in firmware:

  • Legalese - firmware is non-GPL compatible
  • Some firmware may be optional
  • Firmware upgrades are possible, therefore a new firmware would implicate a complete kernel rebuild.
  • Some firmware files may be really large in size. The remote-proc subsystem is an example subsystem which deals with these sorts of firmware
  • The firmware may need to be scraped out from some device specific location dynamically, an example is calibration data for for some WiFi chipsets. This calibration data can be unique per sold device.

2.4.编程步骤

  linux内核为方案3提供了完整的解决方案, 驱动开发起来也相当的方便, 具体的步骤如下:

  • 在编译的时候, 将fw打包到具体镜像中. 对于android系统,可以将fw放在/etc/firmware, /vendor/firmware, /firmware/image这几个目录. 当上层服务ueventd接收到kernel上报的请求fw的uevent事件, 会依次搜索这几个目录, 查找对应名称的fw, 并通过节点data传递给kernel.
  • 由于内核已经封装好了接口, 驱动代码比较简单, 具体如下:
if(request_firmware(&fw_entry, $FIRMWARE, device) == 0)
       copy_fw_to_device(fw_entry->data, fw_entry->size);
release_firmware(fw_entry);

  上面的步骤可知, 内核已经把接口封装的相当简洁, 使用简单的接口就可以完成load 用户空间的fw任务. 当然kernel还为我们提供了其他类型接口, 主要是提供了一些其他的特性, 满足特定条件下load 用户空间的fw. 例如, 如果在原子上下文load fw,则只能用request_firmware_nowait()接口, 该接口不会导致进程睡眠. 但是所有的这些接口, 其工作原理是一样, 因此下文将以request_firmware()为入口分析load用户空间fw的原理.

Note:
  request_firmware在内核使用,需要文件系统支持,就是说,启动的时候如果在驱动里面的probe函数调用 request_firmware ,那么系统将等待30s左右,因为文件系统还没有挂载,当然找不到固件了,所以最好在中断里面启动tasklet,然后request_firmware 。如果不想等待,就用request_firmware_nowait,说明如下:
  Asynchronous variant of request_firmware for user contexts: - sleep for as small periods as possible since it may increase kernel boot time of built-in device drivers requesting firmware in their ->probe methods, if gfp is GFP_KERNEL.

2.4.1.相关结构体

struct firmware:

include/linux/firmware.h
struct firmware {
        size_t size;
        const u8 *data;
        struct page **pages;

        /* firmware loader private fields */
        void *priv;
};

  该结构体主要用于向驱动导出load 到内核的fw信息。

struct builtin_fw:

include/linux/firmware.h
struct builtin_fw {
        char *name;
        void *data;
        unsigned long size;
};

  该结构主要用于描述编译到内核builtin_fw段的fw。

struct firmware_buf:

driver/base/firmware_class.c
struct firmware_buf {
        struct kref ref;
        struct list_head list;
        struct completion completion;
        struct firmware_cache *fwc;
        unsigned long status;
        void *data;
        size_t size;
        size_t allocated_size;
#ifdef CONFIG_FW_LOADER_USER_HELPER
        bool is_paged_buf;
        bool need_uevent;
        struct page **pages;
        int nr_pages;
        int page_array_size;
        struct list_head pending_list;
#endif
        const char *fw_id;
};

  该结构体主要用于存储fw data,以及一些控制状态等等。

2.4.2 实现原理分析

driver/base/firmware_class.c
int request_firmware(const struct firmware **firmware_p, const char *name,
		 struct device *device)
{
	int ret;

	/* Need to pin this module until return */
	__module_get(THIS_MODULE);
	ret = _request_firmware(firmware_p, name, device, NULL, 0,
				FW_OPT_UEVENT | FW_OPT_FALLBACK);
	module_put(THIS_MODULE);
	return ret;
}

static int _request_firmware(const struct firmware **firmware_p, const char *name,
                  struct device *device, void *buf, size_t size,
                  unsigned int opt_flags)
{
        ......
        ret = _request_firmware_prepare(&fw, name, device, buf, size,
                                        opt_flags);
        if (ret <= 0) /* error or already assigned */
                goto out;

        ......
         ret = fw_get_filesystem_firmware(device, fw->priv);
         if (ret) {
                 if (!(opt_flags & FW_OPT_NO_WARN))
                         dev_dbg(device,
                                  "Firmware %s was not found in kernel paths. rc:%d\n",
                                  name, ret);
                 if (opt_flags & FW_OPT_USERHELPER) {
                         dev_err(device, "[%s]Falling back to user helper\n", __func__);
                         ret = fw_load_from_user_helper(fw, name, device,
                                                        opt_flags, timeout);
                 }
         }
      ......
	 if (!ret)
	        ret = assign_firmware_buf(fw, device, opt_flags);
     ......
 }

  Firmware lookup order:

  • The ‘’Built-in firmware’’ is checked first, if the firmware is present we return it immediately
  • The ‘’Firmware cache’’ is looked at next. If the firmware is found we return it immediately
  • The ‘’Direct filesystem lookup’’ is performed next, if found we return it immediately
  • If no firmware has been found and the fallback mechanism was enabled the sysfs interface is created. After this either a kobject uevent is issued or the custom firmware loading is relied upon for firmware loading up to the timeout value.

2.5.第1种情况分析

  实现在fw_get_builtin_firmware()函数中, 原理是通过遍历builtin_fw段的firmware, 并比较firmware的name是否相同, 如果相同, 表示匹配上,则将firmware的size和data赋值给驱动传过来的firmware结构体指针, request_firmware就完成load firmware功能。

  Mechanism of firmware builtin
  When run command “make bzImage”, Makefile under “firmware” generates an assembly file “fw_sst_0f28.bin.gen.S”:

  1 /* Generated by firmware/Makefile */
  2     .section .rodata
  3     .p2align 2
  4 _fw_fw_sst_0f28_bin_bin:
  5     .incbin "/lib/firmware/fw_sst_0f28.bin"                                                              
  6 _fw_end:
  7    .section .rodata.str,"aMS",%progbits,1
  8     .p2align 2
  9 _fw_fw_sst_0f28_bin_name:
 10     .string "fw_sst_0f28.bin"
 11     .section .builtin_fw,"a",%progbits
 12     .p2align 2
 13     .long _fw_fw_sst_0f28_bin_name
 14     .long _fw_fw_sst_0f28_bin_bin
 15     .long _fw_end - _fw_fw_sst_0f28_bin_bin

  This assembly file contains three sections: “.rodata”, “rodata.str” and “.builtin_fw”.

  • section “.rodata” saves the firmware data in “fw_sst_0f28.bin”
  • section “.rodata.str” saves the name of the file “fw_sst_0f28.bin”
  • section “.builtin_fw” saves the address of the firmware name and the address of firmware data

  The link-script vmlinux.lds involves the following section:

        .builtin_fw      : {
              __start_builtin_fw = .
              *(.builtin_fw)
              __end_builtin_fw = .
         }

  GNU linker locates this section in all the object files into the “.builtin_fw” section of the kernel image. After kernel image is running in memory, “__start_builtin_fw” and “__end_builtin_fw” are the start and end address of this section.

  When kernel calls “request_firmware()”, firmware names in section “.builtin_fw” will be checked to match the firmware name passed in. If matched, kernel copy the firmware data to a buffer and “request_firmware()” returns successfully.

2.6.第2种情况

  实现在fw_lookup_and_allocate_buf()函数中, 匹配原理和第1种情况相同, 只不过查找实在全局变量fw_cache的链表上查找. fw_cache的 head链表上保存了以前load过的fw的信息,比如name, data, size等等. 其中在函数fw_state_wait()中主要检查fw是否已经load到内核空间, 如果没有, 则等待; 否在就调用fw_set_page_data(), 将fw相关的信息赋值到驱动的firmware结构体指针.

2.7.第3种情况

  依据内核预先定义好的路径fw_path调用内核文件读写接口fw_get_filesystem_firmware->kernel_read_file_from_path load入相应的fw, 在fw_finish_direct_load()函数中做了一些load fw后的清理工作,比如设置完成标志等等.

Load one target firmware from fs(file system) directly

  Kernel searchs the firmware with the firmware name in the following path:

  <1>. fw_path_para(defined in kernel command line with "firmware_class.path = xxx")
  <2>. "/lib/firmware/updates" UTS_RELEASE,
  <3>. "/lib/firmware/updates",
  <4>. "/lib/firmware" UTS_RELEASE,
  <5>. "/lib/firmware",

  If target firmware file is found, kernel grows a buffer to hold the binary files and “request_firmware()” returns sucessfully.

2.8.第4种情况

  其工作在函数为fw_load_from_user_helper()种实现.

Load firmware in userspace

  Kernel creates a directory under “/sys/class/firmware/”. For example, “/sys/class/firmware/fw_sst_0f28.bin/{loading, data}”.

Steps of loading firmware in userspace:

  • echo 1 > /sys/class/firmware/fw_sst_0f28.bin/loading
  • cat appropriate_firmware_image > /sys/class/firmware/fw_sst_0f28.bin/data
  • echo 0 > /sys/class/firmware/fw_sst_0f28.bin/loading

  Kernel grows a buffer to hold the binary file as it comes in, and “request_firmware()” returns successfully.

refer to

  • https://www.kernel.org/doc/html/v4.17/driver-api/firmware/introduction.html
  • http://kernel.meizu.com/implementation-of-loading-fw-from-userspace.html
  • https://wiki.tizen.org/Usage_and_Mechanism_of_kernel_function_%22request_firmware()%22
  • Documentation/firmware_class/README
  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值