linux驱动- firmware子系统
文章目录
- 1、数据结构
- 1.1 firmware
- 1.2 firmware_buf
- 1.3 firmware_cache
- 1.4 firmware_priv
- 2、request_firmware
- 2.1 通过 build_in 方式
- 2.1.1 fw_get_builtin_firmware
- 2.2 直接从内核读取文件
- 2.3 内核通知上层下发固件
- 2.3.1 fw_create_instance
- 2.3.2 _request_firmware_load
- 2.3.3 上层处理
- 2.3.3.1 LoadFirmware
- 2.3.4 总结
对于某些特定的外设, 可能需更新内部的 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_bufvoid *priv;
};
1.2 firmware_buf
该结构体包含固件的基本信息,已经下载的固件会保存到 fw_cache 中,以防重复下载。
struct firmware_buf {struct kref ref; // 引用计数struct list_head list; // 挂接到 fw_cachestruct firmware_cache *fwc; // 指向所属的 fw_cachestruct fw_state fw_st; // 保存固件的下载状态,FW_STATUS_UNKNOWN,FW_STATUS_LOADING,FW_STATUS_DONE,FW_STATUS_ABORTEDvoid *data; // 固件保存的首地址size_t size; // 固件的大小size_t allocated_size;
#ifdef CONFIG_FW_LOADER_USER_HELPERbool is_paged_buf;bool need_uevent;struct page **pages;int nr_pages;int page_array_size;struct list_head pending_list;
#endifconst char *fw_id;
};
1.3 firmware_cache
用来记录固件的下载信息
struct firmware_cache {spinlock_t lock;struct list_head head; // 挂接已经下载的固件int state;#ifdef CONFIG_PM_SLEEPspinlock_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->sizeret = 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");// Sret = fw_load_from_user_helper(fw, name, device,opt_flags);}} elseret = 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 = nameret = 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 stimeout = 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 和 loadingretval = 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;elseretval = -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_fdunique_fd loading_fd(open(loading.c_str(), O_WRONLY | O_CLOEXEC));// 获取 data 的文件描述符 data_fdunique_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_fdstd::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 写 0const 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 表示下载完成。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
