用户空间 (Android init/ueventd)内核 (firmware_loader)驱动程序用户空间 (Android init/ueventd)内核 (firmware_loader)驱动程序alt[找到 firmware][未找到]调用 request_firmware()查找 /lib/firmware返回 firmware触发 sysfs fallback发送 ueventinit/ueventd 接收 uevent根据 ueventd.rc 依次查找/vendor/firmware/firmware/image...通过 sysfs fallback 提供 firmware返回 firmware
Userspace (Android init/ueventd)Kernel (firmware_loader)DriverUserspace (Android init/ueventd)Kernel (firmware_loader)Driveralt[Firmware found][Not found]Call request_firmware()Look up /lib/firmwareReturn firmwareTrigger sysfs fallbackSend ueventinit/ueventd receives the ueventSearch /vendor/firmware/firmware/image... in orderaccording to ueventd.rcProvide firmware via sysfs fallbackReturn firmware

本文将解析 Android 系统中固件加载的路径和流程,帮助理解驱动程序如何获取所需的固件文件。

固件的加载流程主要由上图所描述,下面我们结合代码来详细说明。

驱动程序调用

驱动程序在需要加载固件时,会调用 request_firmware() 系列函数,这些函数被定义在 Linux 内核的 include/linux/firmware.h 中,以下是高通 CNSS 平台驱动的示例代码:

https://github.com/WANG-Guangxin/wlan-driver/blob/5113495b16420b49004c444715d2daae2066e7dc/platform/cnss2/main.c#L4032

1
2
3
4
5
6
7
8
9
10
11
int cnss_request_firmware_direct(struct cnss_plat_data *plat_priv,
const struct firmware **fw_entry,
const char *filename)
{
if (IS_ENABLED(CONFIG_CNSS_REQ_FW_DIRECT))
return request_firmware_direct(fw_entry, filename,
&plat_priv->plat_dev->dev);
else
return firmware_request_nowarn(fw_entry, filename,
&plat_priv->plat_dev->dev);
}

内核处理

对于 CNSS 驱动可能使用到的 request_firmware_direct()firmware_request_nowarn() 函数,他们的实现位于内核的 drivers/base/firmware_loader/main.c 文件中。

request_firmware_direct

request_firmware_direct() 函数直接从文件系统加载固件,不会回退到用户空间的 udev 或者 Android 的 init 进程。

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/main.c#L1002

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* request_firmware_direct() - load firmware directly without usermode helper
* @firmware_p: pointer to firmware image
* @name: name of firmware file
* @device: device for which firmware is being loaded
*
* This function works pretty much like request_firmware(), but this doesn't
* fall back to usermode helper even if the firmware couldn't be loaded
* directly from fs. Hence it's useful for loading optional firmwares, which
* aren't always present, without extra long timeouts of udev.
**/
int request_firmware_direct(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, 0,
FW_OPT_UEVENT | FW_OPT_NO_WARN |
FW_OPT_NOFALLBACK_SYSFS);
module_put(THIS_MODULE);
return ret;
}
EXPORT_SYMBOL_GPL(request_firmware_direct);

firmware_request_nowarn

firmware_request_nowarn() 函数则会尝试从文件系统加载固件,如果失败则会回退到 sysfs fallback 机制,但不会打印警告信息。所谓的 sysfs fallback 机制,就是通过发送 uevent 给用户空间的 init 进程,来查找其他可能的固件路径。

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/main.c#L977

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
/**
* firmware_request_nowarn() - request for an optional fw module
* @firmware: pointer to firmware image
* @name: name of firmware file
* @device: device for which firmware is being loaded
*
* This function is similar in behaviour to request_firmware(), except it
* doesn't produce warning messages when the file is not found. The sysfs
* fallback mechanism is enabled if direct filesystem lookup fails. However,
* failures to find the firmware file with it are still suppressed. It is
* therefore up to the driver to check for the return value of this call and to
* decide when to inform the users of errors.
**/
int firmware_request_nowarn(const struct firmware **firmware, const char *name,
struct device *device)
{
int ret;

/* Need to pin this module until return */
__module_get(THIS_MODULE);
ret = _request_firmware(firmware, name, device, NULL, 0, 0,
FW_OPT_UEVENT | FW_OPT_NO_WARN);
module_put(THIS_MODULE);
return ret;
}
EXPORT_SYMBOL_GPL(firmware_request_nowarn);

firmware_fallback_sysfs

无论是 request_firmware_direct() 还是 firmware_request_nowarn(),它们都会调用 _request_firmware() 函数来处理固件加载的具体逻辑。

而在 _request_firmware() 中,我们可以看到这里存在很多 fallback 机制,这里我们重点关注的是 sysfs fallback 机制。

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/main.c#L906

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/* called from request_firmware() and request_firmware_work_func() */
static int
_request_firmware(const struct firmware **firmware_p, const char *name,
struct device *device, void *buf, size_t size,
size_t offset, u32 opt_flags)
{
struct firmware *fw = NULL;
struct cred *kern_cred = NULL;
const struct cred *old_cred;
bool nondirect = false;
int ret;

if (!firmware_p)
return -EINVAL;

if (!name || name[0] == '\0') {
ret = -EINVAL;
goto out;
}


/*
* Reject firmware file names with ".." path components.
* There are drivers that construct firmware file names from
* device-supplied strings, and we don't want some device to be
* able to tell us "I would like to be sent my firmware from
* ../../../etc/shadow, please".
*
* This intentionally only looks at the firmware name, not at
* the firmware base directory or at symlink contents.
*/
if (name_contains_dotdot(name)) {
dev_warn(device,
"Firmware load for '%s' refused, path contains '..' component\n",
name);
ret = -EINVAL;
goto out;
}

ret = _request_firmware_prepare(&fw, name, device, buf, size,
offset, opt_flags);
if (ret <= 0) /* error or already assigned */
goto out;

/*
* We are about to try to access the firmware file. Because we may have been
* called by a driver when serving an unrelated request from userland, we use
* the kernel credentials to read the file.
*/
kern_cred = prepare_kernel_cred(&init_task);
if (!kern_cred) {
ret = -ENOMEM;
goto out;
}
old_cred = override_creds(kern_cred);

ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL);

