Linux firmware 加載【轉】


轉自: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);
/*要求用戶空間定位並提供一個固件映象給內核;若成功加載, 返回值是 0(否則返回錯誤碼)*/

/*因為 request_firmware 需要用戶空間的操作, 所以返回前將保持休眠。若驅動必須使用固件而不能進入休眠時,可使用以下異步函數:*/
int request_firmware_nowait(
struct module *module, /* = THIS_MODULE*/
int uevent,
const char *name,
struct device *device,
void *context,/*不由固件子系統使用的私有數據指針*/
void (*cont)(const struct firmware *fw, void *context));
/*如果一切正常,request_firmware_nowait 開始固件加載過程並返回 0. 過了一段時間后(默認10秒),將用加載的結果(若加載失敗, fw 為 NULL)作為參數調用 cont。*/

/* fw 參數指向以下結構體:*/
struct firmware {
size_t size;
u8 *data;
};
/*那個結構包含實際的固件, 它現在可被下載到設備中.但是請注意:在發送它到硬件之前,必須檢查這個文件以確保它是正確的固件映象(設備固件常常包含標識字符串、 校驗和等等)*/

/*當固件已經發送到設備后,應當釋放 firmware 結構體, 使用:*/
void release_firmware(struct firmware *fw);

注意:要使用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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM