Wednesday, June 24, 2009

一千零一夜之 Kernel and Firmware

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

Driver 需要 firmware 時會呼叫 request_firmware,這個函數會在 /sys 底下產生適當的裝置讓 userspace 上傳 firmware 給 driver。它會等到 firmware 上傳完成 (或發生錯誤) 才回傳。除了建立適當的裝置,它也會送一個 uevent,內容類似

KERNEL[1245809267.000297] add      /devices/pci0000:00/0000:00:1c.2/0000:02:00.0/firmware/0000:02:00.0 (firmware)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1c.2/0000:02:00.0/firmware/0000:02:00.0
SUBSYSTEM=firmware
FIRMWARE=iwlwifi-3945-2.ucode
SEQNUM=1226


uevent 事件通常是 udev 在處理,在遇到 firmware 事件,也就是 SUBSYSTEM 是 firmware 的事件時,udev 預設會執行 /lib/udev/firmware.agent (在 Debian 上) 來上傳 firmware。簡化過的 firmware.agent 其實只做了

echo 1 > /sys/$DEVPATH/loading # 準備上傳
cat $firmwaredir/$FIRMWARE > /sys/$DEVPATH/data
echo 0 > /sys/$DEVPATH/loading # 上傳完畢


注意到上面大寫的變數內容都是 uevent 提供,像是 $DEVPATH 就是 kernel 剛產生用來接受 firmware 上傳的虛擬裝置。

這是 firmware 擺在 filesystem 上的情形。當 CONFIG_FIRMWARE_IN_KERNEL 打開時,如果 driver 被編進 kernel,firmware 也會一起被編進去。在 request_firmware 中有一段

for (builtin = __start_builtin_fw; builtin != __end_builtin_fw;
builtin++) {
if (strcmp(name, builtin->name))
continue;
dev_info(device, "firmware: using built-in firmware %s\n",
name);
firmware->size = builtin->size;
firmware->data = builtin->data;
return 0;
}


會先檢查 driver 要求的 firmware 是否有內建。這邊的 __start_builtin_fw 與 __end_builtin_fw 是 link time 產生的 symbol,所以不會在任何 C code 找到它們的定義。在 vmlinux.lds (或 include/asm-generic/vmlinux.lds.h) 可以看到,它們的值會是所有 .builtin_fw section 內容的開始位址與結束位址。

.builtin_fw 的內容要再找到 firmware/Makefile。在那份 Makefile 可以看到 kbuild 會為每一個要內建的 firmware 產生一份 assembly code。這些 assembly code 編出來的 object code 會跟 kernel link 在一起,達成 firmware 內建的目的。從 assembly code 可以看出,這些 object code 的內容會是 firmware 本身,以及一個放在 .builtin_fw section 的資料結構,給出 firmware 的檔名、位置、還有大小。

這邊看到內建 firmware 的做法其實是 kernel 常見的手法之一。寫 driver 最開始會加的 module_init(entry_point) 是另一個例子。這些 macro 會產生 initcall,而所有的 initcall 在 link time 也是用類似的方式被收集。在電腦剛開機進 userspace 前它們會依照被收集的順序依序被執行。這邊可以看到一個要注意的小地方。在每個 subsystem (rtc, scsi, ...) 的 Makefile 中,核心的部份必須放在 driver 之前。

No comments: