CreateFileA fails to open HID device in Windows - windows

EDIT: Issue reported here: https://github.com/signal11/hidapi/issues/276
Inkling is a pen-device from Wacom. (InklingReader) is an open source project that gets real-time data from it.
I'm trying to tidy up InklingReader to use HIDAPI rather than libusb (as it works at higher level: HID rather than raw USB, so is much more compact & suitable. Also libusb fails on recent OSX).
HID API a small lib: one .h, one (per-platform) .c.
My code looks like this:
unsigned short inklingVendorId = 0x056a, inklingProductId = 0x0221;
if (hid_init() == FAIL) return;
handle = hid_open(inklingVendorId, inklingProductId, nullptr);
On Windows hid_open fails. Single stepping reveals the fail-point here:
// path = "\\\\?\\hid#vid_056a&pid_0221&mi_00&col01#8&1ea90857&0&0000#"
// "{4d1e55b2-f16f-11cf-88cb-001111000030}"
//
static HANDLE open_device(const char *path, BOOL enumerate)
{
HANDLE handle;
DWORD desired_access = (enumerate)? 0: (GENERIC_WRITE | GENERIC_READ);
DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
// enumerate = 0
handle = CreateFileA(path,
desired_access,
share_mode,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/
0);
int err = GetLastError(); // 5 i.e. ERROR_ACCESS_DENIED
return handle; // returns 0xffffffff i.e. INVALID_HANDLE
}
Now the HIDAPI author says "HIDAPI won't work with keyboards and mice on Windows. Windows as a security measure doesn't allow the opening of Mouse and Keyboard HIDs." (here)
And if I enumerate HID devices:
struct hid_device_info *devs, *cur_dev;
devs = hid_enumerate(inklingVendorId, inklingProductId);
cur_dev = devs;
while (cur_dev) {
DBG2("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
DBG2("");
DBG2(" Manufacturer: %ls", cur_dev->manufacturer_string);
DBG2(" Product: %ls", cur_dev->product_string);
DBG2(" Release: %hx", cur_dev->release_number);
DBG2(" Interface: %d", cur_dev->interface_number);
DBG2(" Usage Page: %d", cur_dev->usage_page);
DBG2(" Usage: %d", cur_dev->usage);
DBG2("");
cur_dev = cur_dev->next;
}
hid_free_enumeration(devs);
... I get not one but TWO entries:
Device Found
type: 056a 0221
path: \\?\hid#vid_056a&pid_0221&mi_00&col01#8&1ea90857&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
serial_number: 2B0400001C90C22A0002DD07FE8B022A
Manufacturer: Wacom, Inc.
Product: MSC Device
Release: 1256
Interface: 0
Usage Page: 1
Usage: 2
Device Found
type: 056a 0221
path: \\?\hid#vid_056a&pid_0221&mi_00&col02#8&1ea90857&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
serial_number: 2B0400001C90C22A0002DD07FE8B022A
Manufacturer: Wacom, Inc.
Product: MSC Device
Release: 1256
Interface: 0
Usage Page: 13
Usage: 2
(Note: OSX only reports the SECOND entry! On OSX there is no problem!)
Comparing path:
path: \?\hid#vid_056a&pid_0221&mi_00&col01#8&1ea90857&0&0000#...
path: \?\hid#vid_056a&pid_0221&mi_00&col02#8&1ea90857&0&0001#...
As per http://www.usb.org/developers/hidpage/Hut1_12v2.pdf,
UsagePage/Usage = 1/2 = {Generic Desktop Controls}/{Mouse}.
UsagePage/Usage = 13/2 = {Digitizers}/{Pen}.
(EDIT: Sometimes the first path is the 1/2 and the second is the 13/2, other times it's swapped).
And HIDAPI is only taking the first one it finds.
So it looks like this should be the solution. The Inkling was exposing 2 'devices' and hidapi was taking the wrong (mouse) one, and Windows doesn't allow access to Mouse or Keyboard Devices.
So I tweak the code...
while (cur_dev) {
if (cur_dev->vendor_id == vendor_id &&
cur_dev->product_id == product_id &&
cur_dev->usage_page == 13)
{
... to get the correct entry, it should work right?
Nope, CreateFileA just raises a different error:
usage_page== 1 => Error code 5 (ERROR_ACCESS_DENIED)
usage_page==13 => Error code 32 (ERROR_SHARING_VIOLATION)
Meh. This is rather upsetting. I seem to be at a dead-end!
I've tried fiddling with CreateFileA's params, e.g. replacing GENERIC_READ | GENERIC_WRITE with STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE -- now it happily creates a handle. But subsequent hid_read-s fail to collect any data.
Googling, https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/af869f90-7bda-483d-ba2d-51680073fe9f/createfile-returns-invalid-handle-while-trying-to-access-hid-device-on-windows-8-desktop-app?forum=wdk seems to contain a couple of suggested workarounds:
both toaster and firefly can work in the HID stack. toaster shows how
to address the filter through a raw PDO, firefly shows how to access
it with WMI. From a C perspective, I think the raw PDO is much simpler
to code to, WMI is a bit nasty and complicated.
firefly
toaster
The author is recommending something in toaster, but it is a big CodeBase and I don't have experience with Windows Driver programming.
It looks as though I'm going to have to dig through a lot of very unfamiliar territory to get anything working, so before a start out I am asking here. If nobody answers and I figure it out, I will answer my own question.
The only other thing I can think of it is that maybe another process is already engaging this path. Maybe if I can terminate this process, the CreateFileA might succeed? Roel's libusb approach involves detaching kernel driver: https://github.com/roelj/inklingreader/blob/master/src/usb/online-mode.c#L98
PS Somewhere I read that if another process has already opened this device, our open has to match the permissions of this previous open. And I also read that Windows automatically opens all HID Devices upon detection.
Find out which process has an exclusive lock on a USB device handle
PPS maybe one idea is to try an alternative HID lib What is the best usb library to communicate with usb HID devices on Windows?
PPPS maybe I need to run my code as admin. But that's not a good solution.

I have seen similar behavior. The ERROR_SHARING_VIOLATION problem started to occur after upgrading to Windows 10 Anniversary Edition. The problem is only seen for USB HID devices connected when Windows is started. If you unplug and plug the USB device after Windows has started then CreateFile is successful. I haven't yet found a root cause or a solution.

You're right: ERROR_SHARING_VIOLATION will occur if some other app already opened this device. You need to call CreateFileW API like this:
DWORD desired_access = GENERIC_WRITE | GENERIC_READ;
DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
::CreateFileW(deviceInterfacePath, desired_access, share_mode, 0, OPEN_EXISTING, 0, 0);
If you don't provide dwShareMode then it means that you're trying to open device exclusively. Which can fail if other app (new Windows version that maybe supports these kind of devices natively) already opened this device for its use.
Note about Keyboard and Mouse devices: you can also call ::CreateFileW without even setting desired_access (use zero value): in this case you can use HidD_GetManufacturerString/HidD_GetProductString/HidD_GetSerialNumberString/HidD_GetAttributes (and maybe some others) HID methods with returned handle. But you cannot read/write data to such device. This should be useful if you need to acquire name or VID/PID for HID keyboard/mouse.
Here is list of HID device types and their access modes on Windows.

Related

Is there a way to find out which USB interface Mac OS has a driver attached to?

I'm trying to debug a libusb function called libusb_kernel_driver_active(). It tells if a USB device has an operating system driver attached to it. It takes an interface number as one of its arguments. I want to be able to find out which interface the operating system's driver is currently attached to. Is there a way to do this using a terminal command or IOKit?
I have tried IORegistryExplorer but it doesn't list this information.
ioreg does not appear to list USB interface information.
Maybe someone out there knows of an IOKit function that can tell us the information we want.
To find this information out, open the IORegistryExplorer program (spotlight can find it). Then select IOService from the drop down menu in the upper left corner of the window. In the search bar type the name of your device. If it is found select in on the tree below. Then delete all text from the search field to see all the available fields.
The IORegistryExplorer answer is correct, but as the question hints at, there are other ways to obtain the information, both on the Terminal and programmatically.
Terminal (ioreg)
The command
ioreg -irc IOUSBDevice
Will list all USB devices detected by the system, as well as any client objects in the I/O Registry. This means either the driver client directly (kext, dext, or user space) or the IOUSBInterface objects representing the different interfaces on a composite device, and the driver clients attached to each of those.
You can search by name instead of class type by using the -n option instead of -c but often the USB-level name doesn't match the retail name of the device, or is somehow abbreviated. It's usually easier to inspect all USB devices manually.
Programmatically
In your program, you can search for all USB devices in the system using something like this:
io_iterator_t device_iter = IO_OBJECT_NULL;
IOReturn ret = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(kIOUSBDeviceClassName), &device_iter);
if (ret == kIOReturnSuccess && device_iter != IO_OBJECT_NULL)
{
while (io_service_t device = IOIteratorNext(device_iter))
{
io_name_t device_name = "";
IORegistryEntryGetName(device, device_name);
// do something with device_nameā€¦
}
}
Once you have selected your device, you can then use IORegistryEntryCreateIterator() to iterate over its child objects (possibly recursively), which should allow you to identify the driver being used or obtain any other information you require.

SetupComm, SetCommState, SetCommTimeouts fail with USB device

i am opening a USB device:
for communication using CreateFile:
HANDLE hUsb = CreateFile("\\.\LCLD9",
GENERIC_READ | GENERIC_WRITE,
0,
null,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
The call succeeds (i.e. hUsb is not equal to INVALID_HANDLE_VALUE). But then it comes time to do what we do with every serial port:
SetupComm (set receive and transit buffer sizes)
SetCommState (set flow-control, baud rate, etc)
SetCommTimeouts (set timeouts)
Each of these calls returns a GetLastError() code of 1. E.g.:
SetupComm(hUsb, 1024, 1024);
Why are operations to configure the serial device failing when using a "USB" serial device, but work when using a "virtual COM port"? Do USB devices not support such baud rates, buffers, flow control, and timeouts?
If this is a limitation/feature of Universal Serial devices, how can i detect that a handle refers to a "Universal Serial Device", rather than a "COMM Port"? For example, the user is the one who specifies which port to use:
\.\COM5
\.\LCLD9
Other serial functions that fail when talking to Universal Serial Bus serial device:
GetCommModemStatus (with error code 1)
ReadFile (with error code 6)
PurgeComm (with error code 6)
WriteFile (with error code 6)
Which begs the larger question, how do you communicate with a USB device once it's been opened with CreateFile?
No, USB devices do not use these things. If your device is an actual USB-to-RS232 (or other slow serial), then you should be opening the COM port it associated with. Let the drivers handle the work of sending that data.
USB communication is not like COM ports. You can think of it more as an external PCI bus than a simple send-whatever-data-you-want line.
Turns out that i don't have to do anything with Comm, because it's not a COM port. The reason my WriteFile was failing was because i was attempting to write to \\.\LCLD9 rather than \\.\LCLD9\.
The trailing backslash is critical; even though CreateFile returns success both ways.
void WriteToDisplay(String s)
{
//Open the display
var hLineDisplay = CreateFile("\\.\LCLD9\", GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0);
//Write the command
DWORD bytesWritten;
WriteFile(hLineDisplay, s, s.Length, ref bytesWritten, nil);
FileClose(hLineDisplay);
}
Anyone using Logic Controls LD9000 USB Line Display, the above is how you write to the display.
After reverse engineering their .NET Line Display driver i will also mention that the name of the port you use, e.g.:
\\.\LCLD9\
\\.\LCPD6\
\\.\LCPD3\
can be inferred from the full devicePath returned using the Windows Setup APIs. For example, my pole display's full device path is:
\\?\USB#VID_0FA8&PID_A090#6&DF2EE03&0&1#{A5DCBF10-6530-11D2-901F-00C04FB951ED}
\______/
|
ProductID
The rule is to check the device path for Product IDs. In my case PID_A090 means it will be available as file \\.\LCLD9\. Other product IDs and their associated file paths:
Contains DeviceName (trailing backslash is not optional)
======== ===============================================
PID_A030 \\.\LCPD3\
PID_A060 \\.\LCPD6\
PID_A090 \\.\LCLD9\
Note: Any code is released into the public domain. No attribution required.

IoGetDevicePropertyData() returns STATUS_OBJECT_NAME_NOT_FOUND

I'm updating a functioning KMDF driver for a PCI device, using WinDDK 7600.16385.1 and OSR's ddkbuild.cmd, targeting WLH, testing on Win7 x86 and x64.
I'm attempting to retrieve the DEVPKEY_Device_LocationPaths property.
The Device Manager Device Properties Details tab displays the expected value in "Location Paths" ...
PCIROOT(0)#PCI(1C00)#PCI(0000)#PCI(0200)#PCI(0000)#PCI(0900)#PCI(0000)#PCI(0400)
... but calling IoGetDevicePropertyData() from the EvtDriverDeviceAdd handler ...
PDEVICE_OBJECT pWdmPDO = WdfFdoInitWdmGetPhysicalDevice( pDeviceInit );
[... WdfDeviceCreate succeeds ...]
WCHAR wszLocationStrings[128] = { 0 }; // Temporary, just to confirm DDI works
ULONG ulRequiredSize = 0;
DEVPROPTYPE devPropType = 0;
status = IoGetDevicePropertyData( pWdmPDO,
&DEVPKEY_Device_LocationPaths,
LOCALE_NEUTRAL,
/*ulFlags*/ 0,
sizeof(wszLocationStrings),
wszLocationStrings,
&ulRequiredSize,
&devPropType );
... always returns STATUS_OBJECT_NAME_NOT_FOUND.
I have tried ...
calling IoGetDevicePropertyData() for other DEVPKEY_Device_* values. The result is the same.
calling IoGetDevicePropertyData() in the EvtDevicePrepareHardware handler. The result is the same.
calling WdfDeviceWdmGetPhysicalDevice() to retrieve the PDO. The result is the same
WdfDeviceAllocAndQueryProperty(). It works correctly but does not provide the info I require.
WdfFdoQueryForInterface( GUID_PNP_LOCATION_INTERFACE ). It works correctly but only provides the current node (i.e. "PCI(0400)")
Searching for sample code that calls IoGetDevicePropertyData. I found the Windows CDROM Storage Class Driver sample but it doesn't appear to do anything I haven't tried already.
So ... what am I missing?
I found a MS PnP support email address in a WinHEC 2006 powerpoint and received a response from Microsoft ...
On Vista and Windows 7, IoGetDevicePropertyData cannot retrieve all the DEVPKEY
properties that user-mode can. There is no way for you to directly retrieve that
full path as created by the operating system from kernel mode.
For further questions, the Windows Hardware WDK and Driver Development forum
(http://social.msdn.microsoft.com/Forums/en-US/wdk/threads ) is a good place to
ask driver development questions.
... to which I responded ...
Thank you very much to your quick response!
It would be very helpful if the IoGetDevicePropertyData online docs could be updated
to include a list of the DEVPKEY properties that are supported and restrictions on
their availability (i.e. only after Start IRP completes as noted in the WinHEC
powerpoint)
And thanks for the link to the forum, I'll start there next time.

Getting the BSD name of a USB device using IOKit to write to the device?

I am trying to determine the BSD name of virtual serial port using IOKit under MacOS.
I have a USB CDC device that looks like a virtual serial port, and I want to get the BSD device path so that I can just do a fopen("/dev/tty.usbmodem123"). I have a program that takes the VID and PID and waits for the device to be plugged in, and then I want to use the BSD name write to the device. The device mounts differently on every system and I am trying to use this as a teaching tool, so I need to search for the device before I write to it without manually inspecting /dev/tty.* for where the device mounted.
I have 3 questions.
Firstly, can one get the BSD name of a virtual serial port using CFSTR(kIOBSDNameKey)?
IORegistryEntrySearchCFProperty() and FindProp() always return "null". Does anyone know if the BSD name can be returned by a non-block device?
I am currently doing this:
bsdName = IORegistryEntrySearchCFProperty(p_usb_ref, kIOServicePlane, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, kIORegistryIterateRecursively );
Secondly, I have been able to get service plane name:
IOService:/AppleACPIPlatformExpert/PCI0#0/AppleACPIPCI/OHC1#4/AppleUSBOHCI/Intro to Electronics#4100000
and this corresponds to a mount point of: /dev/tty.usbmodem411
Does anyone know how to translate the service plane name to the dev tree name?
Thirdly, am I making this too complicated? I already know the device io handle, is there a way to use that to write data to the device? I just need to send a few ASCII bytes to flash some LEDs.
Any advice would be greatly appreciated.
EDIT:
After spending some more time looking at this, I found that my issue was that I was querying for the BSD name before the CDC driver was being loaded. I am currently getting the BSD name, and then sorting out for the VID and PID.
The code that solved my issue above is:
matchingDictionary = IOServiceMatching(kIOSerialBSDServiceValue);
CFDictionarySetValue(matchingDictionary, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType));
kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iter);
And then you iterate through iter to find the device with the correct ID.
This is what I use with a IONotification when adding a USB serial device:
Under 10.11 it came up empty. After trying a lot of things, this was my solution:
while ((usbDevice = IOIteratorNext(iterator)))
{
//when hotplugging under OSX 10.11:
sleep(1);//otherwise the property will be empty.
CFStringRef deviceBSDName_cf = (CFStringRef) IORegistryEntrySearchCFProperty (usbDevice,
kIOServicePlane,
CFSTR (kIOCalloutDeviceKey),
kCFAllocatorDefault,
kIORegistryIterateRecursively );
NSLog(#"device path: %#", deviceBSDName_cf);
}
It should find something like: /dev/cu.xxxxx
Hope it helps someone.
Perhaps things have changed in OS X 10.10? Your last code snippet doesn't seem to find the /dev/tty.usbmodem00054741 device on my system:
io_iterator_t devlisthndl = 0;
CFMutableDictionaryRef matchingDictionary = IOServiceMatching(kIOSerialBSDServiceValue);
CFIndex dict_count = CFDictionaryGetCount(matchingDictionary);
CFDictionarySetValue(matchingDictionary, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType));
kern_return_t kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &devlisthndl);
(lldb) p matchingDictionary
(CFMutableDictionaryRef) $3 = 0x0000610000267780 #"0 entries"
How did you end up obtaining the mount point string?

Differentiating between USB flash drive and USB hard drive on Windows

I'm trying to differentiate between a USB flash drive and a USB hard drive on Windows using the Win32 API.
The GetDriveType() function will return DRIVE_REMOVABLE if the drive is removable, and USB flash drives are of course removable. But I'm thinking that Windows probably considers USB hard drives removable as well (unfortunately I don't have access to a USB hard drive to test it out).
Thanks in advance.
If you want to determine that a device is USB device, you can open its handle and send IOCTL queries using DeviceIoControl() to get bus type a device is connected to.
EnumUsbDrivesLetters - the post is in Russian but it contains C++ source code, so the matter could be understood easily.
Cheers,
Andriy
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Method OpenVolume
// Purpose: Open volume for removal. Change to ::CreateFile(volumeName, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
// if you just want to inquire if it's removable.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HANDLE OpenVolume(const char& driveLetter)
{
char volumeName[8] = "";
char* volumeFormat = "\\\\.\\%c:";
sprintf(volumeName, volumeFormat, driveLetter);
HANDLE volume = ::CreateFile(volumeName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (volume == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE;
DWORD bytesReturned = 0;
STORAGE_HOTPLUG_INFO Info = {0};
if (::DeviceIoControl(volume, IOCTL_STORAGE_GET_HOTPLUG_INFO, 0, 0, &Info, sizeof(Info), &bytesReturned, NULL))
{
if (!(Info.MediaRemovable || Info.DeviceHotplug))
{
::CloseHandle(volume);
::SetLastError(ERROR_INVALID_PARAMETER);
return INVALID_HANDLE_VALUE;
}
}
return volume;
}
Actually windows doesn't, GetDriveType returns 3 (DRIVE_FIXED) for both my usb hard-drives.
Windows returns DRIVE_FIXED for external USB hard drives and usually returns DRIVE_REMOVABLE for USB flash sticks. For this reason if you want to access multiple partitions on a flash memory you have to install a filter driver to tell windows it's not a DRIVE_REMOVABLE but a DRIVE_FIXED instead. Windows only "sees" the first partition on flash sticks causing a lot of trouble for ESXi boot usb stick users ;-)
I thinks the key is drive properties, eg Cylinder count. You can use WMI interface to determine such information. Here is an example http://www.computerperformance.co.uk/vbscript/wmi_disks_physical.htm
The drive type is ultimately determined by the drivers; there's no fail-safe way to make the sort of determination that you're looking for.
I can say, however, that while I have seen a USB flash stick return DRIVE_FIXED, I have never seen a normal hard drive return DRIVE_REMOVEABLE. That's not to say that it's completely impossible for that to happen, but I've never seen it.
I'd say relying on those two values is probably the closest that you're going to get.
http://en.wikipedia.org/wiki/SCSI_Pass_Through_Interface will let you send raw SCSI commands to the device - you want to send down either INQUIRY or MODE SENSE to find out what you're looking for. However, a far better alternative may be the VDS APIs, if it will provide you correct information (I'm not sure whether it will in this case)

Resources