Linux Firmware API 应用总结
简介
Linux内核开发过程中,开发人员调试外设驱动设备,比如触控,充电,线性马达,存储,WIFI设备等,均存在需要更新固件的情况。在Linux系统中,设备驱动程序处于内核态,而固件文件处于用户态,因此需要一个安全稳定可靠的机制,用来确保设备驱动程序成功加载固件文件。
为了解决设备驱动程序从内核态稳定加载用户态固件文件的问题,Linux系统提供了固件子系统。
固件API使内核代码可以从用户空间请求功能所需的文件,比如:
- 设备驱动文件
- 设备驱动程序信息数据(驱动层需要调用的用户侧数据均可通过此方式获取)
固件升级流程
Linux固件子系统基于sysfs和Uevent机制实现。
驱动程序调用固件系统函数接口申请固件之后,固件子系统使用固件编译内核的方式去获取固件;如果获取失败,就使用固件缓存的方式去获取固件;如果仍然获取失败,就使用默认路径内核直接查找的方式去获取固件。如果还是获取失败,就通过上报uevent消息给init进程。init进程则接收到uevent消息,过滤出subsystem类型为firmware的消息。init进程根据uevent消息内指向的固件信息去查找固件,通过sysfs提供的文件节点接口,把获取的固件内容从用户态写入内核态,从而使驱动程序,获取到固件文件的数据。
Linux固件系统提供了多种在不同场景下获取固件文件的方法:
-
直接编译到内核的方式;
-
固件缓存的方式;
-
直接根据内核指定路径的方式:
-
通过init进程来协助处理的方式;
固件存放路径一
以下搜索路径用于在根文件系统上查找固件
- fw_path_para - 模块参数 - 默认为空,因此被忽略
- /lib/firmware/updates/UTS_RELEASE/
- /lib/firmware/updates/
- /lib/firmware/UTS_RELEASE/
- /lib/firmware/
模块参数’PATH’可以传递给firmware_class模块来激活第一个可选的fw_path_para。自定义路径最多包含256个字符。传递的内核参数将是:‘firmware_class.path=$CUSTOMIZED_PATH’
可以根据需要自行增加固件路径
方法一:在kernel/drivers/base/firmware_class.c中添加文件目录。添加的位置在以下路径数组中:
如下示例,在数组中新增/system/firmware路径。
static const char* const fw_path[]={
+"/system/firmware",
}
方法二:修改fw_path_para参数
- 添加固件临时存放地址 /vendor/firmware
echo -n “/vendor/firmware” > /sys/module/firmware_class/parameters/path。 - 添加永久地址
在系统初始化该变量的时候改为自己需要的地址:
对应的修改在~/rk3399-android-10/device/rockchip/common/BoardConfig.mk:
固件存放路径二
将固件存放于uevent默认的固件搜索路径,来自 ueventd.rc文件内指定的firmware_directory:
/etc/firmware/
/odm/firmware/
/vendor/firmware/
/firmware.image/
固件回退机制
固件回退机制是针对在指定路径固件查找失败的情况。
对于部分linux系统可能不存在以上固件路径一种指定的固件路径或者不允许用户在以上路径放置文件,这种情况下就需要使用固件回退机制,通过其他路径来升级固件。
启用固件回退机制的方法:
- CONFIG_FW_LOADER_USER_HELPER:配置该项使能固件回退机制。
- CONFIG_FW_LOADER_USER_HELPER_FALLBACK:同时配置使能该项。
但是一般情况下第二项配置是禁用的。所以只能使用自定义的备用机制,即用request_firmware_nowait()接口,将第二个参数设置为false.
固件请求流程
request_firmware实现流程
- 当我们调用固件接口进行固件升级时,固件接口首先调用_request_firmware_prepare函数,该函数通过调用fw_get_builtin_firmware函数判断固件文件是否编译到内核中。
- 若固件未编译到内核,则通过 fw_get_filesystem_firmware函数在默认固件路径(即上述提到的固件存放路径一)搜索固件文件
USER_HELPER模式
- 若默认固件路径也搜索不到固件,则检测是否打开固件回退机制(USER_HELPER模式),前提是需要在内核打开CONFIG_FW_LOADER_USER_HELPER。
流程是:通过kernel上报Uevent消息到init进程,通过init进程获取固件信息,并写入sysfs节点中。
固件请求接口
通常情况下我们使用异步接口加载固件,此处只介绍异步固件加载接口。
-
request_firmware_nowait
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) ) 参数 struct module *module 模块请求固件 bool uevent 如果此标志非零,则发送 uevent 以复制固件映像,否则必须手动完成固件复制。 const char *name 固件文件名 struct device *device 正在为其加载固件的设备 gfp_t gfp 分配标志 void *context 将传递给cont, 如果固件请求失败,则fw可能会传递。NULL void (*cont)(const struct firmware *fw, void *context) 当固件请求结束时,函数将被异步调用
- 应用实例
/** * @brief fml_firmware_config_cb * @param * @retval none */ static void fml_firmware_config_cb(const struct firmware *cfg, void *ctx) { int fw_error = 0; if(cfg) { fw_error = fml_firmware_send_data((unsigned char*)cfg->data, cfg->size); if(fw_error) { LOG_ERR("firmware send data err:%d\n", fw_error); goto err_release_cfg; } } err_release_cfg: release_firmware(cfg); LOG_ERR("end\n"); } /** * @brief firmware update by file * @param * @retval 0:success, -1: fail,1:no need update */ int fml_fw_update_by_file(void) { int ret_error = 0; struct cs_dev *fw_st = NULL; fw_st = devm_kzalloc(&(g_cs_dev.client->dev), sizeof(*fw_st), GFP_KERNEL); if(!fw_st) { LOG_ERR("devm_kzalloc failed\n"); return -1; } fw_st->client = g_cs_dev.client; ret_error = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, FW_FILE_NAME,\ &(g_cs_dev.client->dev), GFP_KERNEL, fw_st, fml_firmware_config_cb); devm_kfree(&(g_cs_dev.client->dev), fw_st); if(ret_error) { LOG_ERR("request_firmware_nowait failed:%d\n",ret_error); return -1; } LOG_ERR("end\n"); return 0; }
参考链接
http://t.zoukankan.com/hellokitty2-p-14594987.html