市面上一些无驱的usbkey 一直是技术卖点,象招商银行的usbkey 就一直以无需安装驱动自豪。无驱动的usbkey确是比较方便,尤其在使用别人的计算机的时候。
刚好最近在研究一款HID 设备,在这方面做了一些探索。 首先工具一定要利索,最好能武装到牙齿,下面的三个工具最好都有,否则就回到了石器时代 bus hound usb device viewer hid descriptor tool 文档是一定要仔细看, Universal Serial Bus Revision 2.0 specification ,尤其是那个著名的chap. 9 Device Class Definition for HID 1.11 google , 一定要多用,你碰到的问题,别人肯定已经碰到过,不过别太相信中文文章,绝大部分没有自己实验过,会误导 我们看一下招行的usbkey 抓包结果 25.0 CTL 80 06 00 01 00 00 12 00 GET DESCRIPTOR 1.1.0 25.0 DI 12 01 10 01 00 00 00 08 6e 09 10 a0 05 20 01 02 00 01 ........n.... .. 1.2.0 25.0 CTL 80 06 00 02 00 00 09 00 GET DESCRIPTOR 2.1.0 25.0 DI 09 02 1b 00 01 01 00 80 0f ......... 2.2.0 25.0 CTL 80 06 00 02 00 00 1b 00 GET DESCRIPTOR 3.1.0 25.0 DI 09 02 1b 00 01 01 00 80 0f 09 04 00 00 00 03 00 00 00 09 21 00 01 00 01 22 a6 00 ................ 3.2.0 25.0 CTL 00 09 01 00 00 00 00 00 SET CONFIG 4.1.0 25.0 CTL 21 0a 00 00 00 00 00 00 SET IDLE 5.1.0 25.0 USTS 04 00 00 c0 stall pid 5.2.0 25.0 CTL 81 06 00 22 00 00 e6 00 GET DESCRIPTOR 6.1.0 25.0 DI 06 a0 ff 09 a2 a1 01 09 a3 a1 03 06 a1 ff 09 c1 09 c2 09 c3 09 da 09 da 09 da 09 da 09 da 09 da ................ 6.2.0 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da ................ 6.2.32 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da ................ 6.2.64 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da ................ 6.2.96 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 c4 15 00 25 ff 35 00 45 ff 75 08 ................ 6.2.128 95 44 b1 02 c0 c0 .D.... 6.2.160 对应的描述符表 usb_device_descriptor ROM_DATA dev_desc = { 0x12, //bDefineLength 0x01, //bDescriptorType,设备描述符 0x10, //bcdUSBL 0x01, //bcdUSBH 0x00, //bDeviceClass 0x00, //bDeviceSubClass 0x00, //bDeviceProtocol 0x08, //bMaxPacketSize0, 包长 0x6e, //idVendorL 0x6e , VID=0x096E 0x09, // 0x09 0x10, //idProductL 0x10 , PID=0xA010 0xa0, // 0xa0 0x05, //bcdDevice , SN=0x2005 0x20, // 0x01, //iManufacturer 0x02, //iProduct 0x00, //iSerialNumber 0x01 //bNumConfigurattions, }; unsigned char cfg_desc [] = { //configuration desc 0x09, // bLength 02, // bDescriptorType, CONFIGURATION,配置描述符 0x1b, // wTotalLength (low byte) 0x00, // wTotalLength (high byte) 0x01, // bNumInterfaces, 接口数 0x01, // bConfigurationValue 0x00, // iConfiguration (none) 0x80, // bmAttributes 0x0f, // bMaxPower (30 mA) //interface desc 0x09, // bLength (Interface1 descriptor starts here) 0x04, // bDescriptorType 0x00, // bInterfaceNumber 0x00, // bAlternateSetting 0x00, // bNumEndpoints (excluding EP0),注意这样配置是不符合规范的,HID设备要求至少一个In endpoint, windows 下可以运行, linux 下不可以 0x03, // bInterfaceClass (HID code) 0x00, // bInterfaceSubClass ( not specified) 0x00, // bInterfaceProtocol ( not specified) 0x00, // iInterface (none) // hid 0x09, // bLength (HID1 descriptor starts here) 0x21, // bDescriptorType, hid 描述 0x00, // bcdHID (low byte) 0x01, // bcdHID (high byte) 0x00, // bCountryCode (none) 0x01, // bNumDescriptors 0x22, // bDescriptorType report 描述 a6 // wDescriptorLength (low byte) 0x00, // wDescriptorLength (high byte) }; 很奇怪没有按要求至少有一个 in endpoint,而windows 下竟然能运行 再看那个很变态的report 表, 去掉那些奇怪的 09 开始的定义, 用dt 表示大致如下 char ReportDescriptor[] = { 0x06, 0xa0, 0xff, // USAGE_PAGE (Vendor Defined Page 1) 0x09, 0x01, // USAGE (Vendor Usage 1) 0xa1, 0x01, // COLLECTION (Application) 0x09, 0x02, // USAGE (Vendor Usage 2) 0xa1, 0x02, // COLLECTION (Logical) 0x06, 0xa0, 0xff, // USAGE_PAGE (Vendor Defined Page 1) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0xff, // LOGICAL_MAXIMUM (255) 0x35, 0x00, // PHYSICAL_MINIMUM (0) 0x45, 0xff, // PHYSICAL_MAXIMUM (255) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x44, // REPORT_COUNT (68) 0xb1,0x02, // 不知道用途 0xc0, // END_COLLECTION 0xc0 // END_COLLECTION }; 即 in/out 的长度都是 0x44* 8 bit, 即68 byte 有了这几个描述表,只要应答正确, 基本上windows系统就能识别出usb hid 设备了,linux 下需要提供一个 in endpoint 才不会出错 windows 下打开文件句柄如下 HANDLE connectToIthUSBHIDDevice (DWORD deviceIndex) { GUID hidGUID; HDEVINFO hardwareDeviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_INTERFACE_DEVICE_DETAIL_DATA deviceDetail; ULONG requiredSize; HANDLE deviceHandle = INVALID_HANDLE_VALUE; DWORD result; //Get the HID GUID value - used as mask to get list of devices HidD_GetHidGuid (&hidGUID); //Get a list of devices matching the criteria (hid interface, present) hardwareDeviceInfoSet = SetupDiGetClassDevs (&hidGUID, NULL, // Define no enumerator (global) NULL, // Define no (DIGCF_PRESENT | // Only Devices present DIGCF_DEVICEINTERFACE)); // Function class devices. deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); //Go through the list and get the interface data result = SetupDiEnumDeviceInterfaces (hardwareDeviceInfoSet, NULL, //infoData, &hidGUID, //interfaceClassGuid, deviceIndex, &deviceInterfaceData); /* Failed to get a device - possibly the index is larger than the number of devices */ if (result == FALSE) { SetupDiDestroyDeviceInfoList (hardwareDeviceInfoSet); return INVALID_HANDLE_VALUE; } //Get the details with null values to get the required size of the buffer SetupDiGetDeviceInterfaceDetail (hardwareDeviceInfoSet, &deviceInterfaceData, NULL, //interfaceDetail, 0, //interfaceDetailSize, &requiredSize, 0); //infoData)) //Allocate the buffer deviceDetail = (PSP_INTERFACE_DEVICE_DETAIL_DATA)malloc(requiredSize); deviceDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); //Fill the buffer with the device details if (!SetupDiGetDeviceInterfaceDetail (hardwareDeviceInfoSet, &deviceInterfaceData, deviceDetail, requiredSize, &requiredSize, NULL)) { SetupDiDestroyDeviceInfoList (hardwareDeviceInfoSet); free (deviceDetail); return INVALID_HANDLE_VALUE; } //Open file on the device deviceHandle = CreateFile (deviceDetail->DevicePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, // no SECURITY_ATTRIBUTES structure OPEN_EXISTING, // No special create flags 0, NULL); // No template file SetupDiDestroyDeviceInfoList (hardwareDeviceInfoSet); free (deviceDetail); return deviceHandle; } HANDLE connectToUSBHIDDevice (DWORD vendorID, DWORD productID, DWORD versionNumber) { HANDLE deviceHandle = INVALID_HANDLE_VALUE; DWORD index = 0; HIDD_ATTRIBUTES deviceAttributes; BOOL matched = FALSE; while (!matched &&index <20 ) { if((deviceHandle = connectToIthUSBHIDDevice (index)) != INVALID_HANDLE_VALUE) { if (!HidD_GetAttributes (deviceHandle, &deviceAttributes)) return INVALID_HANDLE_VALUE; if ((vendorID == 0 || deviceAttributes.VendorID == vendorID) && (productID == 0 || deviceAttributes.ProductID == productID) && (versionNumber == 0 || deviceAttributes.VersionNumber == versionNumber)) return deviceHandle; /* matched */ CloseHandle (deviceHandle); /* not a match - close and try again */ } index++; } return INVALID_HANDLE_VALUE; } 对数据的访问,如果是有 in endpoint 和 out endpoint, 那么用常规的 ReadFile/WriteFile, 如果只有control endpoint, 用DeviceIoControl, 注意in 和out 是从device 的角度定义的 // in: PC ->Device // out: Device->PC 发送数据给usb memset(inbuffer,0,REPORT_PACKET_SIZE+1); inbuffer[0]= 0x0 ; memcpy(inbuffer+1,data); bResult = DeviceIoControl(hDevice,IOCTL_HID_SET_OUTPUT_REPORT, &inbuffer[0], REPORT_PACKET_SIZE+1,&outbuffer[0], 0, &numBytesReturned,(LPOVERLAPPED) NULL); 按规范inbuffer[0] 填 report id, 所以数据长度要0x44+1 接收数据 outbuffer[0] = 0; bResult = DeviceIoControl(hDevice,IOCTL_HID_GET_INPUT_REPORT, &inbuffer[0], 0,&outbuffer[0],0x44, &numBytesReturned,(LPOVERLAPPED) NULL); 按msdn 的文档,outbuffer[0] 填 reportid 按hid 的规范,返回的数据中outbuffer[0]要填reportid, 可是招行的usbkey 好象没有这样做,也能正确解释 linux 下就要严格得多,首先至少要有 中断类型的 in endpoint, 然后数据要严格的第一个byte填 reportid 首先建立hid 的文件节点 #!/bin/sh mkdir -p /dev/usb mknod /dev/usb/hiddev0 c 180 96 mknod /dev/usb/hiddev1 c 180 97 mknod /dev/usb/hiddev2 c 180 98 mknod /dev/usb/hiddev3 c 180 99 mknod /dev/usb/hiddev4 c 180 100 mknod /dev/usb/hiddev5 c 180 101 mknod /dev/usb/hiddev6 c 180 102 mknod /dev/usb/hiddev7 c 180 103 mknod /dev/usb/hiddev8 c 180 104 mknod /dev/usb/hiddev9 c 180 105 mknod /dev/usb/hiddev10 c 180 106 mknod /dev/usb/hiddev11 c 180 107 mknod /dev/usb/hiddev12 c 180 108 mknod /dev/usb/hiddev13 c 180 109 mknod /dev/usb/hiddev14 c 180 110 mknod /dev/usb/hiddev15 c 180 111 发送和接收的程序如下 HANDLE connectToUSBHIDDevice (DWORD vendorID, DWORD productID, DWORD versionNumber) { char evdev[50]; int fd = -1, index; struct hiddev_event ev[64]; struct hiddev_devinfo dinfo; char name[256] = "Unknown"; int matched = 0; const char hid_dir[] = "/dev/usb"; index = 0 ; while(!matched&&index <10) { sprintf(evdev, "%s/hiddev%d",hid_dir, index); if ((fd = open(evdev, O_RDONLY)) > 0) { ioctl(fd, HIDIOCGDEVINFO, &dinfo); if ((vendorID == 0 ||dinfo.vendor == vendorID) && (productID == 0 ||dinfo.product == (__s16)productID) && (versionNumber == 0 || dinfo.version == versionNumber)) return fd; /* matched */ close(fd); /* not a match - close and try again */ } index++ ; } return INVALID_HANDLE_VALUE; } int SendOutputReport(int fd, unsigned char reportID, unsigned char* vals, int num_vals, int delay) { int ret = 0, i; char sval; struct hiddev_report_info out_report; struct hiddev_usage_ref uref; uref.report_type = HID_REPORT_TYPE_OUTPUT; uref.report_id = reportID; uref.field_index = 0; uref.usage_index = 0; /* usage code for this for this usage */ ret = ioctl(fd, HIDIOCGUCODE, &uref); if(0 > ret){ perror("SendCommandFS: ioctl to get usage code for out report"); return ret; } /* fill in usage values. */ for( i = 0; (i < num_vals) && vals; i++){ uref.usage_index = i; sval = vals[i]; uref.value = sval; ioctl(fd, HIDIOCSUSAGE, &uref); if(0 > ret){ perror("SendCommandFS:ioctl to set usage value for out report"); return ret; } } /* tell the driver about the usage values */ out_report.report_type = HID_REPORT_TYPE_OUTPUT; out_report.report_id = reportID; ioctl(fd, HIDIOCGREPORTINFO, &out_report); if (0 > ret) { perror("SendCommandFS: ioctl to get out report info"); return ret; } /* this ioctl puts the report on the wire */ ret = ioctl(fd, HIDIOCSREPORT, &out_report); if (0 > ret) { perror("SendCommandFS: ioctl to send output report"); return ret; } usleep(delay); return ret; } int GetInputReport(int fd, unsigned char reportID, unsigned char *vals, int num_vals, int delay) { int ret = 0, i; /*struct hiddev_usage_ref_multi in_usage_multi;*/ struct hiddev_usage_ref uref; struct hiddev_report_info in_report; /* tell the driver about the usage values */ in_report.report_type = HID_REPORT_TYPE_INPUT; in_report.report_id = reportID; /* this ioctl gets the report on the wire */ ret = ioctl(fd, HIDIOCGREPORT, &in_report); uref.report_type = HID_REPORT_TYPE_INPUT; uref.report_id = reportID; uref.field_index = 0; uref.usage_index = 0; uref.usage_code = 0xff000001; ret = ioctl(fd, HIDIOCGUCODE, &uref); for( i = 0; i < num_vals; i++){ uref.usage_index = i; ioctl(fd, HIDIOCGUSAGE, &uref); usleep(delay); vals[i] = (unsigned char)(uref.value & 0x000000FF) ; } return ret; } 数据好象是一个一个byte 发送, 最后再发一条触发的命令HIDIOCSREPORT 接收也是一样,先发一个控制命令HIDIOCGREPORT,然后一个一个byte 接收,可能都是先缓冲的缘故 |