linux-固件子系统

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

  1. 软件将固件放入到一个数组中, 在开机的时候内核将固件下发到外部 ic. 这种方式无法动态更新 fw ,早期的 tp 采用这种方式.
  2. 将固件提前编译进指定的数据段 builtin_fw, 开机的时候从该数据段获取固件, 下发至 ic
  3. 将固件放入指定的目录, 开机的时候内核从该目录获取固件, 下发至 ic
  4. 将固件放入指定目录, 开机的时候像用户空间发送 uevent 事件, 用户空间解析该事件, 将指定目录的固件通过属性节点下发到内核, 内核获取到固件之后, 再下发至ic

    目前采用较多的是后面三种, 内核已经帮我们实现了

1
2
3
4
5
6
// 依次采用上述 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

    用于保存获取到的固件,

1
2
3
4
5
6
7
8
struct firmware {
size_t size; // 固件大小
const u8 *data; // 固件首地址
struct page **pages; // 存储的物理页面

// 私有数据一般指向 firmware_buf
void *priv;
};

1.2 firmware_buf

    该结构体包含固件的基本信息,已经下载的固件会保存到 fw_cache 中,以防重复下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

     用来记录固件的下载信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

    当使用用户空间下载固件的方式时,使用该结构创建设备节点给用户空间使用。

1
2
3
4
5
6
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 来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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 返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 数据段查找固件,找到则直接返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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 调用之前创建,否则无法获取直接返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* 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 实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 为后面用户空间的下载做准备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 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 事件。上层获取到该事件后下载固件到内核。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 处理该事件。

1
2
3
4
5
--> ueventd_main
--> uevent_listener.Poll // 这部分监听 uevent 事件 ACTION, DEVPATH, SUBSYSTEM, FIRMWARE 等
--> ReadUevent(&uevent)
--> ParseEvent(msg, uevent);
--> ProcessFirmwareEvent // 从指定路径获取固件并写入到内核

    主要的下载由 ProcessFirmwareEvent 处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

    将固件下载到内核。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 表示下载完成。