本文将解析 Android 系统中固件加载的路径和流程,帮助理解驱动程序如何获取所需的固件文件。
固件的加载流程主要由上图所描述,下面我们结合代码来详细说明。
驱动程序调用
驱动程序在需要加载固件时,会调用 request_firmware()
系列函数,这些函数被定义在 Linux 内核的 include/linux/firmware.h
中,以下是高通 CNSS 平台驱动的示例代码:
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 进程。
/**
* 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 进程,来查找其他可能的固件路径。
/**
* 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 机制。
/* 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 的逻辑。
主要的函数调用过程如下:
firmware_fallback_sysfs
=> fw_load_from_user_helper
=> fw_load_sysfs_fallback
/**
* 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
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 。
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
对象。
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
会被解析。
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_
中。
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,表示加载失败。
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);
}