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 的处理是类似的。