request_firmware()分析——加载在用户空间的固件

request_firmware()分析——加载在⽤户空间的固件
版权声明:本⽂为博主原创⽂章,未经博主允许不得转载。 /zifehng/article/details/60321966
  ⼀些不带内置存储的设备,依赖于驱动预加载的固件才能运⾏,传统做法是将固件⼆进制码作为⼀个数组编译进驱动代码,如下所⽰:
static const unsigned char my_firmware[] = {
0x00, 0x01, 0x02,
0x01, 0x03, 0x04,
0xff, 0xef, 0xda,
......
};
这种⽅法图⼀时省⼒,却为后续的维护升级带来了⿇烦,每次设备⼚商发布新的固件,都需要重新编译
驱动模块或整个内核
  针对这种情况,在内核中其实早有解决办法,request_firmware()是⼀套成熟的固件加载⽅案:将固件以⼆进制⽂件形式存储于⽂件系统之中,在内核启动后再从⽤户空间将固件传递⾄内核空间,解析固件获得参数,最后加载⾄硬件设备。
  但是requeset_firmware()必须等到⽂件系统挂载后才能得到⽤户空间的固件,固件加载时间落后于传统⽅法。是想要更早的加载时间还是更⽅便的后续维护?需要驱动开发者根据实际情况去选择。
  在《Linux设备驱动程序》和《Linux设备驱动开发详解》中对request_firmware()的介绍都是寥寥数语,在本⽂中,我对⾃⼰实际使⽤该接⼝的⼀些经验和⽹上零落的资料进⾏了总结,希望可以帮助更多的驱动开发者。 
固件加载相关内核API介绍
#include <linux/firmware.h>
int request_firmware(const struct firmware **fw, const char *name,
struct device *device);
参数描述
fw⽤于保存申请到的固件
name固件名(⾮路径名)
device申请固件的设备结构体
  如果申请固件成功,函数返回0;申请固件失败,返回⼀个负数值(如-EINVAL、-EBUSY)。申请固件成功后,fw指向如下结构体:
struct firmware {
size_t size;
const u8 *data;
struct page **pages;
/* firmware loader private fields */
void *priv;
};
  在调⽤request_firmware()时,函数将在 /sys/class/firmware 下创建⼀个以设备名为⽬录名的新⽬录,其中包含 3 个属性:
  1. loading ,当固件加载时被置1,加载完毕被置0,如果被置-1则终⽌固件加载;
  2. data,内核获取固件接⼝,当loading被置1时,⽤户空间通过该属性接⼝传递固件⾄内核空间;
  3. device ,符号链接,链接⾄/sys/devices/下相关设备⽬录。
  当sysfs接⼝创建完毕,udevd会配合将固件通过sysfs节点写⼊内核。在内核空间中获取到固件后,应该对其进⾏校验检查,然后再解析加载⾄设备,最后通过如下API释放firmware 结构体:
void release_firmware(const struct firmware *fw);
使⽤request_fimware()前的准备
  在使⽤requeset_firmware()之前,要在menuconfig之中勾选相应配置:
Symbol: FW_LOADER [=y]
│ Type  : tristate
│ Prompt: Userspace firmware loading support
│  Location:
│    -> Device Drivers
│ (1)  -> Generic Driver Options
│  Defined at drivers/base/Kconfig:77
或者直接在.config中配置:
// .config
......
CONFIG_FW_LOADER=y
......
  在内核中CONFIG_FW_LOADER选项默认关闭,如果不进⾏⼿动配置,requset_firmware()会被预编译为⼀个内联函数(不做任何操作,直接返回-EINVAL):
// include/linux/firmware.h
阿伐那非的作用与功效#if defined(CONFIG_FW_LOADER) || (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
int request_firmware(const struct firmware **fw, const char *name,
struct device *device);
......
#else
static inline int request_firmware(const struct firmware **fw,
const char *name,
struct device *device)
{
return -EINVAL;
}
......
#endif
  在上⽂介绍request_firmware()时提到第⼆个参数name为固件名,⽽⾮路径名,那么内核是怎么在没有⽂件路径的情况下到固件呢?答案其实是内核已经在fw_path数组中指定了路径名,我们只需要将固件置于相对应⽬录下即可:
