笔者曾得到一个类似于加密“狗”的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的访问信息。 |