作為一個驅動作者, 你可能發現你面對一個設備必須在它能支持工作前下載固件到它里面。 硬件市場的許多地方的競爭是如此得強烈, 以至于甚至一點用作設備控制固件的 EEPROM 的成本制造商都不愿意花費。 因此固件發布在隨硬件一起的一張 CD 上, 并且操作系統負責傳送固件到設備自身。
硬件越來越復雜,硬件的許多功能使用了程序實現,與直接硬件實現相比,固件擁有處理復雜事物的靈活性和便于升級、維護等優點。固件(firmware)就是這樣的一段在設備硬件自身中執行的程序,通過固件標準驅動程序才能實現特定機器的操作,如:光驅、刻錄機等都有內部的固件。
固件一般存放在設備上的flash存儲器中,但出于成本和靈活性考慮,許多設備都將固件的映像(image)以文件的形式存放在硬盤中,設備驅動程序初始化時再裝載到設備內部的存儲器中。這樣,方便了固件的升級,并省略了設備的flash存儲器。
一、驅動和固件的區別
計算機領域來說,驅動和固件從來沒有過明確的定義,就好像今天我們說內存,大部分人用來表示SDRAM,但也有人把Android里的“固化的Flash/Storage“稱為“內存”,你不能說這樣說就錯了,因為這確實是一種“內部存儲”。
但在Linux Kernel中,Driver和Firmware是有明確含義的,
1、驅動
Driver是控制被操作系統管理的外部設備(Device)的代碼段。很多時候Driver會被實現為LKM,但這不是必要條件。driver通過driver_register()注冊到總線(bus_type)上,代表系統具備了驅動某種設備(device)的能力。當某個device被注冊到同樣的總線的時候(通常是總線枚舉的時候發現了這個設備),總線驅動會對driver和device會通過一定的策略進行binding(即進行匹配),如果Binding成功,總線驅動會調用driver的probe()函數,把設備的信息(例如端口,中斷號等)傳遞給驅動,驅動就可以對真實的物理部件進行初始化,并把對該設備的控制接口注冊到Linux的其他子系統上(例如字符設備,v4l2子系統等)。這樣操作系統的其他部分就可以通過這些通用的接口來訪問設備了。
2、固件
Firmware,是表示運行在非“控制處理器”(指不直接運行操作系統的處理器,例如外設中的處理器,或者被用于bare metal的主處理器的其中一些核)中的程序。這些程序很多時候使用和操作系統所運行的處理器完全不同的指令集。這些程序以二進制形式存在于Linux內核的源代碼樹中,生成目標系統的時候,通常拷貝在/lib/firmware目錄下。當driver對device進行初始化的時候,通過request_firmware()等接口,在一個用戶態helper程序的幫助下,可以把指定的firmware加載到內存中,由驅動傳輸到指定的設備上。
所以,總的來說,其實driver和firmware沒有什么直接的關系,但firmware通常由驅動去加載。我們討論的那個OS,一般不需要理解firmware是什么,只是把它當做數據。firmware是什么,只有使用這些數據的那個設備才知道。好比你用一個電話,電話中有一個軟件,這個軟件你完全不關心如何工作的,你換這個軟件的時候,就可以叫這個軟件是“固件”,但如果你用了一個智能手機,你要細細關系什么是上面的應用程序,Android平臺,插件之類的細節內容,你可能就不叫這個東西叫“固件”了。
如何解決固件問題呢?你可能想解決固件問題使用這樣的一個聲明:
static char my_firmware[] = { 0x34, 0x78, 0xa4, 。.. };
但是, 這個方法幾乎肯定是一個錯誤。 將固件編碼到一個驅動擴大了驅動的代碼, 使固件升級困難, 并且非常可能產生許可問題。 供應商不可能已經發布固件映象在 GPL 之下, 因此和 GPL-許可的代碼混合常常是一個錯誤。 為此, 包含內嵌固件的驅動不可能被接受到主流內核或者被 Linux 發布者包含。
二、內核固件接口
正確的方法是當你需要它時從用戶空間獲取它。 但是, 請抵制試圖從內核空間直接打開包含固件的文件的誘惑; 那是一個易出錯的操作, 并且它安放了策略(以一個文件名的形式)到內核。 相反, 正確的方法時使用固件接口, 它就是為此而創建的:
[cpp] view plain copy
1. #include
2.
3. int request_firmware(const struct firmware **fw, char *name, struct device *device);
函數request_firmware向用戶空間請求提供一個名為name固件映像文件并等待完成。參數device為固件裝載的設備。文件內容存入request_firmware 返回,如果固件請求成功,返回0。該函數從用戶空間得到的數據未做任何檢查,用戶在編寫驅動程序時,應對固件映像做數據安全檢查,檢查方向由設備固件提供商確定,通常有檢查標識符、校驗和等方法。
調用 request_firmware 要求用戶空間定位并提供一個固件映象給內核; 我們一會兒看它如何工作的細節。 name 應當標識需要的固件; 正常的用法是供應者提供的固件文件名。 某些象 my_firmware.bin 的名子是典型的。 如果固件被成功加載, 返回值是 0(負責常用的錯誤碼被返回), 并且 fw 參數指向一個這些結構:
[cpp] view plain copy
1. struct firmware {
2. size_t size;
3. u8 *data;
4. };
那個結構包含實際的固件, 它現在可被下載到設備中。 小心這個固件是來自用戶空間的未被檢查的數據; 你應當在發送它到硬件之前運用任何并且所有的你能夠想到的檢查來說服你自己它是正確的固件映象。 設備固件常常包含標識串, 校驗和, 等等; 在信任數據前全部檢查它們。
在你已經發送固件到設備前, 你應當釋放 in-kernel 結構, 使用:
[cpp] view plain copy
1. void release_firmware(struct firmware *fw);
因為 request_firmware 請求用戶空間來幫忙, 它保證在返回前睡眠。 如果你的驅動當它必須請求固件時不在睡眠的位置, 異步的替代方法可能要使用:
[cpp] view plain copy
1. int request_firmware_nowait(struct module *module,
2. char *name, struct device *device, void *context,
3. void (*cont)(const struct firmware *fw, void *context));
這里額外的參數是 moudle( 它將一直是 THIS_MODULE), context (一個固件子系統不使用的私有數據指針), 和 cont. 如果都進行順利, request_firmware_nowait 開始固件加載過程并且返回 0. 在將來某個時間, cont 將用加載的結果被調用。 如果由于某些原因固件加載失敗, fw 是 NULL.
三、固件如何工作
固件子系統使用 sysfs 和熱插拔機制。 當調用 request_firmware, 一個新目錄在 /sys/class/firmware 下使用你的驅動的名子被創建。 那個目錄包含 3 個屬性:
loading
這個屬性應當被加載固件的用戶空間進程設置為 1. 當加載進程完成, 它應當設為 0. 寫一個值 -1 到 loading 會中止固件加載進程。
data
data 是一個二進制的接收固件數據自身的屬性。 在設置 loading 后, 用戶空間進程應當寫固件到這個屬性。
device
這個屬性是一個符號連接到 /sys/devices 下面的被關聯入口項。
一旦創建了 sysfs 入口項, 內核為你的設備產生一個熱插拔事件。 傳遞給熱插拔處理者的環境包括一個變量 FIRMWARE, 它被設置為提供給 request_firmware 的名子。 這個處理者應當定位固件文件, 并且拷貝它到內核使用提供的屬性。 如果這個文件無法找到, 處理者應當設置 loading 屬性為 -1.
如果一個固件請求在 10 秒內沒有被服務, 內核就放棄并返回一個失敗狀態給驅動。 超時周期可通過 sysfs 屬性 /sys/class/firmware/timeout 屬性改變。
使用 request_firmware 接口允許你隨你的驅動發布設備固件。 當正確地集成到熱插拔機制, 固件加載子系統允許設備簡化工作”在盒子之外“ 顯然這是處理問題的最好方法。
但是, 請允許我們提出多一條警告: 設備固件沒有制造商的許可不應當發布。 許多制造商會同意在合理的條款下許可它們的固件, 如果客氣地請求; 一些其他的可能不何在。 無論如何, 在沒有許可時拷貝和發布它們的固件是對版權法的破壞并且招致麻煩。
四、固件接口函數的使用方法
當驅動程序需要使用固件驅動時,在驅動程序的初始化化過程中需要加下如下的代碼:
[cpp] view plain copy
1. if(request_firmware(&fw_entry, $FIRMWARE, device) == 0) /*從用戶空間請求映像數據*/
2.
3. /*將固件映像拷貝到硬件的存儲器,拷貝函數由用戶編寫*/
4. copy_fw_to_device(fw_entry-》data, fw_entry-》size);
5. release(fw_entry);
用戶還需要在用戶空間提供腳本通過文件系統sysfs中的文件data將固件映像文件讀入到內核的緩沖區中。腳本樣例列出如下:
[cpp] view plain copy
1. #變量$DEVPATH(固件設備的路徑)和$FIRMWARE(固件映像名)應已在環境變量中提供
2.
3. HOTPLUG_FW_DIR=http://www.eechina.com/usr/lib/hotplug/firmware/ #固件映像文件所在目錄
4.
5. echo 1 》 /sys/$DEVPATH/loading
6. cat $HOTPLUG_FW_DIR/$FIRMWARE 》 /sysfs/$DEVPATH/data
7. echo 0 》 /sys/$DEVPATH/loading
五、固件請求函數request_firmware
函數request_firmware請求從用戶空間拷貝固件映像文件到內核緩沖區。該函數的工作流程列出如下:
a -- 在文件系統sysfs中創建文件/sys/class/firmware/xxx/loading和data,”xxx“表示固件的名字,給文件loading和data附加讀寫函數,設置文件屬性,文件loading表示開/關固件映像文件裝載功能;文件data的寫操作將映像文件的數據寫入內核緩沖區,讀操作從內核緩沖區讀取數據。
b -- 將添加固件的uevent事件(即”add“)通過內核對象模型發送到用戶空間。
c -- 用戶空間管理uevent事件的后臺進程udevd接收到事件后,查找udev規則文件,運行規則所定義的動作,與固件相關的規則列出如下:
[cpp] view plain copy
1. $ /etc/udev/rules.d/50-udev-default.rules
2. ……
3. # firmware class requests
4. SUBSYSTEM==”firmware“, ACTION==”add“, RUN+=”firmware.sh“
5. ……
從上述規則可以看出,固件添加事件將引起運行腳本firmware.sh。
d -- 腳本firmware.sh打開”裝載“功能,同命令”cat 映像文件 》 /sys/class/firmware/xxx/data“將映像文件數據寫入到內核的緩沖區。
e -- 映像數據拷貝完成后,函數request_firmware從文件系統/sysfs注銷固件設備對應的目錄”xxx“。如果請求成功,函數返回0。
f -- 用戶就將內核緩沖區的固件映像數據拷貝到固件的內存中。然后,調用函數release_firmware(fw_entry)釋放給固件映像分配的緩沖區。
-
驅動
+關注
關注
12文章
1825瀏覽量
85178 -
Linux
+關注
關注
87文章
11229瀏覽量
208927
發布評論請先 登錄
相關推薦
評論