轉自:http://blog.chinaunix.net/uid-22028680-id-3157922.html
1、request_firmware在內核使用,需要文件系統支持,就是說,啟動的時候如果在驅動里面的probe函數調用 request_firmware ,那么系統將等待30s左右,因為文件系統還沒有掛載,當然找不到固件了,所以最好在中斷里面啟動tasklet,然后request_firmware 。如果不想等待,就用request_firmware_nowait,好像是這樣寫的。
2、那么用戶層怎么用?
實際上這個分x86和嵌入式,比如arm,平台。x86的用到了udev,
比如你要請求固件 fw.hex,那么必須在文件系統中導出環境變量,比如 export FIRMWARE=/lib/firmware,而且目錄/lib/firmware不能少,因為busybox要用到。然后把固件fw.hex放到 /lib/firmware目錄下即可。內核request_firmware的時候,busybox就知道去FIRMWARE找了。
3、對linux來講,所謂的固件什么也不是,他只是按fopen()返回二進制文件給你,看看busybox的處理就知道了。所以你的文件隨便定 義,比如一個mp3文件,你也可以稱為固件:request_firmware(“xxx.mp3″),那么你文件系統里面也要有這個xxx.mp3文 件,只不過系統給你返回二進制數據,具體的處理要在內核進行。
request_firmware 返回二進制firmware文件的地址和大小
//firmware->data和firmware->size來讀取有uevent處理程序init加載進來的firmware數據了
request_firmware( & priv- > firmware, fw_name, priv- > hotplug_device) ; 給priv- > hotplug_device設備申請名字為fw_name的firmware 數據, 然后將結果放到& priv- > firmware中,
struct firmware {
size_t size;
u8 * data;
} ;
可以看到, 如果應用層的程序成功load了firmware固件文件, 那么firmware. data將指向固件數據, firmware. size為固件大小.
前段時間移植 wifi 驅動到 android 的內核上,發現 firmware 的加載始終出錯,問了幾個人,都不是很了解,沒辦法,只好自己研究一下。
原理分析
從本質上來說, firmware 需要做的事情包括兩件:
1, 通知用戶態程序,我需要下載 firmware 了;
2, 用戶態程序把用戶態的數據 copy 到內核層;
3, 內核把內核態的數據寫到設備上,比如 wifi 模塊里;
其中第三步應該不難,關鍵是看看, linux 里面是如何實現第一、二步的;
實現機制
簡單的說,它的機制分成以下幾部分:
1, 通過一定的方式,通知用戶態程序,比如 init 程序,如圖所示:
顯然是通過 kobject_uevent 的方式通知的 應用層,它的機制我有空再詳細解釋,簡單的說,就是往一個 socket 廣播一個消息,只需要在應用層打開 socket 監聽 NETLINK_KOBJECT_UEVENT 組的消息,就可以收到了。
用戶態的 init 是如何做的?
可以看到 init 程序打開了一個 socket ,然后綁定它, 最后通過 select 來監聽 socket 上來的數據,最后調用 handle_device_fd 來處理收到的消息;當內核發送一個 KOBJ_ADD 的消息上來的時候,經過過 濾,判斷是否是 firmware 要被加載的消息,然后調用
handle_firmware_event 來處理;
2, 用戶態的數據如何下載到內核;
本質上它是內核創建了兩個文件,一個文件 A 用來標志下載的開始和結 束,另外一個文件 B 用來接收用戶層傳下來的數據,當用戶態的程序往 A 文件寫入 1 的時候,標志用戶態程序已經往里面寫程序來,而往里面寫入 0 的時候,就標志下載成功結束,如果寫入 -1 就表示下載失敗了;下面 看看這兩個文件是如何被創建的 , 以及數據是如何寫到內核的,請看圖:
這個圖基本上就是兩個文件被創立的過程,以及當這兩個文 件被用戶態程序訪問的時候將要被調用的函數,比如對於標志文件,如果往里面寫入數據,將會觸發函數 firmware_loading_store 函數,如果往 bin 文件里面寫入數據將會觸發 bin 文件類型的 write 函數;
用戶態寫數據的過程大約是這樣的:當用戶態收到 KOBJ_ADD 消息的時候 最終將會調用 handle_firmware_event 的函數;
它的過程就是:
a, 先往標志文件里面寫 1 ;
b, 從用戶空間讀取數據;
c, 往內核創建的文件里面寫數據;
d, 如果成功寫入 0 ,否則寫入 -1 ;
下面看看內核是如何接受這些文件的,前面提到內核創建了一個 bin 文件,用來接收用戶態的數據,下面看 看這個過程:
對於 SYSFS_KOBJ_BIN_ATTR 屬 性的文件,在 inode 初始化的時候,將會被賦予 bin_fops 的文件操作函數集,於是當上層調用 write 的時候,將會走到內核的 bin_fops.write 函數;這個函數干的事情很簡單,就是把用戶態的數據 copyright 到 bb->buffer ,而 bb->buffer 其 實是在 open 的 時候分配的空間,這樣的話,就實現了用戶態的數據到內核的 copy ;過程是不是完了?
還有一個步驟,這個 bb->buffer 本身是如何與 wifi 驅動交互的呢?這只是一個中間層,它的數據必須要寫到 wifi 的驅動才應該算完整,而這一步其實 就是通過 flush_write 來完成的,下面看看這個過程:
這里可以清楚的看到, flush_write 做的事情就是把 bb->buffer 的內容 copy 到 wifi driver 分配的空間 fw->data 里面去了,至此,用戶態的數據已經完整的寫到了 wifi 的 driver 空間了;
3, 內核態的數據到 wifi 模塊
這個就比較簡單了,通過函數 sdio_writesb 利用 sdio 總線把數據寫到模塊 里面去了;
總結
Firmware 的加載主要是利用了 uevent 的通訊機制實現用戶態和內核 態的交互,另外還涉及了 sys 文件系統里的文件創建 , 我加載 wifi firmware 始終出錯的原因是 android 的文件系統要求把 wifi 的 firmware helper 放到 /etc/firmware 里面,而把真正 的 firmware sd8686.bin 放到 /etc/firmware/mrvl 里面,估計是 marvel 修改后的結果,結論就是,這個設計真丑;
獲取固件的正確方法是當需要時從用戶空間獲取它。一定不要試圖從內核空間直接打開包含固件的文件,那是一個易出錯的操作, 因為它把策略(以文件名的形式)包含進了內核。正確的方法是使用固件接口:
#include int request_firmware(const struct firmware **fw, const char *name, /* name 為固件文件名*/ struct device *device); /*因為 request_firmware 需要用戶空間的操作, 所以返回前將保持休眠。若驅動必須使用固件而不能進入休眠時,可使用以下異步函數:*/ /* fw 參數指向以下結構體:*/ /*當固件已經發送到設備后,應當釋放 firmware 結構體, 使用:*/ |
注意:要使用firmware,必須要在配置內核時選上:
Device Drivers --->
Generic Driver Options --->
<*> Userspace firmware loading support
否則會出現: Unknown symbol release_firmware 和: Unknown symbol request_firmware 的錯誤。
當調用 request_firmware時, 函數將在 /sys/class/firmware 下創建一個以設備名為目錄名的新目錄,其中包含 3 個屬性:
loading :這個屬性應當被加載固件的用戶空間進程設置為 1。當加載完畢, 它將被設為 0。被設為 -1 時,將中止固件加載。
data :一個用來接收固件數據的二進制屬性。在設置 loading 為1后, 用戶空間進程將固件寫入這個屬性。
device :一個鏈接到 /sys/devices 下相關入口項的符號鏈接。
一旦創建了 sysfs 入口項, 內核將為設備產生一個熱插拔事件,並傳遞包括變量 FIRMWARE 的環境變量給處理熱插拔的用戶空間程序。FIRMWARE 被設置為提供給 request_firmware 的固件文件名。
用戶空間程序定位固件文件, 並將其拷貝到內核提供的二進制屬性;若無法定位文件, 用戶空間程序設置 loading 屬性為 -1。
若固件請求在 10 秒內沒有被服務, 內核就放棄並返回一個失敗狀態給驅動。超時周期可通過 sysfs 屬性 /sys/class/firmware/timeout 屬性改變。
request_firmware 接口允許使用驅動發布設備固件。當正確地集成進熱插拔機制后, 固件加載子系統允許設備不受干擾地工作。顯然這是處理問題的最好方法,但固件受版權保護,小心違反版權法。
這里主要介紹硬件驅動使用 Linux kernel 提供Firmware load 功能的方法;
(1) kernel source code :
drivers/base/firmware_class.c // linux 2.6.11
(2) header file:
(3) document
Document/firmware/
(4) 使用例子
Documentation/firmware_class/firmware_sample_driver.c
1 /*
2 * firmware_sample_driver.c -
3 *
4 * Copyright (c) 2003 Manuel Estrada Sainz <ranty@debian.org>
5 *
6 * Sample code on how to use request_firmware() from drivers.
7 *
8 * Note that register_firmware() is currently useless.
9 *
10 */
11
12 #include
13 #include
14 #include
15 #include
16
17 #include “linux/firmware.h”
18
19 #define WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE
20 #ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE
21 char __init inkernel_firmware[] = “let’s say that this is firmware\n”;
22 #endif
23
24 static struct device ghost_device = {
25 .name = “Ghost Device”,
26 .bus_id = “ghost0″,
27 };
28
29
30 static void sample_firmware_load(char *firmware, int size)
31 {
32 u8 buf[size+1];
33 memcpy(buf, firmware, size);
34 buf[size] = ‘\0′;
35 printk(“firmware_sample_driver: firmware: %s\n”, buf);
36 }
37
38 static void sample_probe_default(void)
39 {
40 /* uses the default method to get the firmware */
41 const struct firmware *fw_entry;
42 printk(“firmware_sample_driver: a ghost device got inserted \n”);
43
44 if(request_firmware(&fw_entry, “sample_driver_fw”, &ghost_device)!=0)
45 {
46 printk(KERN_ERR
47 “firmware_sample_driver: Firmware not available\n”);
48 return;
49 }
50
51 sample_firmware_load(fw_entry->data, fw_entry->size);
52
53 release_firmware(fw_entry);
54
55 /* finish setting up the device */
56 }
57 static void sample_probe_specific(void)
58 {
59 /* Uses some specific hotplug support to get the firmware from
60 * userspace directly into the hardware, or via some sysfs file */
61
62 /* NOTE: This currently doesn’t work */
63
64 printk(“firmware_sample_driver: a ghost device got inserted \n”);
65
66 if(request_firmware(NULL, “sample_driver_fw”, &ghost_device)!=0)
67 {
68 printk(KERN_ERR
69 “firmware_sample_driver: Firmware load failed\n”);
70 return;
71 }
72
73 /* request_firmware blocks until userspace finished, so at
74 * this point the firmware should be already in the device */
75
76 /* finish setting up the device */
77 }
78 static void sample_probe_async_cont(const struct firmware *fw, void *context)
79 {
80 if(!fw){
81 printk(KERN_ERR
82 “firmware_sample_driver: firmware load failed\n”);
83 return;
84 }
85
86 printk(“firmware_sample_driver: device pointer \”%s\”\n”,
87 (char *)context);
88 sample_firmware_load(fw->data, fw->size);
89 }
90 static void sample_probe_async(void)
91 {
92 /* Let’s say that I can’t sleep */
93 int error;
94 error = request_firmware_nowait (THIS_MODULE,
95 “sample_driver_fw”, &ghost_device,
96 “my device pointer”,
97 sample_probe_async_cont);
98 if(error){
99 printk(KERN_ERR
100 “firmware_sample_driver:”
101 ” request_firmware_nowait failed\n”);
102 }
103 }
104
105 static int sample_init(void)
106 {
107 #ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE
108 register_firmware(“sample_driver_fw”, inkernel_firmware,
109 sizeof(inkernel_firmware));
110 #endif
111 device_initialize(&ghost_device);
112 /* since there is no real hardware insertion I just call the
113 * sample probe functions here */
114 sample_probe_specific();
115 sample_probe_default();
116 sample_probe_async();
117 return 0;
118 }
119 static void __exit sample_exit(void)
120 {
121 }
122
123 module_init (sample_init);
124 module_exit (sample_exit);
125
126 MODULE_LICENSE(“GPL”);
The kernel doesn’t actually load any firmware at all. It simply informs userspace, “I want a firmware by the name of xxx“, and waits for userspace to pipe the firmware image back to the kernel.
udev is configured to run firmware_helper when the kernel asks for firmware
If you read the source, you’ll find that Ubuntu wrote a firmware_helper which is hard-coded to first look for /lib/modules/$(uname -r)/$FIRMWARE, then /lib/modules/$FIRMWARE, and no other locations. Translating it to sh, it does approximately this:
echo -n 1 > /sys/$DEVPATH/loading
cat /lib/firmware/$(uname -r)/$FIRMWARE > /sys/$DEVPATH/data \
|| cat /lib/firmware/$FIRMWARE > /sys/$DEVPATH/data
if [ $? = 0 ]; then
echo -n 1 > /sys/$DEVPATH/loading
echo -n -1 > /sys/$DEVPATH/loading
fi
which is exactly the format the kernel expects.