对于某些特定的外设, 可能需更新内部的 fw, 最常见的外设就是 tp, 内核提供了 4 种方式来更新 fw.

  1. 软件将固件放入到一个数组中, 在开机的时候内核将固件下发到外部 ic. 这种方式无法动态更新 fw ,早期的 tp 采用这种方式.
  2. 将固件提前编译进指定的数据段 builtin_fw, 开机的时候从该数据段获取固件, 下发至 ic
  3. 将固件放入指定的目录, 开机的时候内核从该目录获取固件, 下发至 ic
  4. 将固件放入指定目录, 开机的时候像用户空间发送 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 总结

  1. 首先创建一个中间设备包含两个属性节点 loading 和 data
  2. 向上层发送 FIRMWARE 固件下载 event 事件
  3. 上层监听到 FIRMWARE 启动固件下载,依次遍历 firmware_dirs 数组中的目录找到固件。
  4. 首先往 loading 节点下发 1 表示准备下载,然后将固件通过 data 目录下发到内核,发完之后向 loading 节点写 0 表示下载完成。
Logo

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

更多推荐