星期四, 8月 30, 2012

Linux 2.6.13+ 的嵌入式系統上實現 USB 熱插拔

某個我習慣用來做紀錄的 BBS 系統因為硬體故障,所以暫時無法使用,所以還是寫篇文章紀錄起來好了。

最近因為工作上的需要接觸了些嵌入式系統的開發工作。基於設計需求,必須讓系統支援 USB 裝置的熱插拔。如果只是單純的 USB thumb drive 的熱插拔其實問題不大,但是如果接上去的裝置是個 USB 的記憶卡讀卡機的話,狀況就複雜了些。為了解決讀卡機的熱插拔問題,花了點時間研究了一下以 udev 為基礎的熱插拔機制到底是怎麼運作的。


以 Ubuntu Linux 為例,Kernel 2.6.13+ 的 Linux 系統目前的熱插拔機制,主要是靠 kernel 的 udev 機制,搭配 udev 軟體套件和 udisks 套件,來動態管理 /dev 下的裝置檔案以及實現 USB 熱插拔。根據這份有點稍微過時的文件,目前 kernel 的 uevent 介面,提供了兩種由 user space 實現熱插拔的機制,一個是透過在 /sys/kernel/uevent_helper (或 /proc/sys/kernel/hotplug) 設定一個 user space 的 helper 程式,在 uevent 觸發時執行;另一個則是透過 netlink 由 user space daemon 來接收 uevent。

在像是 Ubuntu Linux 之類的 Desktop 系統上,目前常見的熱插拔,是 udevd 透過 netlink 接收 uevent 介面送出的熱插拔事件,然後 udevd 根據 rule 分別在 /dev 下建立對應的裝置檔案或觸發其他程式,例如透過 dbus 通知 GNOME 之類的桌面環境有新裝置出現等等。在嵌入式系統上,最常見的替代方案,則是使用 busybox 中的 mdev 當 uevent_helper 來取代 udevd。mdev 在 uevent 觸發執行時,會根據 /etc/mdev.conf 中設定的規則來建立和管理 /dev 下的裝置檔案,也能用來呼叫其他的程式進行像是自動掛載等動作。不過如果插入的裝置是 USB 讀卡機的話,整個狀況又不太一樣了。

USB 讀卡機如果在接上 USB 插槽前,讀卡機中沒有記憶卡的話,插上去之後透過 uevent_helper 介面可以發現 kernel 偵測到 raw device,但是之後再插入記憶卡的話,從 dmesg 的輸出中會看到 kernel 完全沒有任何反應。使用一個簡單的 shell script 取代 mdev 來檢視整個 uevent 觸發的狀況,以 env 把 uevent 傳入的環境變數內容全部紀錄下來,可以發現確實在讀卡機插入 USB 插槽時,會觸發一連串的 uevent,但是之後插卡時則完全沒有任何 uevent 發生。使用 kernel 的 usbmon 搭配 debugfs 監測 USB 控制器的通訊狀態,可以發現問題出在讀卡機在記憶卡插上去之後,並沒有和 USB Host 有任何通訊產生。

那到底 Desktop 系統中的記憶卡熱插拔又是怎樣辦到的呢?檢查 udev 的 rule 可以發現,udevd 根據這些 rule,由 vendor ID 和 product ID 等資訊,以及透過 usb_id 等輔助程式查詢,確認是讀卡機類型的裝置之後,會設定 ID_DRIVE_FLASH_* 等環境變數,通知 udisks 這個裝置是讀卡機裝置,然後再由 udisks 定時對讀卡機的 raw device 做讀取,也就是做所謂的 polling 的動作。當記憶卡插入讀卡機後,polling 的動作會讓 kernel 偵測到有 partition 裝置,並又觸發後續的 uevent。

因此,要在嵌入式裝置上實現讀卡機的熱插拔,最直接的方式自然是實作類似於 udevd 和 udisks 的裝置類型判斷以及裝置 polling 的機制。不過問題是要移植 udisks 和 usb_id 等輔助程式到資源有限的嵌入式系統上,似乎不是個理想的做法。因此我的變通辦法是,透過 mdev 在 sd[a-z] 的 raw device 建立時呼叫一個輔助判別程式,這個判別程式則是使用 uevent 的 DEVPATH 中的 SCSI host、channel、ID、LUN 等資訊,在 /proc/scsi/scsi 的裝置列表中找到對應的裝置內容,並比對 "Model" 資訊來判斷插入的 raw device 是否是讀卡機,如果不是的話,則串聯呼叫自動掛載程式;如果發現是讀卡機的話,則啟動另一個 poller daemon 程式對該裝置定時做 polling 的動作,來處理卡片插入和移除的事件,直到裝置移除時,才由輔助判別程式通知 poller daemon 裝置已移除,讓 daemon 結束執行。

這樣的解決方案也許不是最佳方案,但是應該是相對簡單的解決方法,至少在目標系統上,我使用 busybox 的 ash,用 shell script 就實作了驗證這個想法用的 tester 程式和 pollerd 程式,成功的以軟體的方式達成 USB 熱插拔。


繼續閱讀全文