/* Only full reads can support decompression, platform, and sysfs. */
if (!(opt_flags & FW_OPT_PARTIAL))
nondirect = true;

#ifdef CONFIG_FW_LOADER_COMPRESS_ZSTD
if (ret == -ENOENT && nondirect)
ret = fw_get_filesystem_firmware(device, fw->priv, ".zst",
fw_decompress_zstd);
#endif
#ifdef CONFIG_FW_LOADER_COMPRESS_XZ
if (ret == -ENOENT && nondirect)
ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
fw_decompress_xz);
#endif
if (ret == -ENOENT && nondirect)
ret = firmware_fallback_platform(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 (nondirect)
ret = firmware_fallback_sysfs(fw, name, device,
opt_flags, ret);
} else
ret = assign_fw(fw, device);

revert_creds(old_cred);
put_cred(kern_cred);

out:
if (ret < 0) {
fw_abort_batch_reqs(fw);
release_firmware(fw);
fw = NULL;
} else {
fw_log_firmware_info(fw, name, device);
}

*firmware_p = fw;
return ret;
}

firmware_fallback_sysfs() 函数的实现定义在 drivers/base/firmware_loader/fallback.c 中,这个文件只有 200 多行代码,其中实现了向用户空间发送 uevent 的逻辑。

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/fallback.c#L224

主要的函数调用过程如下:

firmware_fallback_sysfs => fw_load_from_user_helper => fw_load_sysfs_fallback

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
62
/**
* fw_load_sysfs_fallback() - load a firmware via the sysfs fallback mechanism
* @fw_sysfs: firmware sysfs information for the firmware to load
* @timeout: timeout to wait for the load
*
* In charge of constructing a sysfs fallback interface for firmware loading.
**/
static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs, long timeout)
{
int retval = 0;
struct device *f_dev = &fw_sysfs->dev;
struct fw_priv *fw_priv = fw_sysfs->fw_priv;

/* fall back on userspace loading */
if (!fw_priv->data)
fw_priv->is_paged_buf = true;

dev_set_uevent_suppress(f_dev, true);

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);
if (fw_load_abort_all || fw_state_is_aborted(fw_priv)) {
mutex_unlock(&fw_lock);
retval = -EINTR;
goto out;
}
list_add(&fw_priv->pending_list, &pending_fw_head);
mutex_unlock(&fw_lock);

if (fw_priv->opt_flags & FW_OPT_UEVENT) {
fw_priv->need_uevent = true;
dev_set_uevent_suppress(f_dev, false);
dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name);
kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); // 发送 uevent 通知
} else {
timeout = MAX_JIFFY_OFFSET;
}

retval = fw_sysfs_wait_timeout(fw_priv, timeout);
if (retval < 0 && retval != -ENOENT) {
mutex_lock(&fw_lock);
fw_load_abort(fw_sysfs);
mutex_unlock(&fw_lock);
}

if (fw_state_is_aborted(fw_priv)) {
if (retval == -ERESTARTSYS)
retval = -EINTR;
} else if (fw_priv->is_paged_buf && !fw_priv->data)
retval = -ENOMEM;

out:
device_del(f_dev);
err_put_dev:
put_device(f_dev);
return retval;
}

用户空间处理

firmware 路径的配置文件

在 Android 系统中,固件的加载路径通常由 ueventd.rc 文件配置。这个文件位于 /vendor/etc//system/etc/ 目录下。

在 AOSP 代码库中,ueventd.rc 文件的示例可以在以下路径找到:system/core/rootdir/ueventd.rc

其中 firmware_directories 指令用于指定固件的搜索路径。Android 的 init 进程会根据这些路径来查找固件文件。

注意: 中国大陆屏蔽了 Google 的代码托管服务,无法直接访问 cs.android.com 需要通过 VPN 或其他方式访问。

https://cs.android.com/android/platform/superproject/main/+/main:system/core/rootdir/ueventd.rc

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import /vendor/etc/ueventd.rc
import /odm/etc/ueventd.rc

# firmware_directories 用于指定固件的搜索路径
firmware_directories /etc/firmware/ /odm/firmware/ /vendor/firmware/ /firmware/image/
uevent_socket_rcvbuf_size 16M

subsystem graphics
devname uevent_devpath
dirname /dev/graphics

subsystem drm
devname uevent_devpath
dirname /dev/dri

subsystem input
devname uevent_devpath
dirname /dev/input

subsystem sound
devname uevent_devpath
dirname /dev/snd

subsystem dma_heap
devname uevent_devpath
dirname /dev/dma_heap

subsystem vfio
devname uevent_devpath
dirname /dev/vfio

# ueventd can only set permissions on device nodes and their associated
# sysfs attributes, not on arbitrary paths.
#
# format for /dev rules: devname mode uid gid
# format for /sys rules: nodename attr mode uid gid
# shortcut: "mtd@NN" expands to "/dev/mtd/mtdNN"

/dev/null 0666 root root
/dev/zero 0666 root root
/dev/full 0666 root root
/dev/ptmx 0666 root root
/dev/tty 0666 root root
/dev/random 0666 root root
/dev/urandom 0666 root root
# Aside from kernel threads, only prng_seeder needs access to HW RNG
/dev/hw_random 0400 prng_seeder prng_seeder
/dev/ashmem* 0666 root root
/dev/binder 0666 root root
/dev/hwbinder 0666 root root
/dev/vndbinder 0666 root root
/dev/vfio/* 0666 root root

/dev/pmsg0 0222 root log
/dev/dma_heap/system 0444 system system
/dev/dma_heap/system-uncached 0444 system system
/dev/dma_heap/system-secure 0444 system system

# kms driver for drm based gpu
/dev/dri/* 0666 root graphics

# these should not be world writable
/dev/uhid 0660 uhid uhid
/dev/uinput 0660 uhid uhid
/dev/rtc0 0640 system system
/dev/tty0 0660 root system
/dev/graphics/* 0660 root graphics
/dev/input/* 0660 root input
/dev/v4l-touch* 0660 root input
/dev/snd/* 0660 system audio
/dev/bus/usb/* 0660 root usb
/dev/mtp_usb 0660 root mtp
/dev/usb_accessory 0660 root usb
/dev/tun 0660 system vpn
/dev/hidraw* 0660 system system

# CDMA radio interface MUX
/dev/ppp 0660 radio vpn

/dev/kvm 0666 root root
/dev/vhost-vsock 0666 root root

# sysfs properties
/sys/devices/platform/trusty.* trusty_version 0440 root log
/sys/devices/virtual/input/input* enable 0660 root input
/sys/devices/virtual/input/input* poll_delay 0660 root input
/sys/devices/virtual/usb_composite/* enable 0664 root system
/sys/devices/system/cpu/cpu* cpufreq/scaling_max_freq 0664 system system
/sys/devices/system/cpu/cpu* cpufreq/scaling_min_freq 0664 system system
/sys/devices/virtual/misc/uhid/*/leds/* brightness 0664 system system
/sys/devices/virtual/misc/uhid/*/leds/* multi_intensity 0664 system system

ueventd.rc 文件解析

ueventd.rc 文件中定义了多个固件搜索路径,这些路径会被 Android 的 init 进程加载。

system/core/init/main.cpp 中,会调用 ueventd_main() 函数来处理 uevent 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using namespace android::init;

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#elif __has_feature(hwaddress_sanitizer)
__hwasan_set_error_report_callback(AsanReportCallback);
#endif
// Boost prio which will be restored later
setpriority(PRIO_PROCESS, 0, -20);
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv); // 处理 ueventd 的主函数
}
...
return FirstStageMain(argc, argv);
}

system/core/init/ueventd.cpp 中,ueventd_main() 函数中,会调用静态方法 GetConfiguration() 来读取和解析 ueventd.rc 文件。将解析到的内容保存在 ueventd_configuration 对象中。

然后通过 ueventd_configuration 中的成员变量,也就是 ueventd.rc 文件中定义的内容,来创建各种 uevent 处理器。

其中,就创建了用来处理固件加载的 FirmwareHandler 对象。

https://cs.android.com/android/platform/superproject/main/+/main:system/core/init/ueventd.cpp;l=337;drc=61197364367c9e404c7da6900658f1b16c42d0da;bpv=1;bpt=1

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
int ueventd_main(int argc, char** argv) {
/*
* init sets the umask to 077 for forked processes. We need to
* create files with exact permissions, without modification by
* the umask.
*/
umask(000);

android::base::InitLogging(argv, &android::base::KernelLogger);

LOG(INFO) << "ueventd started!";

SelinuxSetupKernelLogging();
SelabelInitialize();

// 维护一个 uevent 处理器的容器
std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;

// 获取 ueventd.rc 配置
auto ueventd_configuration = GetConfiguration();

UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);

// Right after making DeviceHandler, replay all events looking for which
// block device has the boot partition. This lets us make symlinks
// for all of the other partitions on the same disk. Note that by the time
// we get here we know that the boot partition has already shown up (if
// we're looking for it) so just regenerating events is enough to know
// we'll see it.
std::unique_ptr<DeviceHandler> device_handler = std::make_unique<DeviceHandler>(
std::move(ueventd_configuration.dev_permissions),
std::move(ueventd_configuration.sysfs_permissions),
std::move(ueventd_configuration.drivers), std::move(ueventd_configuration.subsystems),
android::fs_mgr::GetBootDevices(), android::fs_mgr::GetBootPartUuid(), true);
uevent_listener.RegenerateUevents([&](const Uevent& uevent) -> ListenerAction {
bool uuid_check_done = device_handler->CheckUeventForBootPartUuid(uevent);
return uuid_check_done ? ListenerAction::kStop : ListenerAction::kContinue;
});

uevent_handlers.emplace_back(std::move(device_handler));

// 创建固件处理器,这里就传入了 ueventd_configuration 中的固件目录 firmware_directories
uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(
std::move(ueventd_configuration.firmware_directories),
std::move(ueventd_configuration.external_firmware_handlers)));
...

GetConfiguration() 函数中我们可以看到有哪些 ueventd.rc 会被解析。

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
static UeventdConfiguration GetConfiguration() {
if (IsMicrodroid()) {
return ParseConfig({"/system/etc/ueventd.rc", "/vendor/etc/ueventd.rc"});
}

auto hardware = android::base::GetProperty("ro.hardware", "");

struct LegacyPathInfo {
std::string legacy_path;
std::string preferred;
};
std::vector<LegacyPathInfo> legacy_paths{
{"/vendor/ueventd.rc", "/vendor/etc/ueventd.rc"},
{"/odm/ueventd.rc", "/odm/etc/ueventd.rc"},
{"/ueventd." + hardware + ".rc", "another ueventd.rc file"}};

std::vector<std::string> canonical{"/system/etc/ueventd.rc"};

if (android::base::GetIntProperty("ro.product.first_api_level", 10000) < __ANDROID_API_T__) {
// TODO: Remove these legacy paths once Android S is no longer supported.
for (const auto& info : legacy_paths) {
canonical.push_back(info.legacy_path);
}
} else {
// Warn if newer device is using legacy paths.
for (const auto& info : legacy_paths) {
if (access(info.legacy_path.c_str(), F_OK) == 0) {
LOG(FATAL_WITHOUT_ABORT)
<< "Legacy ueventd configuration file detected and will not be parsed: "
<< info.legacy_path << ". Please move your configuration to "
<< info.preferred << " instead.";
}
}
}

return ParseConfig(canonical);
}

FirmwareHandler

FirmwareHandler 类是处理固件加载的核心类,它继承自 UeventHandler,并实现了 HandleUevent() 方法来处理接收到的 uevent。

它的构造函数接收固件目录和外部固件处理器的列表,并将这些信息存储在成员变量 firmware_directories_ 中。

https://cs.android.com/android/platform/superproject/main/+/main:system/core/init/firmware_handler.h;l=45?q=FirmwareHandler&ss=android%2Fplatform%2Fsuperproject%2Fmain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class FirmwareHandler : public UeventHandler {
public:
FirmwareHandler(std::vector<std::string> firmware_directories,
std::vector<ExternalFirmwareHandler> external_firmware_handlers);
virtual ~FirmwareHandler() = default;

void HandleUevent(const Uevent& uevent) override;

private:
friend void FirmwareTestWithExternalHandler(const std::string& test_name,
bool expect_new_firmware);

std::string GetFirmwarePath(const Uevent& uevent) const;
void ProcessFirmwareEvent(const std::string& path, const std::string& firmware) const;
bool ForEachFirmwareDirectory(std::function<bool(const std::string&)> handler) const;

std::vector<std::string> firmware_directories_;
std::vector<ExternalFirmwareHandler> external_firmware_handlers_;
};

HandleUevent() 方法中,FirmwareHandler 类会检查接收到的 uevent 是否与固件相关,并在需要时创建一个子进程来处理固件加载事件。

在子进程中,它会调用 GetFirmwarePath() 方法来获取固件的完整路径,这个 GetFirmwarePath() 方法实际上是根据 uevent 的信息中拿到固件名称。

然后在 ProcessFirmwareEvent() 方法中,会依次遍历 firmware_directories_ 中的路径,尝试加载固件文件。如果找到了固件文件,就会将其写入到 /sys/<path>/data 文件中,并将 /sys/<path>/loading 文件的内容设置为 0,表示固件加载完成。

如果没有找到固件文件,则会打印错误信息,并将 /sys/<path>/loading 文件的内容设置为 -1,表示加载失败。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void FirmwareHandler::HandleUevent(const Uevent& uevent) {
if (uevent.subsystem != "firmware" || uevent.action != "add") return;

// Loading the firmware in a child means we can do that in parallel...
auto pid = fork();
if (pid == -1) {
PLOG(ERROR) << "could not fork to process firmware event for " << uevent.firmware;
}
if (pid == 0) {
Timer t;
auto firmware = GetFirmwarePath(uevent);
ProcessFirmwareEvent(uevent.path, firmware);
LOG(INFO) << "loading " << uevent.path << " took " << t;
_exit(EXIT_SUCCESS);
}
}

static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
size_t fw_size, int loading_fd, int data_fd) {
// Start transfer.
WriteFully(loading_fd, "1", 1);

// Copy the firmware.
int rc = sendfile(data_fd, fw_fd, nullptr, fw_size);
if (rc == -1) {
PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << firmware << "' }";
}

// Tell the firmware whether to abort or commit.
const char* response = (rc != -1) ? "0" : "-1";
WriteFully(loading_fd, response, strlen(response));
}

void FirmwareHandler::ProcessFirmwareEvent(const std::string& path,
const std::string& firmware) const {
std::string root = "/sys" + path;
std::string loading = root + "/loading";
std::string data = root + "/data";

unique_fd loading_fd(open(loading.c_str(), O_WRONLY | O_CLOEXEC));
if (loading_fd == -1) {
PLOG(ERROR) << "couldn't open firmware loading fd for " << firmware;
return;
}

unique_fd data_fd(open(data.c_str(), O_WRONLY | O_CLOEXEC));
if (data_fd == -1) {
PLOG(ERROR) << "couldn't open firmware data fd for " << firmware;
return;
}

std::vector<std::string> attempted_paths_and_errors;
auto TryLoadFirmware = [&](const std::string& firmware_directory) {
std::string file = firmware_directory + firmware;
unique_fd fw_fd(open(file.c_str(), O_RDONLY | O_CLOEXEC));
if (fw_fd == -1) {
attempted_paths_and_errors.emplace_back("firmware: attempted " + file +
", open failed: " + strerror(errno));
return false;
}
struct stat sb;
if (fstat(fw_fd.get(), &sb) == -1) {
attempted_paths_and_errors.emplace_back("firmware: attempted " + file +
", fstat failed: " + strerror(errno));
return false;
}
LOG(INFO) << "found " << file << " for " << path;

// 这个 LoadFirmware 函数会将固件写入到 data_fd 中
LoadFirmware(firmware, root, fw_fd.get(), sb.st_size, loading_fd.get(), data_fd.get());
return true;
};

int booting = IsBooting();
try_loading_again:
attempted_paths_and_errors.clear();
if (ForEachFirmwareDirectory(TryLoadFirmware)) {
return;
}

if (booting) {
// If we're not fully booted, we may be missing
// filesystems needed for firmware, wait and retry.
std::this_thread::sleep_for(100ms);
booting = IsBooting();
goto try_loading_again;
}

LOG(ERROR) << "firmware: could not find firmware for " << firmware;
for (const auto& message : attempted_paths_and_errors) {
LOG(ERROR) << message;
}

// Write "-1" as our response to the kernel's firmware request, since we have nothing for it.
write(loading_fd.get(), "-1", 2);
}

This article analyzes the firmware loading paths and process in Android systems, helping you understand how drivers obtain the firmware files they need.

The firmware loading flow is mainly described by the diagram above; let’s walk through it in detail with the source code below.

Driver Invocation

When a driver needs to load firmware, it calls functions in the request_firmware() family, which are defined in the Linux kernel’s include/linux/firmware.h. Here is an example from Qualcomm’s CNSS platform driver:

https://github.com/WANG-Guangxin/wlan-driver/blob/5113495b16420b49004c444715d2daae2066e7dc/platform/cnss2/main.c#L4032

1
2
3
4
5
6
7
8
9
10
11
int cnss_request_firmware_direct(struct cnss_plat_data *plat_priv,
const struct firmware **fw_entry,
const char *filename)
{
if (IS_ENABLED(CONFIG_CNSS_REQ_FW_DIRECT))
return request_firmware_direct(fw_entry, filename,
&plat_priv->plat_dev->dev);
else
return firmware_request_nowarn(fw_entry, filename,
&plat_priv->plat_dev->dev);
}

Kernel Handling

The request_firmware_direct() and firmware_request_nowarn() functions that the CNSS driver may use are implemented in the kernel’s drivers/base/firmware_loader/main.c.

request_firmware_direct

request_firmware_direct() loads firmware directly from the filesystem and does not fall back to usermode udev or Android’s init process.

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/main.c#L1002

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* request_firmware_direct() - load firmware directly without usermode helper
* @firmware_p: pointer to firmware image
* @name: name of firmware file
* @device: device for which firmware is being loaded
*
* This function works pretty much like request_firmware(), but this doesn't
* fall back to usermode helper even if the firmware couldn't be loaded
* directly from fs. Hence it's useful for loading optional firmwares, which
* aren't always present, without extra long timeouts of udev.
**/
int request_firmware_direct(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, 0,
FW_OPT_UEVENT | FW_OPT_NO_WARN |
FW_OPT_NOFALLBACK_SYSFS);
module_put(THIS_MODULE);
return ret;
}
EXPORT_SYMBOL_GPL(request_firmware_direct);

firmware_request_nowarn

firmware_request_nowarn() also tries to load the firmware from the filesystem first, and falls back to the sysfs fallback mechanism if that fails — but it does not print warning messages. The so-called sysfs fallback mechanism works by sending a uevent to the init process in userspace to search other possible firmware paths.

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/main.c#L977

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
/**
* firmware_request_nowarn() - request for an optional fw module
* @firmware: pointer to firmware image
* @name: name of firmware file
* @device: device for which firmware is being loaded
*
* This function is similar in behaviour to request_firmware(), except it
* doesn't produce warning messages when the file is not found. The sysfs
* fallback mechanism is enabled if direct filesystem lookup fails. However,
* failures to find the firmware file with it are still suppressed. It is
* therefore up to the driver to check for the return value of this call and to
* decide when to inform the users of errors.
**/
int firmware_request_nowarn(const struct firmware **firmware, const char *name,
struct device *device)
{
int ret;

/* Need to pin this module until return */
__module_get(THIS_MODULE);
ret = _request_firmware(firmware, name, device, NULL, 0, 0,
FW_OPT_UEVENT | FW_OPT_NO_WARN);
module_put(THIS_MODULE);
return ret;
}
EXPORT_SYMBOL_GPL(firmware_request_nowarn);

firmware_fallback_sysfs

Both request_firmware_direct() and firmware_request_nowarn() call _request_firmware() to handle the actual firmware loading logic.

Inside _request_firmware(), we can see there are many fallback mechanisms; here we focus on the sysfs fallback mechanism.

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/main.c#L906

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/* called from request_firmware() and request_firmware_work_func() */
static int
_request_firmware(const struct firmware **firmware_p, const char *name,
struct device *device, void *buf, size_t size,
size_t offset, u32 opt_flags)
{
struct firmware *fw = NULL;
struct cred *kern_cred = NULL;
const struct cred *old_cred;
bool nondirect = false;
int ret;

if (!firmware_p)
return -EINVAL;

if (!name || name[0] == '\0') {
ret = -EINVAL;
goto out;
}


/*
* Reject firmware file names with ".." path components.
* There are drivers that construct firmware file names from
* device-supplied strings, and we don't want some device to be
* able to tell us "I would like to be sent my firmware from
* ../../../etc/shadow, please".
*
* This intentionally only looks at the firmware name, not at
* the firmware base directory or at symlink contents.
*/
if (name_contains_dotdot(name)) {
dev_warn(device,
"Firmware load for '%s' refused, path contains '..' component\n",
name);
ret = -EINVAL;
goto out;
}

ret = _request_firmware_prepare(&fw, name, device, buf, size,
offset, opt_flags);
if (ret <= 0) /* error or already assigned */
goto out;

/*
* We are about to try to access the firmware file. Because we may have been
* called by a driver when serving an unrelated request from userland, we use
* the kernel credentials to read the file.
*/
kern_cred = prepare_kernel_cred(&init_task);
if (!kern_cred) {
ret = -ENOMEM;
goto out;
}
old_cred = override_creds(kern_cred);

ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL);

/* Only full reads can support decompression, platform, and sysfs. */
if (!(opt_flags & FW_OPT_PARTIAL))
nondirect = true;

#ifdef CONFIG_FW_LOADER_COMPRESS_ZSTD
if (ret == -ENOENT && nondirect)
ret = fw_get_filesystem_firmware(device, fw->priv, ".zst",
fw_decompress_zstd);
#endif
#ifdef CONFIG_FW_LOADER_COMPRESS_XZ
if (ret == -ENOENT && nondirect)
ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
fw_decompress_xz);
#endif
if (ret == -ENOENT && nondirect)
ret = firmware_fallback_platform(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 (nondirect)
ret = firmware_fallback_sysfs(fw, name, device,
opt_flags, ret);
} else
ret = assign_fw(fw, device);

revert_creds(old_cred);
put_cred(kern_cred);

out:
if (ret < 0) {
fw_abort_batch_reqs(fw);
release_firmware(fw);
fw = NULL;
} else {
fw_log_firmware_info(fw, name, device);
}

*firmware_p = fw;
return ret;
}

The firmware_fallback_sysfs() function is implemented in drivers/base/firmware_loader/fallback.c, a file of only about 200 lines that implements the logic for sending uevents to userspace.

https://github.com/torvalds/linux/blob/260f6f4fda93c8485c8037865c941b42b9cba5d2/drivers/base/firmware_loader/fallback.c#L224

The main call chain is as follows:

firmware_fallback_sysfs => fw_load_from_user_helper => fw_load_sysfs_fallback

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
62
/**
* fw_load_sysfs_fallback() - load a firmware via the sysfs fallback mechanism
* @fw_sysfs: firmware sysfs information for the firmware to load
* @timeout: timeout to wait for the load
*
* In charge of constructing a sysfs fallback interface for firmware loading.
**/
static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs, long timeout)
{
int retval = 0;
struct device *f_dev = &fw_sysfs->dev;
struct fw_priv *fw_priv = fw_sysfs->fw_priv;

/* fall back on userspace loading */
if (!fw_priv->data)
fw_priv->is_paged_buf = true;

dev_set_uevent_suppress(f_dev, true);

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);
if (fw_load_abort_all || fw_state_is_aborted(fw_priv)) {
mutex_unlock(&fw_lock);
retval = -EINTR;
goto out;
}
list_add(&fw_priv->pending_list, &pending_fw_head);
mutex_unlock(&fw_lock);

if (fw_priv->opt_flags & FW_OPT_UEVENT) {
fw_priv->need_uevent = true;
dev_set_uevent_suppress(f_dev, false);
dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_name);
kobject_uevent(&fw_sysfs->dev.kobj, KOBJ_ADD); // Send uevent notification
} else {
timeout = MAX_JIFFY_OFFSET;
}

retval = fw_sysfs_wait_timeout(fw_priv, timeout);
if (retval < 0 && retval != -ENOENT) {
mutex_lock(&fw_lock);
fw_load_abort(fw_sysfs);
mutex_unlock(&fw_lock);
}

if (fw_state_is_aborted(fw_priv)) {
if (retval == -ERESTARTSYS)
retval = -EINTR;
} else if (fw_priv->is_paged_buf && !fw_priv->data)
retval = -ENOMEM;

out:
device_del(f_dev);
err_put_dev:
put_device(f_dev);
return retval;
}

Userspace Handling

The Configuration File for Firmware Paths

On Android systems, the firmware loading paths are usually configured by the ueventd.rc file, located in /vendor/etc/ or /system/etc/.

In the AOSP source tree, an example ueventd.rc can be found at system/core/rootdir/ueventd.rc.

The firmware_directories directive specifies the firmware search paths. Android’s init process searches for firmware files in these directories.

Note: Mainland China blocks Google’s code hosting services, so cs.android.com cannot be accessed directly — you need a VPN or other workarounds.

https://cs.android.com/android/platform/superproject/main/+/main:system/core/rootdir/ueventd.rc

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import /vendor/etc/ueventd.rc
import /odm/etc/ueventd.rc

# firmware_directories specifies the firmware search paths
firmware_directories /etc/firmware/ /odm/firmware/ /vendor/firmware/ /firmware/image/
uevent_socket_rcvbuf_size 16M

subsystem graphics
devname uevent_devpath
dirname /dev/graphics

subsystem drm
devname uevent_devpath
dirname /dev/dri

subsystem input
devname uevent_devpath
dirname /dev/input

subsystem sound
devname uevent_devpath
dirname /dev/snd

subsystem dma_heap
devname uevent_devpath
dirname /dev/dma_heap

subsystem vfio
devname uevent_devpath
dirname /dev/vfio

# ueventd can only set permissions on device nodes and their associated
# sysfs attributes, not on arbitrary paths.
#
# format for /dev rules: devname mode uid gid
# format for /sys rules: nodename attr mode uid gid
# shortcut: "mtd@NN" expands to "/dev/mtd/mtdNN"

/dev/null 0666 root root
/dev/zero 0666 root root
/dev/full 0666 root root
/dev/ptmx 0666 root root
/dev/tty 0666 root root
/dev/random 0666 root root
/dev/urandom 0666 root root
# Aside from kernel threads, only prng_seeder needs access to HW RNG
/dev/hw_random 0400 prng_seeder prng_seeder
/dev/ashmem* 0666 root root
/dev/binder 0666 root root
/dev/hwbinder 0666 root root
/dev/vndbinder 0666 root root
/dev/vfio/* 0666 root root

/dev/pmsg0 0222 root log
/dev/dma_heap/system 0444 system system
/dev/dma_heap/system-uncached 0444 system system
/dev/dma_heap/system-secure 0444 system system

# kms driver for drm based gpu
/dev/dri/* 0666 root graphics

# these should not be world writable
/dev/uhid 0660 uhid uhid
/dev/uinput 0660 uhid uhid
/dev/rtc0 0640 system system
/dev/tty0 0660 root system
/dev/graphics/* 0660 root graphics
/dev/input/* 0660 root input
/dev/v4l-touch* 0660 root input
/dev/snd/* 0660 system audio
/dev/bus/usb/* 0660 root usb
/dev/mtp_usb 0660 root mtp
/dev/usb_accessory 0660 root usb
/dev/tun 0660 system vpn
/dev/hidraw* 0660 system system

# CDMA radio interface MUX
/dev/ppp 0660 radio vpn

/dev/kvm 0666 root root
/dev/vhost-vsock 0666 root root

# sysfs properties
/sys/devices/platform/trusty.* trusty_version 0440 root log
/sys/devices/virtual/input/input* enable 0660 root input
/sys/devices/virtual/input/input* poll_delay 0660 root input
/sys/devices/virtual/usb_composite/* enable 0664 root system
/sys/devices/system/cpu/cpu* cpufreq/scaling_max_freq 0664 system system
/sys/devices/system/cpu/cpu* cpufreq/scaling_min_freq 0664 system system
/sys/devices/virtual/misc/uhid/*/leds/* brightness 0664 system system
/sys/devices/virtual/misc/uhid/*/leds/* multi_intensity 0664 system system

Parsing the ueventd.rc File

The ueventd.rc file defines several firmware search paths, which are loaded by Android’s init process.

In system/core/init/main.cpp, the ueventd_main() function is called to handle uevents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using namespace android::init;

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#elif __has_feature(hwaddress_sanitizer)
__hwasan_set_error_report_callback(AsanReportCallback);
#endif
// Boost prio which will be restored later
setpriority(PRIO_PROCESS, 0, -20);
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv); // Main function handling ueventd
}
...
return FirstStageMain(argc, argv);
}

In system/core/init/ueventd.cpp, the ueventd_main() function calls the static method GetConfiguration() to read and parse the ueventd.rc file, storing the parsed content in a ueventd_configuration object.

Then, based on the member variables of ueventd_configuration — that is, the content defined in ueventd.rc — various uevent handlers are created.

Among them, a FirmwareHandler object for handling firmware loading is created.

https://cs.android.com/android/platform/superproject/main/+/main:system/core/init/ueventd.cpp;l=337;drc=61197364367c9e404c7da6900658f1b16c42d0da;bpv=1;bpt=1

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
int ueventd_main(int argc, char** argv) {
/*
* init sets the umask to 077 for forked processes. We need to
* create files with exact permissions, without modification by
* the umask.
*/
umask(000);

android::base::InitLogging(argv, &android::base::KernelLogger);

LOG(INFO) << "ueventd started!";

SelinuxSetupKernelLogging();
SelabelInitialize();

// Maintains a container of uevent handlers
std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;

// Obtain the ueventd.rc configuration
auto ueventd_configuration = GetConfiguration();

UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);

// Right after making DeviceHandler, replay all events looking for which
// block device has the boot partition. This lets us make symlinks
// for all of the other partitions on the same disk. Note that by the time
// we get here we know that the boot partition has already shown up (if
// we're looking for it) so just regenerating events is enough to know
// we'll see it.
std::unique_ptr<DeviceHandler> device_handler = std::make_unique<DeviceHandler>(
std::move(ueventd_configuration.dev_permissions),
std::move(ueventd_configuration.sysfs_permissions),
std::move(ueventd_configuration.drivers), std::move(ueventd_configuration.subsystems),
android::fs_mgr::GetBootDevices(), android::fs_mgr::GetBootPartUuid(), true);
uevent_listener.RegenerateUevents([&](const Uevent& uevent) -> ListenerAction {
bool uuid_check_done = device_handler->CheckUeventForBootPartUuid(uevent);
return uuid_check_done ? ListenerAction::kStop : ListenerAction::kContinue;
});

uevent_handlers.emplace_back(std::move(device_handler));

// Create the firmware handler; the firmware directories (firmware_directories)
// from ueventd_configuration are passed in here
uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(
std::move(ueventd_configuration.firmware_directories),
std::move(ueventd_configuration.external_firmware_handlers)));
...

From the GetConfiguration() function we can see which ueventd.rc files are parsed.

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
static UeventdConfiguration GetConfiguration() {
if (IsMicrodroid()) {
return ParseConfig({"/system/etc/ueventd.rc", "/vendor/etc/ueventd.rc"});
}

auto hardware = android::base::GetProperty("ro.hardware", "");

struct LegacyPathInfo {
std::string legacy_path;
std::string preferred;
};
std::vector<LegacyPathInfo> legacy_paths{
{"/vendor/ueventd.rc", "/vendor/etc/ueventd.rc"},
{"/odm/ueventd.rc", "/odm/etc/ueventd.rc"},
{"/ueventd." + hardware + ".rc", "another ueventd.rc file"}};

std::vector<std::string> canonical{"/system/etc/ueventd.rc"};

if (android::base::GetIntProperty("ro.product.first_api_level", 10000) < __ANDROID_API_T__) {
// TODO: Remove these legacy paths once Android S is no longer supported.
for (const auto& info : legacy_paths) {
canonical.push_back(info.legacy_path);
}
} else {
// Warn if newer device is using legacy paths.
for (const auto& info : legacy_paths) {
if (access(info.legacy_path.c_str(), F_OK) == 0) {
LOG(FATAL_WITHOUT_ABORT)
<< "Legacy ueventd configuration file detected and will not be parsed: "
<< info.legacy_path << ". Please move your configuration to "
<< info.preferred << " instead.";
}
}
}

return ParseConfig(canonical);
}

