Linux 2.6内核中包含了HID驱动,能够自动把USB Key等HID外设识别成“/dev/hiddev0”之类的设备。但是该驱动没有实现write接口,因此无法象Windows平台那样使用 ReadFile和WriteFile来读写HID设备,而只能使用ioctl接口。
网上有各种各样读写HID设备的源代码例子,有的是通过HIDIOCSUSAGE和HIDIOCGUSAGE来每次收发4个字节,适合鼠标、键盘之类数据传输量小的设备;有的是通过HIDIOCSUSAGES和HIDIOCGUSAGES来连续接收和发送多个字节,适合USB Key一类的设备。
在上一篇日志(已删除)中,介绍了如何利用《USB and PIC: quick guide to an USB HID framework》一文提供的方法与USB Key进行通信(先发送HIDIOCSUSAGES和HIDIOCSREPORT进行写操作,再发送HIDIOCGREPORT和HIDIOCGUSAGES进行读操作,从而完成一次通信过程)。但是经过好友测试,发现该方法不论是在PC机上,还是在Cavium Octeon 52XX开发板上均存在问题,读出的数据始终是第一次通信的结果,除非在每次通信之前都发送HIDIOCINITREPORT控制码,但这又会造成相当长时间的阻塞。
进一步的测试表明,如果按照HIDIOCGUCODE、HIDIOCSUSAGES、HIDIOCSREPORT、HIDIOCGUCODE、HIDIOCGUSAGES的顺序发送控制码,那么可以每次都读出正确数据。不过该方法虽然在PC机上只需400毫秒延时,但是在Octeon开发板上仍会长时间阻塞在usbhid_wait_io函数那里。
无奈之下,我只好根据Cavium SDK自带的Linux内核源码中的usb_skeleton.c写了一个USB设备驱动程序,试图通过直接读写USB端点来完成通信过程。以下是在开发和调试过程中需要注意的几个问题:
首先,必须卸载Linux内核自带的HID驱动,否则它会自动“接管”新插入的USB Key,导致我们自己编写的驱动程序找不到设备。对于开发板,可以在编译内核时去掉HID相关的选项;对于PC机上已经安装好的Linux,我也不知道该怎么卸载其中的HID驱动。
其次,端点类型。在usb_skeleton.c中是通过bulk端点来访问USB设备的,而USB Key作为HID设备,一般只有0号控制端点和一个中断输入端点(例如3号)。对于中断端点,可以用usb_interrupt_msg(其实就是usb_bulk_msg)函数进行访问;对于控制端点,则稍微麻烦一些,因为除了数据,还需要构造一个8字节的setup包。有关setup包的详细结构,可以参考USB和HID规范。获取setup包具体数值最简单的方法,就是在Windows环境下用BusHound观察USB Key的通信过程。
最后,关于Report ID。在Windows环境下通过ReadFile和WriteFile访问HID设备时,必须在数据开头附加1字节的Report ID(一般为0)。在Linux环境下,如果使用HID驱动的ioctl接口,那么需要在hiddev_usage_ref结构中指定Report ID;如果使用自己编写的USB驱动程序,则不需要考虑Report ID,直接发送数据就得了。
经过测试,利用自己编写的驱动程序,可以在Octeon开发板上正确读写HID类型的USB Key,而且读写之间的时间间隔也可以缩短至50毫秒。