註:本文轉載自http://club.topsage.com/thread-1231567-1-1.html
筆者曾得到一個類似於加密“狗”的USB裝置,要使之在Linux下正常工作。然而,通過一個名為USBView的小程式判斷,Linux核心無 法驅動這個USB裝置,並且在“Linux USB Working Devices”的列表中也沒有找到該裝置,這意味著只有很少的人在使用這種類型的 USB裝置。
在Linux的/proc/bus/usb/devices檔案中,有這個USB裝置的一些資訊:
T: Bus=01 Lev=02 Prnt=03 Port=02 Cnt=01 Dev#=4 Spd=1.5 MxCh=0 D: Ver=1.00 Cls=ff(vend.) Sub=00 Prot=ff MxPS=8 #Cfgs=1 P: Vendor=0d7a ProdID=0001 Rev=1.07 S: Manufacturer=Marx S: Product=USB crypToken C: * #Ifs=1 Cfg#=1 Atr=80 MxPwr=16mA I: If#=0 Alt=0 #EPs=0 Cls=ff(vend.) Sub=00 Prot=ff Driver=(none) |
通過查閱該USB裝置產品光碟片上提供的文檔,可以簡單瞭解這個USB裝置的用途。產品光碟片上還提供了一個共用庫,應用程式可以通過共用庫使用這個USB裝置。此外,產品光碟片還提供了一個小的測試程式來展示不同的庫方法是如何工作的。通過這個共用庫,使用者可以採用libusb方式直接存取該裝置。也就是說,使用這個USB裝置並不需要一個核心級的驅動。但是,這個共用庫的授權協議不允許一個遵循GPL協議的程式來使用它。只有以上這些資訊是無法把這個USB裝置驅動起來的,下面就從追溯Linux上的USB資料流開始,給大家提供一種解決此類問題的思路。
解決方案
可以從最新的2.6版本的核心入手,解決此問題。2.6版本的核心可以通過很多種渠道得到,這裡不做詳述。
在核心原始碼的結構樹中的drivers/usb/core/目錄下,有inode.c、devices.c和devio.c三個檔案,這三個檔案共同實現了usbfs 檔案系統。也就是說,Linux是通過這三個檔案訪問USB裝置的。實現檔案系統的主要工作由inode.c完成,它提供多種VFS代碼,用 這些代碼可以建立虛擬檔案系統或虛擬檔案。devices.c檔案用於建立和讀取/proc/bus/usb/devices下表示USB裝置狀態資訊的文 件。devio.c檔案用於控制通過usbfs檔案系統對USB裝置的訪問。要想使Linux能夠訪問USB裝置,需要對devices.c和devio.c做些修改。
具體實現
1.記錄訪問資訊
探索USB資料流從屬記錄所有的USB裝置訪問資訊開始。在使用者程式中,通過usbfs訪問USB裝置需要在相應的裝置檔案上調用ioctl()命令。通過進一步分析devices.c檔案,發現每次執行ioctl()時都會調用usbdev_ioctl()函數,因此,在這裡記錄發送給USB裝置的訪問資訊應該是一個很好的解決方案。通過在每一個case語句中加入一個自訂的printk()函數,可以實現此功能,基本內容如下:
case USBDEVFS_CLAIMINTERFACE: printk("CLAIMINTERFACE"); ret = proc_claiminterface(ps, (void __user *)arg); break; case USBDEVFS_RELEASEINTERFACE: printk("RELEASEINTERFACE"); ret = proc_releaseinterface(ps, (void __user *)arg); break; |
經過編譯、安裝等過程後,每個usbfs訪問都被記錄在核心日誌當中,通過dmesg命令可以訪問這些日誌記錄。
2.記錄控制資訊
通過分析/proc/bus/usb/devices檔案中對USB裝置的描述,可以得知要最終解決問題必須關心這些訪問的控制終點(Control Endp oint)。devio.c檔案中的proc_control()函數是用來處理控制訊息的。該函數通過如下語句區分請求訪問的類型是讀控制訊息,還是寫控制訊息:
if (ctrl.bRequestType & 0x80) |
bRequestType是一個二進位變數,其最高位決定請求的類型。
在處理讀訊息的程式碼片段中加入如下代碼,用以記錄控制請求的資訊:
printk("control read: " "bRequest=%02x bRrequestType=%02x " "wValue=%04x wIndex=%04x", ctrl.bRequest, ctrl.bRequestType, ctrl.wValue, ctrl.wIndex); |
然後,再加入如下代碼,用來記錄從裝置中讀出的實際資料:
printk("control read: data "); for (j = 0; j < ctrl.wLength; ++j) printk("%02x ", ctrl.data[j]); printk(""); |
同樣,可以在處理寫訊息的程式碼片段中加入類似的代碼。這樣以來,產生並重新裝載新的usbcore模組後,就可以記錄對裝置的所有 雙向控制訊息了。這些訊息可能會返回如下資訊:
CONTROL control read: bRequest=06 bRrequestType=80 wValue=0300 wIndex=0000 control read: data 00 00 61 63 |
3.最佳化
接下來要對修改代碼做些最佳化,以使核心原始碼很好地接受這些修改。首先,應該修正在printk()調用中的一些不正確的地方。其次,所有的printk()調用都應該有一個相應的記錄層級(LoggingLevel),這些記錄層級可以通過預先處理的方式產生。將include /linux/kernel.h檔案修改如下:
#define KERN_EMERG "" /* system is unusable */ #define KERN_ALERT "" /* action must be taken immediately */ #define KERN_CRIT "" /* critical conditions */ #define KERN_ERR "" /* error conditions */ #define KERN_WARNING "" /* warning conditions */ #define KERN_NOTICE "" /* normal but significant condition */ #define KERN_INFO "" /* informational */ #define KERN_DEBUG "" /* debug-level messages */ |
以上修改完成後,將usbfs_ioctl()函數中的printk()調用從“printk("CLAIMINTERFACE");”修改為“printk(KERN_INFO "CLAIMINTE RFACE);”。
實際上,並不是所有的訊息都需要記錄到日誌中。可以採用標頭檔include/linux/device.h中所提供的宏(包括dev_printk()、de v_dbg()、dev_warn()、dev_info()和dev_err())來決定何種類型的USB裝置訪問訊息需要被記錄。這些宏都需要一個附加指標指向一個結構類型,以此標識惟一的裝置ID。將dev_info()調用修改如下:
dev_info(&dev->dev, "CLAIMINTERFACE"); |
然後將確定讀、寫訊息請求的printk()調用修改如下:
dev_info(&dev->dev, "control read: " "bRequest=%02x bRrequestType=%02x " "wValue=%04x wIndex=%04x", ctrl.bRequest, ctrl.bRequestType, ctrl.wValue, ctrl.wIndex); dev_info(&dev->dev, "control read: data "); for (j = 0; j < ctrl.wLength; ++j) printk("%02x ", ctrl.data[j]); printk(""); |
經過上述修改,得到如下返回資訊:
usb 1-1: CONTROL usb 1-1: control read: bRequest=06 bRrequestType=80 wValue=0300 wIndex=0000 usb 1-1: control read: data 00 00 61 63 |
不難看出,經過上述修改,清除掉了無關的USB裝置的訊息記錄資訊。
上述過程會產生一個易用性上的問題,即訊息記錄資訊並不是在使用者請求時刻產生的,這樣會導致使用者的訊息記錄過於龐大。在de vio.c檔案中加入下面的程式碼可以解決這一問題:
static int usbfs_snoop = 0; module_param (usbfs_snoop, bool, S_IRUGO | S_IWUSR); MODULE_Parm_DESC (usbfs_snoop, "true to log all usbfs traffic"); |
上述代碼定義了一個新的module_param()函數,用來代替原來的MODULE_Parm()函數。兩者之間的主要區別在於module_param()中含有一個新的參數“usbfs_snoop”。可以運行modinfo命令查看修改後的效果:
$ modinfo usbcore license: GPL parm: blinkenlights:true to cycle leds on hubs parm: usbfs_snoop:true to log all usbfs traffic |
正常的模組裝載命令如下:
修改後,可以通過下面的命令裝載模組:
#modprobe usbcore usbfs_snoop=1 |
當usbfs_snoop不為0時,它將會在sysfs中顯示,並允許使用者在模組被裝載時查詢和修改選項,其形式如下:
$ ls -l /sys/module/usbcore/ -r--r--r-- 1 root root 4096 May 13 15:33 blinkenlights -r--r--r-- 1 root root 4096 May 13 15:33 refcnt -rw-r--r-- 1 root root 4096 May 13 15:33 usbfs_snoop |
如果想開啟訊息記錄功能,可以進行如下操作:
#echo 1 > /sys/module/usbcore/usbfs_snoop |
如果要確定使用者是否想開啟訊息記錄功能,還需對dev_info()進行更深一步修改,建立下述宏:
#define snoop(dev, format, arg...) do { if (usbfs_snoop) dev_info( dev , format , ## arg); } while (0) |
這個宏用於測試參數usbfs_snoop的值,如果為“true”,則調用dev_info(dev , format , ##arg)。
接下來將先前調用dev_info()宏的地方改為調用snoop()宏:
snoop(&dev->dev, "control read: ","bRequest=%02x bRrequestType=%02x ","wValue= %04x wIndex=%04x", ctrl.bRequest, ct rl.bRequestType,ctrl.wValue, ctrl.wIndex); |
為了能正確列印資料,需對snoop()宏做簡單修改(只需判斷usbfs_snoop的值即可):
if (usbfs_snoop) { dev_info(&dev->dev, "control read: data "); for (j = 0; j < ctrl.wLength; ++j) printk("%02x ", ctrl.data[j]); printk(""); } |
至此,修改工作完畢。這樣,當通過libusb訪問一個USB裝置時,應該可以很好地得到usbfs的訪問資訊。