FirmwareHandler

FirmwareHandler is the core class for handling firmware loading. It inherits from UeventHandler and implements the HandleUevent() method to process received uevents.

Its constructor receives the list of firmware directories and external firmware handlers, and stores them in the member variable firmware_directories_.

https://cs.android.com/android/platform/superproject/main/+/main:system/core/init/firmware_handler.h;l=45?q=FirmwareHandler&ss=android%2Fplatform%2Fsuperproject%2Fmain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class FirmwareHandler : public UeventHandler {
public:
FirmwareHandler(std::vector<std::string> firmware_directories,
std::vector<ExternalFirmwareHandler> external_firmware_handlers);
virtual ~FirmwareHandler() = default;

void HandleUevent(const Uevent& uevent) override;

private:
friend void FirmwareTestWithExternalHandler(const std::string& test_name,
bool expect_new_firmware);

std::string GetFirmwarePath(const Uevent& uevent) const;
void ProcessFirmwareEvent(const std::string& path, const std::string& firmware) const;
bool ForEachFirmwareDirectory(std::function<bool(const std::string&)> handler) const;

std::vector<std::string> firmware_directories_;
std::vector<ExternalFirmwareHandler> external_firmware_handlers_;
};

In the HandleUevent() method, FirmwareHandler checks whether the received uevent is firmware-related, and forks a child process to handle the firmware loading event when needed.

In the child process, it calls GetFirmwarePath() to obtain the full firmware path; this GetFirmwarePath() method actually derives the firmware name from the uevent information.

Then, in ProcessFirmwareEvent(), it iterates over the paths in firmware_directories_ in order, trying to load the firmware file. If a firmware file is found, it writes it to the /sys/<path>/data file and sets the content of /sys/<path>/loading to 0, indicating that the firmware load is complete.

If no firmware file is found, it prints an error message and sets the content of /sys/<path>/loading to -1, indicating that the load failed.

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void FirmwareHandler::HandleUevent(const Uevent& uevent) {
if (uevent.subsystem != "firmware" || uevent.action != "add") return;

// Loading the firmware in a child means we can do that in parallel...
auto pid = fork();
if (pid == -1) {
PLOG(ERROR) << "could not fork to process firmware event for " << uevent.firmware;
}
if (pid == 0) {
Timer t;
auto firmware = GetFirmwarePath(uevent);
ProcessFirmwareEvent(uevent.path, firmware);
LOG(INFO) << "loading " << uevent.path << " took " << t;
_exit(EXIT_SUCCESS);
}
}

static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
size_t fw_size, int loading_fd, int data_fd) {
// Start transfer.
WriteFully(loading_fd, "1", 1);

// Copy the firmware.
int rc = sendfile(data_fd, fw_fd, nullptr, fw_size);
if (rc == -1) {
PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << firmware << "' }";
}

// Tell the firmware whether to abort or commit.
const char* response = (rc != -1) ? "0" : "-1";
WriteFully(loading_fd, response, strlen(response));
}

void FirmwareHandler::ProcessFirmwareEvent(const std::string& path,
const std::string& firmware) const {
std::string root = "/sys" + path;
std::string loading = root + "/loading";
std::string data = root + "/data";

unique_fd loading_fd(open(loading.c_str(), O_WRONLY | O_CLOEXEC));
if (loading_fd == -1) {
PLOG(ERROR) << "couldn't open firmware loading fd for " << firmware;
return;
}

unique_fd data_fd(open(data.c_str(), O_WRONLY | O_CLOEXEC));
if (data_fd == -1) {
PLOG(ERROR) << "couldn't open firmware data fd for " << firmware;
return;
}

std::vector<std::string> attempted_paths_and_errors;
auto TryLoadFirmware = [&](const std::string& firmware_directory) {
std::string file = firmware_directory + firmware;
unique_fd fw_fd(open(file.c_str(), O_RDONLY | O_CLOEXEC));
if (fw_fd == -1) {
attempted_paths_and_errors.emplace_back("firmware: attempted " + file +
", open failed: " + strerror(errno));
return false;
}
struct stat sb;
if (fstat(fw_fd.get(), &sb) == -1) {
attempted_paths_and_errors.emplace_back("firmware: attempted " + file +
", fstat failed: " + strerror(errno));
return false;
}
LOG(INFO) << "found " << file << " for " << path;

// This LoadFirmware function writes the firmware into data_fd
LoadFirmware(firmware, root, fw_fd.get(), sb.st_size, loading_fd.get(), data_fd.get());
return true;
};

int booting = IsBooting();
try_loading_again:
attempted_paths_and_errors.clear();
if (ForEachFirmwareDirectory(TryLoadFirmware)) {
return;
}

if (booting) {
// If we're not fully booted, we may be missing
// filesystems needed for firmware, wait and retry.
std::this_thread::sleep_for(100ms);
booting = IsBooting();
goto try_loading_again;
}

LOG(ERROR) << "firmware: could not find firmware for " << firmware;
for (const auto& message : attempted_paths_and_errors) {
LOG(ERROR) << message;
}

// Write "-1" as our response to the kernel's firmware request, since we have nothing for it.
write(loading_fd.get(), "-1", 2);
}