2020import platform
2121import threading
2222import queue
23- from typing import Optional
23+ import struct
24+ import array
25+ from typing import Optional , Tuple
2426
2527from .interface import Interface
2628from .common import (
27- USB_CLASS_HID ,
2829 filter_device_by_usage_page ,
2930 generate_device_unique_id ,
3031 is_known_cmsis_dap_vid_pid ,
3132 )
3233from ..dap_access_api import DAPAccessIntf
33- from .pyusb_backend import MatchCmsisDapv1Interface
3434from ....utility .compatibility import to_str_safe
3535from ....utility .timeout import Timeout
3636
4040
4141try :
4242 import hid
43- import usb .util
44- try :
45- from libusb_package import find as usb_find
46- except ImportError :
47- from usb .core import find as usb_find
4843except ImportError :
4944 IS_AVAILABLE = False
5045else :
5449_IS_DARWIN = (platform .system () == 'Darwin' )
5550_IS_WINDOWS = (platform .system () == 'Windows' )
5651
52+ if _IS_WINDOWS :
53+ import ctypes
54+ from ctypes import wintypes
55+
56+ # Define necessary Windows structures and functions for HID
57+ class HIDP_CAPS (ctypes .Structure ):
58+ _fields_ = [
59+ ("Usage" , wintypes .USHORT ),
60+ ("UsagePage" , wintypes .USHORT ),
61+ ("InputReportByteLength" , wintypes .USHORT ),
62+ ("OutputReportByteLength" , wintypes .USHORT ),
63+ ("FeatureReportByteLength" , wintypes .USHORT ),
64+ ("Reserved" , wintypes .USHORT * 17 ),
65+ ("NumberLinkCollectionNodes" , wintypes .USHORT ),
66+ ("NumberInputButtonCaps" , wintypes .USHORT ),
67+ ("NumberInputValueCaps" , wintypes .USHORT ),
68+ ("NumberInputDataIndices" , wintypes .USHORT ),
69+ ("NumberOutputButtonCaps" , wintypes .USHORT ),
70+ ("NumberOutputValueCaps" , wintypes .USHORT ),
71+ ("NumberOutputDataIndices" , wintypes .USHORT ),
72+ ("NumberFeatureButtonCaps" , wintypes .USHORT ),
73+ ("NumberFeatureValueCaps" , wintypes .USHORT ),
74+ ("NumberFeatureDataIndices" , wintypes .USHORT ),
75+ ]
76+
77+ hid_dll = ctypes .windll .hid
78+ kernel32_dll = ctypes .windll .kernel32
79+
80+ # HidD_GetPreparsedData
81+ hid_dll .HidD_GetPreparsedData .argtypes = [wintypes .HANDLE , ctypes .POINTER (wintypes .HANDLE )]
82+ hid_dll .HidD_GetPreparsedData .restype = wintypes .BOOLEAN
83+
84+ # HidP_GetCaps
85+ hid_dll .HidP_GetCaps .argtypes = [wintypes .HANDLE , ctypes .POINTER (HIDP_CAPS )]
86+ hid_dll .HidP_GetCaps .restype = ctypes .c_long # NTSTATUS
87+
88+ # HidD_FreePreparsedData
89+ hid_dll .HidD_FreePreparsedData .argtypes = [wintypes .HANDLE ]
90+ hid_dll .HidD_FreePreparsedData .restype = wintypes .BOOLEAN
91+
92+ # CreateFileW
93+ kernel32_dll .CreateFileW .argtypes = [wintypes .LPCWSTR , wintypes .DWORD , wintypes .DWORD , ctypes .c_void_p , wintypes .DWORD , wintypes .DWORD , wintypes .HANDLE ]
94+ kernel32_dll .CreateFileW .restype = wintypes .HANDLE
95+
96+ # CloseHandle
97+ kernel32_dll .CloseHandle .argtypes = [wintypes .HANDLE ]
98+ kernel32_dll .CloseHandle .restype = wintypes .BOOL
99+
100+ INVALID_HANDLE_VALUE = - 1
101+ GENERIC_READ = 0x80000000
102+ GENERIC_WRITE = 0x40000000
103+ FILE_SHARE_READ = 1
104+ FILE_SHARE_WRITE = 2
105+ OPEN_EXISTING = 3
106+
107+ elif _IS_DARWIN :
108+ import ctypes
109+ import ctypes .util
110+
111+ iokit = ctypes .cdll .LoadLibrary (ctypes .util .find_library ('IOKit' ))
112+
113+ kCFStringEncodingUTF8 = 0x08000100
114+
115+ # Define CFString functions
116+ CFStringCreateWithCString = iokit .CFStringCreateWithCString
117+ CFStringCreateWithCString .restype = ctypes .c_void_p
118+ CFStringCreateWithCString .argtypes = [ctypes .c_void_p , ctypes .c_char_p , ctypes .c_uint32 ]
119+
120+ CFRelease = iokit .CFRelease
121+ CFRelease .restype = None
122+ CFRelease .argtypes = [ctypes .c_void_p ]
123+
124+ # Define IOHIDDevice functions
125+ IOHIDDeviceGetProperty = iokit .IOHIDDeviceGetProperty
126+ IOHIDDeviceGetProperty .restype = ctypes .c_void_p
127+ IOHIDDeviceGetProperty .argtypes = [ctypes .c_void_p , ctypes .c_void_p ]
128+
129+ # Define CFNumber functions
130+ CFNumberGetValue = iokit .CFNumberGetValue
131+ CFNumberGetValue .restype = ctypes .c_bool
132+ CFNumberGetValue .argtypes = [ctypes .c_void_p , ctypes .c_long , ctypes .c_void_p ]
133+ kCFNumberSInt32Type = 3
134+
135+ # Keys for IOHIDDeviceGetProperty
136+ def _create_cfstring (s ):
137+ return CFStringCreateWithCString (None , s .encode ('utf-8' ), kCFStringEncodingUTF8 )
138+
139+ kIOHIDMaxInputReportSizeKey = _create_cfstring ("MaxInputReportSize" )
140+ kIOHIDMaxOutputReportSizeKey = _create_cfstring ("MaxOutputReportSize" )
141+
142+
57143class HidApiUSB (Interface ):
58144 """@brief CMSIS-DAP USB interface class using hidapi backend."""
59145
@@ -91,48 +177,14 @@ def set_packet_count(self, count):
91177 self .packet_count = count
92178
93179 def open (self ):
180+ probe_id = self .serial_number or f"VID={ self .vid :#06x} :PID={ self .pid :#06x} "
94181
95- # Use pyUSB to get HID Interrupt EP wMaxPacketSize, since hidapi is not reliable
96-
97- # Get device handle.
98- # If multiple identical (same PID & VID) probes without serial number are connected,
99- # assume they share the same wMaxPacketSize.
100-
101- usb_serial = self .device_info ['serial_number' ]
102-
103- kwargs = {'idVendor' : self .vid , 'idProduct' : self .pid }
104- if usb_serial : # only pass a real USB serial
105- kwargs ['serial_number' ] = usb_serial
106-
107- probe_id = usb_serial or f"VID={ self .vid :#06x} :PID={ self .pid :#06x} "
108-
109- dev = usb_find (** kwargs )
110- if dev is None :
111- raise DAPAccessIntf .DeviceError (f"Probe { probe_id } not found" )
112-
113- # Get active config
114- config = dev .get_active_configuration ()
115-
116- # Get count of HID interfaces and create the matcher object
117- hid_interface_count = len (list (usb .util .find_descriptor (config , find_all = True , bInterfaceClass = USB_CLASS_HID )))
118- matcher = MatchCmsisDapv1Interface (hid_interface_count )
119-
120- # Get CMSIS-DAPv1 interface
121- interface = usb .util .find_descriptor (config , custom_match = matcher )
122- if interface is None :
123- raise DAPAccessIntf .DeviceError (f"Probe { probe_id } has no CMSIS-DAPv1 interface" )
124-
125- # Set report sizes, assuming HID report size matches endpoint wMaxPacketSize.
126- for endpoint in interface :
127- if usb .util .endpoint_type (endpoint .bmAttributes ) == usb .util .ENDPOINT_TYPE_INTR :
128- if endpoint .bEndpointAddress & usb .util .ENDPOINT_IN :
129- self .report_in_size = endpoint .wMaxPacketSize
130- else :
131- self .report_out_size = endpoint .wMaxPacketSize
182+ self .report_in_size , self .report_out_size = self ._get_hid_report_sizes ()
132183
133184 if self .report_in_size is None :
134- raise DAPAccessIntf .DeviceError (
135- f"Could not determine packet sizes for probe { probe_id } " )
185+ # Fallback: set report size to 64 bytes
186+ LOG .warning ("Could not determine IN report size for probe %s, defaulting to 64 bytes" , probe_id )
187+ self .report_in_size = 64
136188
137189 if self .report_out_size is None :
138190 # No interrupt OUT endpoint. Out reports will be sent via control transfer.
@@ -156,6 +208,74 @@ def open(self):
156208
157209 self .closed = False
158210
211+ def _get_hid_report_sizes (self ) -> Tuple [Optional [int ], Optional [int ]]:
212+ """Get actual HID report sizes using platform-specific APIs."""
213+ if _IS_WINDOWS :
214+ return self ._get_hid_report_sizes_windows ()
215+ elif _IS_DARWIN :
216+ return self ._get_hid_report_sizes_macos ()
217+ else :
218+ # Linux and other OSes are not supported for this mechanism.
219+ LOG .debug ("Unsupported OS for getting HID report sizes directly." )
220+ return None , None
221+
222+ def _get_hid_report_sizes_windows (self ) -> Tuple [Optional [int ], Optional [int ]]:
223+ try :
224+ path = to_str_safe (self .device_info ['path' ])
225+ handle = kernel32_dll .CreateFileW (path , GENERIC_READ | GENERIC_WRITE , FILE_SHARE_READ | FILE_SHARE_WRITE , None , OPEN_EXISTING , 0 , None )
226+ except Exception as exc :
227+ LOG .error ("Exception opening device handle for HID report size query: %s" , exc )
228+ return None , None
229+
230+ if handle == INVALID_HANDLE_VALUE :
231+ LOG .debug ("Failed to open device handle for HID report size query." )
232+ return None , None
233+
234+ try :
235+ preparsed_data = wintypes .HANDLE ()
236+ if not hid_dll .HidD_GetPreparsedData (handle , ctypes .byref (preparsed_data )):
237+ LOG .debug ("HidD_GetPreparsedData failed." )
238+ return None , None
239+
240+ try :
241+ caps = HIDP_CAPS ()
242+ HIDP_STATUS_SUCCESS = 0x00110000
243+ if hid_dll .HidP_GetCaps (preparsed_data , ctypes .byref (caps )) == HIDP_STATUS_SUCCESS :
244+ # The lengths include the report ID byte, which we don't use in the packet size.
245+ # However, the packet size should match the report size.
246+ return caps .InputReportByteLength , caps .OutputReportByteLength
247+ else :
248+ LOG .debug ("HidP_GetCaps failed." )
249+ return None , None
250+ finally :
251+ hid_dll .HidD_FreePreparsedData (preparsed_data )
252+ finally :
253+ kernel32_dll .CloseHandle (handle )
254+ return None , None
255+
256+ def _get_hid_report_sizes_macos (self ) -> Tuple [Optional [int ], Optional [int ]]:
257+ # The hid.device object from hidapi is what we need.
258+ # The underlying object is an IOHIDDeviceRef, which is a void*
259+ hid_device_ref = ctypes .c_void_p (self .device .dev )
260+ input_size = None
261+ output_size = None
262+
263+ # Get input report size
264+ value_ref = IOHIDDeviceGetProperty (hid_device_ref , kIOHIDMaxInputReportSizeKey )
265+ if value_ref :
266+ val = ctypes .c_int32 (0 )
267+ if CFNumberGetValue (value_ref , kCFNumberSInt32Type , ctypes .byref (val )):
268+ input_size = val .value
269+
270+ # Get output report size
271+ value_ref = IOHIDDeviceGetProperty (hid_device_ref , kIOHIDMaxOutputReportSizeKey )
272+ if value_ref :
273+ val = ctypes .c_int32 (0 )
274+ if CFNumberGetValue (value_ref , kCFNumberSInt32Type , ctypes .byref (val )):
275+ output_size = val .value
276+
277+ return input_size , output_size
278+
159279 def rx_task (self ):
160280 try :
161281 while not self .closed_event .is_set ():
0 commit comments