News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

How to open a USB port?

Started by BlueMR2, February 04, 2015, 04:53:02 AM

Previous topic - Next topic

BlueMR2

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
My Code Site
https://github.com/BrianKnoblauch

dedndave

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

i can imagine that "Access Denied" is what you might see if you try to use the wrong mode or speed

dedndave

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

adeyblue

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.

BlueMR2

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.  :-)
My Code Site
https://github.com/BrianKnoblauch

BlueMR2

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...  :-)
My Code Site
https://github.com/BrianKnoblauch