Linux 内核 firmware 加载过程¶
@2013-11-26 新版功能: 创建
Broadcom 的一些网卡需要加载一些 firmware 才能正常工作,相应的内核信息如下:
bnx2: Can't load firmware file "bnx2/bnx2-mips-09-6.2.1b.fw"
借这个机会对 Linux 内核加载所需的 firmware 的过程做了个了解。
首先参考内核文档 firmware_class/README 里的说明:
1), kernel(driver):
- calls request_firmware(&fw_entry, $FIRMWARE, device)
- kernel searchs the fimware image with name $FIRMWARE directly
in the below search path of root filesystem:
User customized search path by module parameter 'path'[1]
"/lib/firmware/updates/" UTS_RELEASE,
"/lib/firmware/updates",
"/lib/firmware/" UTS_RELEASE,
"/lib/firmware"
- If found, goto 7), else goto 2)
[1], the 'path' is a string parameter which length should be less
than 256, user should pass 'firmware_class.path=$CUSTOMIZED_PATH'
if firmware_class is built in kernel(the general situation)
2), userspace:
- /sys/class/firmware/xxx/{loading,data} appear.
- hotplug gets called with a firmware identifier in $FIRMWARE
and the usual hotplug environment.
- hotplug: echo 1 > /sys/class/firmware/xxx/loading
3), kernel: Discard any previous partial load.
4), userspace:
- hotplug: cat appropriate_firmware_image > \
/sys/class/firmware/xxx/data
5), kernel: grows a buffer in PAGE_SIZE increments to hold the image as it
comes in.
6), userspace:
- hotplug: echo 0 > /sys/class/firmware/xxx/loading
7), kernel: request_firmware() returns and the driver has the firmware
image in fw_entry->{data,size}. If something went wrong
request_firmware() returns non-zero and fw_entry is set to
NULL.
8), kernel(driver): Driver code calls release_firmware(fw_entry) releasing
the firmware image and any related resource.
从这里描述的过程我们能看到,内核本身并不加载任何 firmware,而只是发一个通知 “我需要名字为 xxx 的 firmware” 给用户空间,然后等待用户空间的程序将该 firmware 的镜像文件推送给内核。这里用户空间的进程一般对应于 udev 或其他类似功能的程序。
以 udev 为例,在 Ubuntu 12.04 上,
$ grep firmware /lib/udev/rules.d/*
/lib/udev/rules.d/50-firmware.rules:# firmware-class requests, copies files into the kernel
/lib/udev/rules.d/50-firmware.rules:SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware --firmware=$env{FIRMWARE} --devpath=$env{DEVPATH}"
可以看到当内核请求 firmware, udev 会调用 firmware 程序处理。接下来我们查看 firmware 的源代码(使用 apt-get source udev 安装):
$ vi udev-175/extras/firmware/firmware.c
...
82 static const char *searchpath[] = { FIRMWARE_PATH };
...
131 /* lookup firmware file */
132 uname(&kernel);
133 for (i = 0; i < ARRAY_SIZE(searchpath); i++) {
134 util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], kernel.release, "/", firmware, NULL);
135 dbg(udev, "trying %s\n", fwpath);
136 fwfile = fopen(fwpath, "r");
137 if (fwfile != NULL)
138 break;-
139
140 util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], firmware, NULL);
141 dbg(udev, "trying %s\n", fwpath);
142 fwfile = fopen(fwpath, "r");
143 if (fwfile != NULL)
144 break;
145 }
...
其中 searchpath 会在 configure 时被设置为 /lib/firmware/updates/ 和 /lib/firmware/ 这两个路径。程序会分别在这两个目录下搜索 $(uname -r)/$FIRMWARE 和 $FIRMWARE 文件。假设内核版本(uname -r的输出)为 3.10.20,请求的 firmware 为 bnx2/bnx2-mips-09-6.2.1b.fw,则 firmware 程序会搜索如下 4 个文件, 找到即退出搜索循环,并将该文件的内容传递到内核。 如果都没有找到就会遇到本文最开始的错误。
/lib/firmware/updates/3.10.20/bnx2/bnx2-mips-09-6.2.1b.fw
/lib/firmware/updates/bnx2/bnx2-mips-09-6.2.1b.fw
/lib/firmware/3.10.20/bnx2/bnx2-mips-09-6.2.1b.fw
/lib/firmware/bnx2/bnx2-mips-09-6.2.1b.fw
其他发行版本的处理过程都是类似的,找到 udev 对应的规则文件,该文件里会指定由 哪个程序来处理。例如,对 Gentoo:
$ grep firmware /lib/udev/rules.d/*
/lib/udev/rules.d/50-firmware.rules:SUBSYSTEM=="firmware", ACTION=="add", RUN{builtin}="firmware"
Gentoo 里 firmware 的加载时由 udev 内置的 firmware 命令来处理。分析其源代码后 确认其处理逻辑和 Ubuntu 的处理是类似的。