linux驱动- firmware子系统
首先创建一个中间设备包含两个属性节点 loading 和 data向上层发送 FIRMWARE 固件下载 event 事件上层监听到 FIRMWARE 启动固件下载,依次遍历 firmware_dirs 数组中的目录找到固件。首先往 loading 节点下发 1 表示准备下载,然后将固件通过 data 目录下发到内核,发完之后向 loading 节点写 0 表示下载完成。
文章目录
对于某些特定的外设, 可能需更新内部的 fw, 最常见的外设就是 tp, 内核提供了 4 种方式来更新 fw.
- 软件将固件放入到一个数组中, 在开机的时候内核将固件下发到外部 ic. 这种方式无法动态更新 fw ,早期的 tp 采用这种方式.
- 将固件提前编译进指定的数据段 builtin_fw, 开机的时候从该数据段获取固件, 下发至 ic
- 将固件放入指定的目录, 开机的时候内核从该目录获取固件, 下发至 ic
- 将固件放入指定目录, 开机的时候像用户空间发送 uevent 事件, 用户空间解析该事件, 将指定目录的固件通过属性节点下发到内核, 内核获取到固件之后, 再下发至ic
目前采用较多的是后面三种, 内核已经帮我们实现了
// 依次采用上述 2, 3, 4 种方式获取固件
int request_firmware(const struct firmware **firmware_p, const char *name,struct device *device)
// 采用 2 , 3 两种方式获取固件.
int request_firmware_direct(const struct firmware **firmware_p, const char *name, struct device *device)
1、数据结构
1.1 firmware
用于保存获取到的固件,
struct firmware {
size_t size; // 固件大小
const u8 *data; // 固件首地址
struct page **pages; // 存储的物理页面
// 私有数据一般指向 firmware_buf
void *priv;
};
1.2 firmware_buf
该结构体包含固件的基本信息,已经下载的固件会保存到 fw_cache 中,以防重复下载。
struct firmware_buf {
struct kref ref; // 引用计数
struct list_head list; // 挂接到 fw_cache
struct firmware_cache *fwc; // 指向所属的 fw_cache
struct fw_state fw_st; // 保存固件的下载状态,FW_STATUS_UNKNOWN,FW_STATUS_LOADING,FW_STATUS_DONE,FW_STATUS_ABORTED
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;
};
1.3 firmware_cache
用来记录固件的下载信息
struct firmware_cache {
spinlock_t lock;
struct list_head head; // 挂接已经下载的固件
int state;
#ifdef CONFIG_PM_SLEEP
spinlock_t name_lock;
struct list_head fw_names;
struct delayed_work work;
struct notifier_block pm_notify;
#endif
};
1.4 firmware_priv
当使用用户空间下载固件的方式时,使用该结构创建设备节点给用户空间使用。
struct firmware_priv {
bool nowait;
struct device dev;
struct firmware_buf *buf;
struct firmware *fw;
};
2、request_firmware
最常用的方式是直接调用 request_firmware 接口该接口已经被封装的十分简单,firmware_p 用来保存获取的固件,name 表示固件的名称,device 是固件所属设备在使用 FW_OPT_USERHELPER 方式获取时使用。request_firmware 接口直接调用了_request_firmware 来实现。
int request_firmware(const struct firmware **firmware_p, const char *name,
struct device *device)
{
int ret;
__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;
}
// 分别采用 build-in , 直接读取内核节点,通知用户空间下载三种方式获取固件。
static int _request_firmware(const struct firmware **firmware_p, const char *name,
struct device *device, void *buf, size_t size,
unsigned int opt_flags)
{
struct firmware *fw = NULL;
int ret;
if (!firmware_p)
return -EINVAL;
if (!name || name[0] == '\0') {
ret = -EINVAL;
goto out;
}
// 尝试通过 build-in 方式获取固件
// 如果未获取到固件, 则在 fw_cache 中查找已经获下载的 firmware_buf, 没有则创建一个
ret = _request_firmware_prepare(&fw, name, device, buf, size);
if (ret <= 0) /* error or already assigned */
goto out;
// 直接通过读取方式获取固件
// 在 fw_path[] 指定的目录下, 获取 name 文件中的内容并保存到 firmware_buf 中的 buf->data 和 buf->size
ret = fw_get_filesystem_firmware(device, fw->priv);
if (ret) {
if (!(opt_flags & FW_OPT_NO_WARN))
dev_warn(device,
"Direct firmware load for %s failed with error %d\n",
name, ret);
if (opt_flags & FW_OPT_USERHELPER) {
dev_warn(device, "Falling back to user helper\n");
// S
ret = fw_load_from_user_helper(fw, name, device,
opt_flags);
}
} else
ret = assign_firmware_buf(fw, device, opt_flags);
out:
if (ret < 0) {
fw_abort_batch_reqs(fw);
release_firmware(fw);
fw = NULL;
}
*firmware_p = fw;
return ret;
}
可以看出整个实现由三个函数分别实现 3 个功能,依次分析 3 个功能的实现
2.1 通过 build_in 方式
首先在 builtin_fw 数据段查找固件如果找不到,则在 fw_cache 中搜索固件是否已经下载,如果是已经下载的固件则直接返回对应的 firmware_buf。没有则动态创建一个空的 firmware_buf 返回。
static int _request_firmware_prepare(struct firmware **firmware_p, const char *name,
struct device *device, void *dbuf, size_t size)
{
struct firmware *firmware;
struct firmware_buf *buf;
int ret;
// 创建固件结构
*firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
if (!firmware) {
dev_err(device, "%s: kmalloc(struct firmware) failed\n",
__func__);
return -ENOMEM;
}
// 通过 build-in 方式获取固件
if (fw_get_builtin_firmware(firmware, name, dbuf, size)) {
dev_dbg(device, "using built-in %s\n", name);
return 0; /* assigned */
}
// 在 fw_cache 中查找已经下载的固件, 没有则创建一个 firmware_buf 其中 buf->fw_id = name
ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf, dbuf, size);
firmware->priv = buf; //设置私有数据
if (ret > 0) {
ret = fw_state_wait(&buf->fw_st);
if (!ret) {
fw_set_page_data(buf, firmware);
return 0; /* assigned */
}
}
if (ret < 0)
return ret;
return 1; /* need to load */
}
2.1.1 fw_get_builtin_firmware
在 __start_builtin_fw 表示的数据段查找固件,也就是在 builtin_fw 数据段查找固件,找到则直接返回。
// include/asm-generic/vmlinux.lds.h
.builtin_fw : AT(ADDR(.builtin_fw) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__start_builtin_fw) = .; \
KEEP(*(.builtin_fw)) \
VMLINUX_SYMBOL(__end_builtin_fw) = .; \
}
static bool fw_get_builtin_firmware(struct firmware *fw, const char *name,
void *buf, size_t size)
{
struct builtin_fw *b_fw;
// 在 __start_builtin_fw 表示的数据段查找固件,也就是在 builtin_fw 数据段查找固件,找到则直接返回
for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) {
if (strcmp(name, b_fw->name) == 0) {
fw->size = b_fw->size;
fw->data = b_fw->data;
if (buf && fw->size <= size)
memcpy(buf, fw->data, fw->size);
return true;
}
}
return false;
}
2.2 直接从内核读取文件
该接口很简单,就是直接遍历读取 fw_path 中支持的路径,并依次读取固件。需要注意的是使用这种方式获取固件要保证。fw_path 中的路径要在 request_firmware 调用之前创建,否则无法获取直接返回。
/* direct firmware loading support */
static char fw_path_para[256];
static const char * const fw_path[] = { // 固件保存路径
"/vendor/firmware",
fw_path_para,
"/lib/firmware/updates/" UTS_RELEASE,
"/lib/firmware/updates",
"/lib/firmware/" UTS_RELEASE,
"/lib/firmware"
};
static int fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
{
loff_t size;
int i, len;
int rc = -ENOENT;
char *path;
enum kernel_read_file_id id = READING_FIRMWARE;
size_t msize = INT_MAX;
for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
if (!fw_path[i][0])
continue;
len = snprintf(path, PATH_MAX, "%s/%s",
fw_path[i], buf->fw_id);
buf->size = 0;
// 直接读取固件
rc = kernel_read_file_from_path(path, &buf->data, &size, msize,
id);
dev_dbg(device, "direct-loading %s\n", buf->fw_id);
buf->size = size;
fw_state_done(&buf->fw_st);
break;
}
__putname(path);
return rc;
}
2.3 内核通知上层下发固件
该方式是实现比较复杂的,采用设备模型的 uevent 实现。
static int fw_load_from_user_helper(struct firmware *firmware,
const char *name, struct device *device,
unsigned int opt_flags)
{
struct firmware_priv *fw_priv;
long timeout;
int ret;
//设置超时时间默认为 60 s
timeout = firmware_loading_timeout();
if (opt_flags & FW_OPT_NOWAIT) {
timeout = usermodehelper_read_lock_wait(timeout);
if (!timeout) {
dev_dbg(device, "firmware: %s loading timed out\n",
name);
return -EBUSY;
}
} else {
ret = usermodehelper_read_trylock();
if (WARN_ON(ret)) {
dev_err(device, "firmware: %s will not be loaded\n",
name);
return ret;
}
}
// 创建一个中间设备 firmware_priv ,该设备包含两个属性节点, loading 和 data 为后面用户空间的下载做准备。
fw_priv = fw_create_instance(firmware, name, device, opt_flags);
if (IS_ERR(fw_priv)) {
ret = PTR_ERR(fw_priv);
goto out_unlock;
}
fw_priv->buf = firmware->priv;
// 下载固件
ret = _request_firmware_load(fw_priv, opt_flags, timeout);
if (!ret) // 将固件转换为 firmware 结构保存
ret = assign_firmware_buf(firmware, device, opt_flags);
out_unlock:
usermodehelper_read_unlock();
return ret;
}
2.3.1 fw_create_instance
创建一个中间设备 firmware_priv ,该设备包含两个属性节点, loading 和 data 为后面用户空间的下载做准备。
// loading 节点用来告诉内核下载状态 0 表示完成下载,1 表示准备下载
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
// data 节点用来下载固件
static struct bin_attribute firmware_attr_data = {
.attr = { .name = "data", .mode = 0644 },
.size = 0,
.read = firmware_data_read,
.write = firmware_data_write,
};
static struct firmware_priv *
fw_create_instance(struct firmware *firmware, const char *fw_name,
struct device *device, unsigned int opt_flags)
{
struct firmware_priv *fw_priv; // 创建一个中间设备结构
struct device *f_dev;
fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL);
if (!fw_priv) {
fw_priv = ERR_PTR(-ENOMEM);
goto exit;
}
fw_priv->nowait = !!(opt_flags & FW_OPT_NOWAIT);
fw_priv->fw = firmware;
f_dev = &fw_priv->dev;
device_initialize(f_dev);
dev_set_name(f_dev, "%s", fw_name); // 设置设备名
f_dev->parent = device; // 所属设备
f_dev->class = &firmware_class; // 所属类
f_dev->groups = fw_dev_attr_groups; // 给用户空间使用的属性文件
exit:
return fw_priv;
}
2.3.2 _request_firmware_load
该接口创建中间属性节点 date 和 loading,同时想上层发送 FIRMWARE 事件。上层获取到该事件后下载固件到内核。
static int _request_firmware_load(struct firmware_priv *fw_priv,
unsigned int opt_flags, long timeout)
{
int retval = 0;
struct device *f_dev = &fw_priv->dev;
struct firmware_buf *buf = fw_priv->buf;
/* fall back on userspace loading */
if (!buf->data)
buf->is_paged_buf = true;
dev_set_uevent_suppress(f_dev, true);
// 创建中间属性节点 date 和 loading
retval = device_add(f_dev);
if (retval) {
dev_err(f_dev, "%s: device_register failed\n", __func__);
goto err_put_dev;
}
mutex_lock(&fw_lock);
list_add(&buf->pending_list, &pending_fw_head);
mutex_unlock(&fw_lock);
if (opt_flags & FW_OPT_UEVENT) {
buf->need_uevent = true;
dev_set_uevent_suppress(f_dev, false);
dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id);
// 发送 uevent 事件
kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD);
} else {
timeout = MAX_JIFFY_OFFSET;
}
// 等待下载是否超时
retval = fw_state_wait_timeout(&buf->fw_st, timeout);
if (retval < 0) {
mutex_lock(&fw_lock);
fw_load_abort(fw_priv);
mutex_unlock(&fw_lock);
}
if (fw_state_is_aborted(&buf->fw_st)) {
if (retval == -ERESTARTSYS)
retval = -EINTR;
else
retval = -EAGAIN;
} else if (buf->is_paged_buf && !buf->data)
retval = -ENOMEM;
device_del(f_dev);
err_put_dev:
put_device(f_dev);
return retval;
}
2.3.3 上层处理
上层在 ueventd_main 中监听 uevent事件,当检测到 FIRMWARE 事件后,由 ProcessFirmwareEvent 处理该事件。
--> ueventd_main
--> uevent_listener.Poll // 这部分监听 uevent 事件 ACTION, DEVPATH, SUBSYSTEM, FIRMWARE 等
--> ReadUevent(&uevent)
--> ParseEvent(msg, uevent);
--> ProcessFirmwareEvent // 从指定路径获取固件并写入到内核
主要的下载由 ProcessFirmwareEvent 处理。
static void ProcessFirmwareEvent(const Uevent& uevent) {
int booting = IsBooting();
LOG(INFO) << "firmware: loading '" << uevent.firmware << "' for '" << uevent.path << "'";
// 获取 loading 和 data 两个属性文件所在路径
std::string root = "/sys" + uevent.path;
std::string loading = root + "/loading";
std::string data = root + "/data";
// 获取 loding 的文件描述符 loading_fd
unique_fd loading_fd(open(loading.c_str(), O_WRONLY | O_CLOEXEC));
// 获取 data 的文件描述符 data_fd
unique_fd data_fd(open(data.c_str(), O_WRONLY | O_CLOEXEC));
// 固件所在目录
static const char* firmware_dirs[] = {"/etc/firmware/", "/odm/firmware/",
"/vendor/firmware/", "/firmware/image/"};
try_loading_again:
for (size_t i = 0; i < arraysize(firmware_dirs); i++) {
// 获取固件所在的文件描述符 fw_fd
std::string file = firmware_dirs[i] + uevent.firmware;
unique_fd fw_fd(open(file.c_str(), O_RDONLY | O_CLOEXEC));
struct stat sb;
if (fw_fd != -1 && fstat(fw_fd, &sb) != -1) {
// 调用 LoadFirmware 下载固件
LoadFirmware(uevent, root, fw_fd, sb.st_size, loading_fd, data_fd);
return;
}
}
if (booting) {
std::this_thread::sleep_for(100ms);
booting = IsBooting();
goto try_loading_again;
}
LOG(ERROR) << "firmware: could not find firmware for " << uevent.firmware;
write(loading_fd, "-1", 2);
}
2.3.3.1 LoadFirmware
将固件下载到内核。
static void LoadFirmware(const Uevent& uevent, const std::string& root, int fw_fd, size_t fw_size,
int loading_fd, int data_fd) {
// 向 loading 节点写 1 表示开始下载
WriteFully(loading_fd, "1", 1);
// 将固件从 fw_fd 描述的节点拷贝到 date 节点。
int rc = sendfile(data_fd, fw_fd, nullptr, fw_size);
if (rc == -1) {
PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << uevent.firmware
<< "' }";
}
// 完了之后向 loading 写 0
const char* response = (rc != -1) ? "0" : "-1";
WriteFully(loading_fd, response, strlen(response));
}
2.3.4 总结
- 首先创建一个中间设备包含两个属性节点 loading 和 data
- 向上层发送 FIRMWARE 固件下载 event 事件
- 上层监听到 FIRMWARE 启动固件下载,依次遍历 firmware_dirs 数组中的目录找到固件。
- 首先往 loading 节点下发 1 表示准备下载,然后将固件通过 data 目录下发到内核,发完之后向 loading 节点写 0 表示下载完成。

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)