// driver/base/firmware_class.c
static const char * const fw_path[] = {
fw_path_para,
"/system/etc/firmware",
"/lib/firmware/updates/" UTS_RELEASE,
"/lib/firmware/updates",
"/lib/firmware/" UTS_RELEASE,
"/lib/firmware",
};
⼤部分情况下固件置于/system/etc/firmware⽬录,如果需要指定⾃定义⽬录,则需要在fw_path数组中添加⽬录路径名。
requset_firmware()——固件加载实例分析
  正如linus所说“talk is cheap, show me the code”,接下来我们以requset_firmware()等系列内核api为基础设计⼀个加载⽤户空间固件的⽅案,具体流程如下:
1、⽣成固件“my_frimware.bin”并置于⾃定义⽬录/data/my_firmware”下,
然后在fw_path数组中添加⽬录路径名:
// driver/base/firmware_class.c
static const char * const fw_path[] = {
减速机测试台fw_path_para,
"/data/my_firmware",
"/system/etc/firmware",
"/lib/firmware/updates/" UTS_RELEASE,
"/lib/firmware/updates",
"/lib/firmware/" UTS_RELEASE,
"/lib/firmware",
};
2、request_firmware()函数如果在probe()中使⽤会⼀直阻塞⾄⽂件系统挂载获取固件后,因此我们在驱动中将其封装为sysfs节点,以便在⽂件系统挂载后调⽤:
// driver/test_driver.c
......
static ssize_t load_fw_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
#define FIRMWARE_NAME "/data/my_firmware/my_firmware.bin"智能卡制作
int ret;
unsigned long value;
const struct firmware *fw = NULL;
if (kstrtoul(buf, 10, &value))
return -EINVAL;
if (value == 1) {
/* 申请⽤户空间固件 */
ret = request_firmware(&fw, FIRMWARE_NAME, dev);
if (ret)
return -ENOENT;
/* 加载固件⾄硬件设备 */
load_fw(fw);
/* 释放firmware结构体 */
release_firmware(fw);
}
return count;
}
// sys/devices/platform/test/load_fw
static DEVICE_ATTR(load_fw, S_IWUGO, NULL, load_fw_store);
......
static int test_probe(struct platform_device *pdev)
{
int ret;
......
/* 创建sysfs节点 */
ret = device_create_file(pdev, &dev_attr_load_fw);
if (ret)
return -EINVAL;
......
return 0;
}
......
3、由于固件位于/data分区,所以在中“on post-fs-data”后添加对sysfs节点的操作,实现开机/data分区挂载后即开始⾃动加载固件:
// system/core/
......
on post-fs-data
write sys/devices/platform/test/load_fw 1
......
requeset_firmware_nowait()——以异步⽅式加载固件
  在上⼀个实例中我们⽤sysfs+request_firmware()实现了加载⽤户空间固件的⽅案,但request_firmware()是以同步⽅式运⾏的,如果在调⽤requset_firmware()的上下⽂中不能睡眠,我们应该选择另⼀个异步api来加载挂件:
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));
参数描述
module模块名透水混凝土施工工艺
肥皂生产设备
uevent
name固件名
device申请固件的设备结构体
gfp内核内存分配标志位
context私有数据指针
cont回调函数
深⼊requeset_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 firmware_work *fw_work;
fw_work = kzalloc(sizeof (struct firmware_work), gfp);
if (!fw_work)
return -ENOMEM;
fw_work->module = module;
fw_work->name = name;
fw_work->device = device;
fw_work->context = context;
fw_work->cont = cont;
fw_work->uevent = uevent;
if (!try_module_get(module)) {
kfree(fw_work);
return -EFAULT;
}
get_device(fw_work->device);
/* 初始化⼯作队列 */
INIT_WORK(&fw_work->work, request_firmware_work_func);
/* 调⽤⼯作队列 */
schedule_work(&fw_work->work);
return 0;
}
了解了⽅法requset_firmware_nowait()的异步原理之后,我们对上例的test_driver.c进⾏⼀些⼩⼩的改动就能实现异步加载固件:

本文发布于:2024-09-20 23:45:15,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/1/105384.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

下一篇:jinfo用法
标签:固件   加载   内核   设备   驱动   空间   申请
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议