Linux設備模型淺析之固件篇

Linux設備模型淺析之固件篇
本文屬本人原創,歡迎轉載,轉載請註明出處。由於個人的見識和能力有限,不可能面
面俱到,也可能存在謬誤,敬請網友指出,本人的郵箱是 yzq.seen@gmail.com,博客是
http://zhiqiang0071.cublog.cn   。   
Linux設備模型,僅僅看理論介紹,比如LDD3的第十四章,會感覺太抽象不易理解,而
通過閱讀內核代碼就更具體更易理解,所以結合理論介紹和內核代碼閱讀能夠更快速的理解掌
握linux設備模型。這一序列的文章的目的就是在於此,看這些文章之前最好能夠仔細閱讀
LDD3的第十四章。固件firmware,一般是在芯片中可運行的程序代碼或配置文件,比如
CY68013 USB2.0芯片,它裏面有一個8051內核,可執行代碼可以在線下載到其ram中運行。
內核中firmware模塊的作用是使得驅動程序可以調用其API獲得用戶空間的固件(一般存放在/
lib/firmware目錄下)再通過一定的通信方式(比如I2C、UART和USB等)下載到芯片裏。以
下就以CY68013爲例子說明固件下載的原理。閱讀這篇文章之前,最好先閱讀文章《Linux設
備模型淺析之設備篇》、《Linux設備模型淺析之驅動篇》和《Linux設備模型淺析之驅動篇》
。在文章的最後貼有一張針對本例的圖片,可在閱讀本文章的時候作爲參照。
一、cy68013的8051單片機程序是intel的hex格式,用keil軟件編譯生成。需要下載的可
執行程序有兩個,一個是"cy68013_loader.hex"程序,另一個是"cy68013_fw.hex"程序。前者是作
爲下載後者的守護程序,也就是說先下載"cy68013_loader.hex"程序到cy68013的ram中運行,
然後其負責"cy68013_fw.hex"程序的下載運行及固化(可以固化到外接i2c eeprom中)。這兩個固
件存放在/lib/firmware目錄中。其中會用到兩個API,request_firmware()例程和
release_firmware()例程。前者前者的使用方式是request_firmware(&fw_entry, 
"cy68013_loader.hex",  &cy68013->udev->dev),請求加載一個固件,用來獲取
cy68013_loader.hex 固件的數據,數據保存在fw_entry->data中,大小是fw_entry->size。後者的
使用方式是release_firmware(fw_entry),釋放獲取的資源。request_firmware()例程的定義如
下:
int request_firmware(const struct firmware **firmware_p, const char *name,
                 struct device *device)
{
        int uevent = 1;
        return _request_firmware(firmware_p, name, device, uevent); //  uevent = 1   ,會產生 uevent 事件
}
代碼中,
1.    第一個形參 struct firmware 的定義如下:
struct firmware {
size_t size; // firmware的大小
const u8 *data; // 指向保存firmware數據的buffer
}。
本例中,cy68013->udev->dev 生成的sys目錄是/sys/devices/pci0000:00/0000:00:1d.7/usb2/2-1,後
面生成的firmware device的目錄在該目錄下。下面分析_request_firmware()例程。
二、_request_firmware()例程會睡眠,直到固件下載完成,其定義如下:
static int _request_firmware(const struct firmware **firmware_p, const char *name,
 struct device *device, int uevent){
struct device *f_dev;
struct firmware_priv *fw_priv;
struct firmware *firmware;
struct builtin_fw *builtin;
int retval;
if (!firmware_p)
return -EINVAL;
// 分配struct firmware結構體,在release_firmware()例程中釋放
*firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); 
if (!firmware) {
dev_err(device, "%s: kmalloc(struct firmware) failed\n",
__func__);
retval = -ENOMEM;
goto out;
}
// 先從內置在內核的固件中查找,如果找到了就返回
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;
}
if (uevent)
dev_info(device, "firmware: requesting %s\n", name);
// 這裏做了比較多的工作,後面分析
retval = fw_setup_device(firmware, &f_dev, name, device, uevent);
if (retval)
goto error_kfree_fw;
fw_priv = dev_get_drvdata(f_dev);
// 顯然,前面已經設置爲1了,所以會執行
if (uevent) {
 /* 說明正在執行裝載firmware ,設置一個定時器,在定時器超時回調例程中調用
complete()例程。*/
if (loading_timeout > 0) {
fw_priv->timeout.expires = jiffies + loading_timeout * HZ; // 默認是10s
add_timer(&fw_priv->timeout);
}/* 通知用戶空間,讓udev或mdev處理,後者會再調用一個腳本來處理,該腳本
是/lib/udev/firmware.sh   文件,在後面會分析 */
kobject_uevent(&f_dev->kobj, KOBJ_ADD);
wait_for_completion(&fw_priv->completion); // 阻塞,等待用戶空間程序處理完
set_bit(FW_STATUS_DONE, &fw_priv->status);
del_timer_sync(&fw_priv->timeout); // 刪除定時器
} else
wait_for_completion(&fw_priv->completion); // 等待用戶空間程序處理完畢
mutex_lock(&fw_lock);
// 檢查返回狀態,看是否出現了問題
if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) {
retval = -ENOENT;
release_firmware(fw_priv->fw);
*firmware_p = NULL;
}
fw_priv->fw = NULL;
mutex_unlock(&fw_lock);
device_unregister(f_dev); //    刪除掉之前註冊的 f_dev
goto out;
error_kfree_fw:
kfree(firmware);
*firmware_p = NULL;
out:
return retval;
}
代碼中,
1. fw_setup_device()例程的定義如下:
static int fw_setup_device(struct firmware *fw, struct device **dev_p,
   const char *fw_name, struct device *device,
   int uevent)
{
struct device *f_dev;
struct firmware_priv *fw_priv;
int retval;
*dev_p = NULL;
retval = fw_register_device(&f_dev, fw_name, device); //    註冊 f_dev,後面分析
if (retval)
goto out;
/* Need to pin this module until class device is destroyed */
__module_get(THIS_MODULE); // 增加對本模塊的引用
fw_priv = dev_get_drvdata(f_dev); // 獲取私有數據
fw_priv->fw = fw; // 反向引用/* 生成名爲"data"的二進制屬性文件,其位於/sys/devices/pci0000:00/0000:00:1d.7/usb2/2-
1/firmware/2-1目錄下,可以用於讀寫firmware   數據 */
retval = sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data);
if (retval) {
dev_err(device, "%s: sysfs_create_bin_file failed\n", __func__);
goto error_unreg;
}
/* 生成名爲"loading”的屬性文件,其位於/sys/devices/pci0000:00/0000:00:1d.7/usb2/2-
1/firmware/2-1目錄下,用於控制firmware   的加載過程 */
retval = device_create_file(f_dev, &dev_attr_loading);
if (retval) {
dev_err(device, "%s: device_create_file failed\n", __func__);
goto error_unreg;
}
// 設置可發送uevent事件,在《Linux設備模型淺析之uevent篇》中曾分析過
if (uevent)
f_dev->uevent_suppress = 0;
*dev_p = f_dev;
goto out;
error_unreg:
device_unregister(f_dev);
out:
return retval;
}
代碼中,
1.1. dev_attr_loading的定義如下:
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store)。顯然是一
個device   類型的屬性結構體,有 firmware_loading_show()   例程和 firmware_loading_store()例程兩
個讀寫方法,分別用於讀出加載的命令和寫入加載的命令,定義如下:
static ssize_t firmware_loading_show(struct device *dev,
     struct device_attribute *attr, char *buf)
{
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
//    如果是在 FW_STATUS_LOADING狀態,則變量loading爲1
int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status);
return sprintf(buf, "%d\n", loading); // 最終會輸出到用戶空間
}
static ssize_t firmware_loading_store(struct device *dev,
      struct device_attribute *attr,
      const char *buf, size_t count)
{
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
int loading = simple_strtol(buf, NULL, 10); //    將用戶空間傳遞的值保存在 loading變量中switch (loading) {
case 1: // 標誌着開始加載固件,要清除之前所獲取的資源
mutex_lock(&fw_lock);
if (!fw_priv->fw) { // 如果清除好了,則直接返回
mutex_unlock(&fw_lock);
break;
}
vfree(fw_priv->fw->data); // 釋放存放固件數據的buffer
fw_priv->fw->data = NULL;
fw_priv->fw->size = 0;
fw_priv->alloc_size = 0;
//    設置狀態爲 FW_STATUS_LOADING
set_bit(FW_STATUS_LOADING, &fw_priv->status); 
mutex_unlock(&fw_lock);
break;
case 0: // 停止加載固件
// 如果正在loading,則喚醒被阻塞的例程
if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) {
complete(&fw_priv->completion);
clear_bit(FW_STATUS_LOADING, &fw_priv->status);
break;
}
/* fallthrough */
default:
dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
/* fallthrough */
case -1: // 由於產生了錯誤,取消固件的加載,並丟棄任何已經加載的數據
fw_load_abort(fw_priv);
break;
}
return count;
}
1.2. fw_register_device()例程代碼如下:
static int fw_register_device(struct device **dev_p, const char *fw_name,
      struct device *device)
{
int retval;
//    獲取 struct firmware_priv大小的內存
struct firmware_priv *fw_priv = kzalloc(sizeof(*fw_priv),
GFP_KERNEL);
//    獲取 struct device 大小的內存
struct device *f_dev = kzalloc(sizeof(*f_dev), GFP_KERNEL);的
*dev_p = NULL;if (!fw_priv || !f_dev) {
dev_err(device, "%s: kmalloc failed\n", __func__);
retval = -ENOMEM;
goto error_kfree;
}
init_completion(&fw_priv->completion);
// 用於生成名爲"data"的二進制屬性文件,該結構體後面分析
fw_priv->attr_data = firmware_attr_data_tmpl;
//    拷貝 fw_name   到 fw_priv->fw_id
strlcpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX);
// 設置軟定時器回調例程,用於設定用戶加載固件的時間,後面會分析該回調例程
fw_priv->timeout.function = firmware_class_timeout;
fw_priv->timeout.data = (u_long) fw_priv; // 設置私有數據,將傳送給回調例程
init_timer(&fw_priv->timeout); // 初始化時鐘
dev_set_name(f_dev, dev_name(device)); //  “ 名字跟父設備的名字相同,也就是 2-1”
f_dev->parent = device; // 在本例中是usb_device.dev
f_dev->class = &firmware_class; //    類型是 firmware_class,後面會分析
dev_set_drvdata(f_dev, fw_priv); //    保存私有數據到 f_dev
f_dev->uevent_suppress = 1;
/* 註冊到設備模型中,生成/sys/devices/pci0000:00/0000:00:1d.7/usb2/2-1/firmware/2-1目
錄,該例程的具體實現過程可參照《Linux設備模型淺析之設備篇》。*/
retval = device_register(f_dev);
if (retval) {
dev_err(device, "%s: device_register failed\n", __func__);
goto error_kfree;
}
*dev_p = f_dev;
return 0;
error_kfree:
kfree(fw_priv);
kfree(f_dev);
return retval;
}
代碼中,
1.2.1. 屬性結構體 irmware_attr_data_tmpl的定義如下:
static struct bin_attribute firmware_attr_data_tmpl = {
.attr = {.name = "data", .mode = 0644},
.size = 0,
.read = firmware_data_read,
.write = firmware_data_write,
};
顯然,其定義了名爲"data"   的屬性文件,有 firmware_data_read()   和 firmware_data_write()兩個讀寫的方法,前者將buffer中的固件數據讀出,後者將固件數據寫到buffer中,都是被應用程序
間接調用,分別定義如下:
static ssize_t
firmware_data_read(struct kobject *kobj, struct bin_attribute *bin_attr,
   char *buffer, loff_t offset, size_t count)
{
struct device *dev = to_dev(kobj);
// 獲取私有數據,在前面的fw_register_device()例程中保存了此私有數據
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
struct firmware *fw;
ssize_t ret_count;
mutex_lock(&fw_lock);
fw = fw_priv->fw;
// 判讀是否已經完成
if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
ret_count = -ENODEV;
goto out;
}
//    從 fw->data中將固件數據拷貝到buffer,此buffer中的數據最終傳遞到用戶空間
ret_count = memory_read_from_buffer(buffer, count, &offset,
fw->data, fw->size);
out:
mutex_unlock(&fw_lock);
return ret_count;
}
static ssize_t
firmware_data_write(struct kobject *kobj, struct bin_attribute *bin_attr,
    char *buffer, loff_t offset, size_t count)
{
struct device *dev = to_dev(kobj);
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
struct firmware *fw;
ssize_t retval;
if (!capable(CAP_SYS_RAWIO))
return -EPERM;
mutex_lock(&fw_lock);
fw = fw_priv->fw;
// 判讀是否已經完成
if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
retval = -ENODEV;
goto out;
}
// 根據固件數據的實際大小重新分配內存
retval = fw_realloc_buffer(fw_priv, offset + count);
if (retval)goto out;
//    將固件數據拷貝到 fw->data中
memcpy((u8 *)fw->data + offset, buffer, count);
fw->size = max_t(size_t, offset + count, fw->size);
retval = count;
out:
mutex_unlock(&fw_lock);
return retval;
}
1.2.2. firmware_class_timeout()超時回調例程定義如下:
static void firmware_class_timeout(u_long data)
{
struct firmware_priv *fw_priv = (struct firmware_priv *) data;
fw_load_abort(fw_priv);
}
代碼中,
1.2.2.1. 調用了例程fw_load_abort(),其定義如下:
static void fw_load_abort(struct firmware_priv *fw_priv)
{
set_bit(FW_STATUS_ABORT, &fw_priv->status);   // 超時了,所以abort
wmb();
complete(&fw_priv->completion); // 喚醒阻塞的進程
}
1.2.3. firmware_class的定義如下:
static struct class firmware_class = {
.name = "firmware", // 將會生成/sys/class/firmware目錄
// 在《Linux設備模型淺析之uevent篇》說明過,在產生uevent事件時輸出環境變量
.dev_uevent = firmware_uevent,
.dev_release = fw_dev_release, // 釋放所有資源,主要是釋放內存
}
其在firmware_class_init()例程中被初始化,該例程定義如下:
static int __init firmware_class_init(void)
{
int error;
//    註冊 firmware_class並初始化,生成/sys/class/firmware目錄
error = class_register(&firmware_class); 
if (error) {
printk(KERN_ERR "%s: class_register failed\n", __func__);
return error;
}
/* 生成/sys/class/firmware/timeout屬性文件,用於獲取和設置固件下載超時時間,後面分
  析 */
error = class_create_file(&firmware_class, &class_attr_timeout);
if (error) {printk(KERN_ERR "%s: class_create_file failed\n",
       __func__);
class_unregister(&firmware_class);
}
return error;
}
代碼中,
1.2.3.1. class_attr_timeout定義如下:
static CLASS_ATTR(timeout, 0644, firmware_timeout_show, firmware_timeout_store),顯然定義了
一個class類的屬性結構體,firmware_timeout_show   例程和 firmware_timeout_store例程分別定
義如下:
static ssize_t firmware_timeout_show(struct class *class, char *buf)
{
return sprintf(buf, "%d\n", loading_timeout); //    將變量 loading_timeout值向用戶空間傳遞
}
static ssize_t firmware_timeout_store(struct class *class, const char *buf, size_t count)
{
//    將用戶空間傳遞進來的值賦給變量 loading_timeout
loading_timeout = simple_strtol(buf, NULL, 10); 
if (loading_timeout < 0)
loading_timeout = 0;
return count;
}
2. 現在說說之前提到過的firmware.sh shell腳本,其定義如下:
#!/bin/sh -e 
// 固件存放的目錄,通常是位於後者,/lib/firmware中
FIRMWARE_DIRS="/lib/firmware/$(uname -r)   /lib/firmware" 
err() { 
echo "$@" >&2 
logger -t "${0##*/}[$$]" "$@" 2>/dev/null || true 

/* 先判斷"loading”屬性文件是否存在。DEVPATH是由內核通過uevent事件輸出的環境變量(一
個目錄),在本例中是/sys/devices/pci0000:00/0000:00:1d.7/usb2/2-1/firmware/2-1目錄。*/
if [ ! -e /sys$DEVPATH/loading ]; then 
err "udev firmware loader misses sysfs directory" 
exit 1 
fi 
//    從前面的定義中可看出, FIRMWARE_DIRS有兩個可能的目錄,/lib/firmware/$(uname -r) 
和/lib/firmware,所以做個遍歷加載。
for DIR in $FIRMWARE_DIRS; do 
[ -e "$DIR/$FIRMWARE" ] || continue  // 如果目錄存在就執行
echo 1 > /sys$DEVPATH/loading  // 開始加載cat "$DIR/$FIRMWARE" > /sys$DEVPATH/data  //  “ 將固件數據寫入到 data”屬性文件中
echo 0 > /sys$DEVPATH/loading  // 停止加載
exit 0  // 成功,則返回
done 
// 如果執行到這裏,說明沒有找到固件,故寫入-1
echo -1 > /sys$DEVPATH/loading 
err "Cannot find  firmware file '$FIRMWARE'" 
exit 1
三、此時獲取到了固件數據,但還要通過USB將其下載到CY68013芯片中運行。具體過
程不在本文的內容範圍內,就不予講述分析了。
四、最後調用release_firmware()例程釋放掉在request_firmware()例程中申請的資源,該例
程的定義如下:
void
release_firmware(const struct firmware *fw)
{
struct builtin_fw *builtin;
if (fw) { 
for (builtin = __start_builtin_fw; builtin != __end_builtin_fw;
     builtin++) {
if (fw->data == builtin->data)
goto free_fw;
}
vfree(fw->data); //    如果固件不是內置在內核中,則釋放掉存放固件的 fw->data 
free_fw:
kfree(fw); //    釋放掉結構體 fw
}
}
至此,一次固件的加載就這樣完成了。總體而言,固件的加載是通過linux設備模型產生
一個uevent事件,通知用戶空間的程序udev或mdev來實現。後者再調用腳本firmware.sh(PC
機上)將固件數據通過屬性文件"data"寫入到內核中,然後驅動程序通過一定的通信方式將其下
載到芯片中,比如本例中的CY68013芯片,PC機就是通過USB總線下載到其中的。。
附圖:注:
1. 其中黑色字體的矩形表示是個文件夾;
2. 其中青色字體的矩形表示是個文件;
發佈了16 篇原創文章 · 獲贊 1 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章