I was recently reminded of a project I worked on a couple years ago. It involved a credit card swiper (attached via USB) that we were never able to get working in our system. The demo software with it worked, but when I attempted to follow the instructions on writing my own code to interface (so I could build the feature in), I would fail.
Originally I wrote it in Java (with JNA to access the supplied DLLs). It would always fail with "Access Denied" after detection when I would try to read the port. So, I decided to drop down to Assembly for better control over the process. Same results on the open (ERROR #5 - Access Denied). The detection code seems to run great, it just fails to read once found. I did try to use a USB debugging package (I believe it was called "snoopy"?), but that would make my machine lockup when I ran the code.
While the project is now abandoned (due to other changes making it obsolete), I'm still curious as to what I was doing wrong... I presume I'm just missing something really silly that one has to do special to open a USB port? Any USB experts (or others interested in a mystery) here?
Code is publically available via: https://github.com/ServiceSpring/CreditCardReader/blob/master/main.asm and is also included below.
; いいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
include \masm32\include\masm32rt.inc
; いいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
comment * -----------------------------------------------------
Build this console app with
"MAKEIT.BAT" on the PROJECT menu.
----------------------------------------------------- *
include \masm32\include\hid.inc
include \masm32\include\setupapi.inc
includelib \masm32\lib\hid.lib
includelib \masm32\lib\setupapi.lib
VendorID EQU 0801h
ProductID EQU 0002h
SP_DEVICE_INTERFACE_DATA struct
CbSize DWORD ?
ClassGuid GUID <>
Flags DWORD ?
Reserved ULONG ?
SP_DEVICE_INTERFACE_DATA ends
HIDD_ATTRIBUTES struct
HSize DWORD ?
VendorID WORD ?
ProductID WORD ?
VersionNumber WORD ?
HIDD_ATTRIBUTES ends
HIDP_CAPS struct
Usagea USHORT ?
UsagePage USHORT ?
InputReportByteLength USHORT ?
OutputReportByteLength USHORT ?
FeatureReportByteLength USHORT ?
Reserved USHORT 17 dup (?)
NumberLinkCollectionNodes USHORT ?
NumberInputButtonCaps USHORT ?
NumberInputValueCaps USHORT ?
NumberInputDataIndices USHORT ?
NumberOutputButtonCaps USHORT ?
NumberOutputValueCaps USHORT ?
NumberOutputDataIndices USHORT ?
NumberFeatureButtonCaps USHORT ?
NumberFeatureValueCaps USHORT ?
NumberFeatureDataIndices USHORT ?
HIDP_CAPS ends
.data
ReportBuffer DWORD ?
HandleToDevice HANDLE ?
HDevInfo HANDLE ?
HIDHandle HANDLE ?
HID_GUID GUID <>
DeviceInterfaceData SP_DEVICE_INTERFACE_DATA <>
HIDAttributes HIDD_ATTRIBUTES <>
HIDCapabilities HIDP_CAPS <>
OverlappedBuffer OVERLAPPED <>
.code
start:
; いいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
call DetectDevice
xor ebx, ebx
mov bx, HIDCapabilities.InputReportByteLength
mov ReportBuffer, halloc(ebx)
invoke ReadFile, HIDHandle, ReportBuffer, ebx, NULL, ADDR OverlappedBuffer ; TODO ERROR #5 - Access Denied
hfree ReportBuffer
invoke CloseHandle, HIDHandle
exit
; いいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
DetectDevice proc
local index:dword
local DeviceInterfaceDetailData:dword
local ReqLength:dword
local HIDPreparsedData:dword
invoke HidD_GetHidGuid, ADDR HID_GUID ; Get the GUID for all system USB HID devices
invoke SetupDiGetClassDevs, ADDR HID_GUID, NULL, NULL, 12h ; Receive a handle to the device information set for all installed devices
; 12h = DIGCF_DEVICEINTERFACE (10h) | DIGCF_PRESENT (02h)
mov HDevInfo, eax
.if eax == INVALID_HANDLE_VALUE
print "Unable to get device information set handle",13,10,0
exit
.endif
mov index, 0
detection_start:
mov HandleToDevice, INVALID_HANDLE_VALUE
mov DeviceInterfaceData.CbSize, SIZEOF DeviceInterfaceData
invoke SetupDiEnumDeviceInterfaces, HDevInfo, NULL, ADDR HID_GUID, index, ADDR DeviceInterfaceData ; Query the device using the index to get the interface data
.if eax == 0 ; If no more HID devices at the root hub - LastError = ERROR_NO_MORE_ITEMS
jmp search_loop_exit ; Go out from the device search loop
.endif
invoke SetupDiGetDeviceInterfaceDetail, HDevInfo, ADDR DeviceInterfaceData, NULL, 0, ADDR ReqLength, NULL ; Obtain the length of the detailed data structure
mov DeviceInterfaceDetailData, halloc(ReqLength) ; Allocate memory for SP_DEVICE_INTERFACE_DETAIL_DATA structure
mov eax, 5 ; and set appropriate length for each device path
mov ebx, DeviceInterfaceDetailData
mov [ebx], eax
invoke SetupDiGetDeviceInterfaceDetail, HDevInfo, ADDR DeviceInterfaceData, DeviceInterfaceDetailData, ReqLength, ADDR ReqLength, NULL
.if eax == 0
print "Possible ERROR_INVALID_USER_BUFFER (cbSize/OS conflict)?",13,10,0
.endif
mov eax, DeviceInterfaceDetailData
add eax, 4
mov ebx, FILE_SHARE_READ
or ebx, FILE_SHARE_WRITE
invoke CreateFile, eax, 0, ebx, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL
mov HandleToDevice, eax
.if eax == INVALID_HANDLE_VALUE
print "Bad handle on create file",13,10,0
.endif
mov HIDAttributes.HSize, SIZEOF HIDAttributes ; Create HIDD_ATTRIBUTES structure
invoke HidD_GetAttributes, HandleToDevice, ADDR HIDAttributes ; Fill HIDD_ATTRIBUTES structure
.if eax == 0
print "Unable to build HIDD_ATTRIBUTES",13,10,0
.endif
.if HIDAttributes.VendorID == VendorID && HIDAttributes.ProductID == ProductID
print "Device found",13,10,0
jmp search_loop_exit
.else
invoke CloseHandle, HandleToDevice
.endif
inc index ; Check the next HID device for the valid VID and PID
jmp detection_start
search_loop_exit:
invoke SetupDiDestroyDeviceInfoList, HDevInfo
mov eax, HandleToDevice ; Save Handle to the opened device
mov HIDHandle, eax
.if eax == INVALID_HANDLE_VALUE
print "Device not found",13,10,0
.else
invoke HidD_GetPreparsedData, HandleToDevice, ADDR HIDPreparsedData
invoke HidP_GetCaps, HIDPreparsedData, ADDR HIDCapabilities
.endif
hfree DeviceInterfaceDetailData
ret
DetectDevice endp
; いいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
end start
it depends on whether or not you have any control over the USB hardware design
a USB device can interface as either a serial device (accessible like RS-232) or high-speed mode
the simplest way is to use the serial mode
you can open the device with OpenFile, using a name like, "COM1:", etc
(i think USB 2.0 supported up to 115200 baud)
but - your code has to be compatible with the (usually hard-wired) mode of the hardware :P
https://msdn.microsoft.com/en-us/library/windows/hardware/ff538930%28v=vs.85%29.aspx (https://msdn.microsoft.com/en-us/library/windows/hardware/ff538930%28v=vs.85%29.aspx)
i can imagine that "Access Denied" is what you might see if you try to use the wrong mode or speed
some code i used to initialize a serial-mode USB port...
CInit1: push ebx
dec eax
mov esi,offset sdcb
mov ecx,sizeof DCB
xchg eax,ebx
cmp ecx,[esi].DCB.DCBlength
mov edi,offset scti
jz CInit2
mov [esi].DCB.DCBlength,ecx
INVOKE GetCommState,ebx,esi
xor eax,eax ;NOPARITY = ONESTOPBIT = 0
mov [esi].DCB.BaudRate,CBR_115200 ; Baud Rate: 115200
mov [esi].DCB.Parity,al ; Parity: None
mov [esi].DCB.StopBits,al ; Stop Bits: 1
mov [esi].DCB.ByteSize,8 ; Data Bits: 8
inc eax
mov [esi].DCB.fbits,BITRECORD<NULL,FALSE,RTS_CONTROL_DISABLE,FALSE,FALSE,\
FALSE,FALSE,FALSE,FALSE,DTR_CONTROL_DISABLE,FALSE,FALSE,FALSE,TRUE>
mov [edi].COMMTIMEOUTS.WriteTotalTimeoutConstant,eax
mov [edi].COMMTIMEOUTS.WriteTotalTimeoutMultiplier,eax
mov [edi].COMMTIMEOUTS.ReadTotalTimeoutMultiplier,eax
mov [edi].COMMTIMEOUTS.ReadIntervalTimeout,eax
CInit2: mov [edi].COMMTIMEOUTS.ReadTotalTimeoutConstant,70
INVOKE SetCommState,ebx,esi
INVOKE SetCommTimeouts,ebx,edi
pop eax
xchg eax,ebx
the device has to be set up for the same rate, start/stop bits, etc
Quote from: BlueMR2 on February 04, 2015, 04:53:02 AM
invoke SetupDiGetDeviceInterfaceDetail, HDevInfo, ADDR DeviceInterfaceData, NULL, 0, ADDR ReqLength, NULL ; Obtain the length of the detailed data structure
mov DeviceInterfaceDetailData, halloc(ReqLength) ; Allocate memory for SP_DEVICE_INTERFACE_DETAIL_DATA structure
mov eax, 5 ; and set appropriate length for each device path
mov ebx, DeviceInterfaceDetailData
mov [ebx], eax
invoke SetupDiGetDeviceInterfaceDetail, HDevInfo, ADDR DeviceInterfaceData, DeviceInterfaceDetailData, ReqLength, ADDR ReqLength, NULL
.if eax == 0
print "Possible ERROR_INVALID_USER_BUFFER (cbSize/OS conflict)?",13,10,0
.endif
mov eax, 5
probably wants to be
mov eax, ReqLength
sub eax, 4
since 5 would only give you the first 4 chars of the device path. That's assuming Microsoft coded it defensively and took the smaller of DeviceInterfaceDetailData.cbSize and the SetupDiGetDeviceInterfaceDetail buffer length parameter as the real size of the buffer.
It also looks like there's a memory leak in the detection loop. halloc is used every time around, but hfree only happens when the function is returning.
Quote from: dedndave on February 04, 2015, 05:42:54 AM
the simplest way is to use the serial mode
IIRC, that method isn't supported for this particular device. I'll have to go scrounge up the spec sheet again though. Might be worth a try anyways. Wouldn't be the first time documentation was just wrong. :-)
Quote from: adeyblue on February 04, 2015, 06:14:08 AM
mov eax, 5
probably wants to be
mov eax, ReqLength
sub eax, 4
since 5 would only give you the first 4 chars of the device path. That's assuming Microsoft coded it defensively and took the smaller of DeviceInterfaceDetailData.cbSize and the SetupDiGetDeviceInterfaceDetail buffer length parameter as the real size of the buffer.
It also looks like there's a memory leak in the detection loop. halloc is used every time around, but hfree only happens when the function is returning.
Good catch on the memory leak. Never turned into a "thing" since this was just test code, but I'll update that as a TODO, should I happen to reuse this code for a production application!
ReqLength does make more logical sense. Why I used the magic number "5" there, I can't recall. My best guess is I found that in someone else's sample, or perhaps even some detection documentation? The detection does work, I do find my device. I'll throw that in as a TODO as well. Even if ReqLength always ends up being 5, I hate magic numbers and should change it to ReqLength... :-)