0

I'm building a small "macro" style keyboard using rust, STM32 chips, usb-device and usbd-hid. The device is correctly detected by my PC and the manufacturer strings etc are visible in device manager. I can use the device as a keyboard.

However when I try to write from host to device using rusb or hidapi, it fails with an IO error. When I debug the C code it gives an "Access Denied" error when it tries to WriteFile to the USB device. It seems on further research that this is a limitation of Windows, in that I can't access HID mice and keyboards as they're system reserved:

the native Windows HID driver is supported by libusb, but there are some limitations, such as not being able to access HID mice and keyboards since they are system reserved

However I can't find any documentation or examples of how to work around this - it seems I should be able to add another configuration, interface or endpoint in my USB descriptors that isn't exclusively held by Windows. Anybody have any hints on where to start?

My current USB device configuration (using usbd-hid's macro, which I'm happy to abandon if I need to) looks like this:

#[gen_hid_descriptor(
    (collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = KEYBOARD) = {
        (usage_page = KEYBOARD, usage_min = 0xE0, usage_max = 0xE7) = {
            #[packed_bits 8] #[item_settings data,variable,absolute] modifier=input;
        };
        (usage_min = 0x00, usage_max = 0xFF) = {
            #[item_settings constant,variable,absolute] reserved=input;
        };
        (usage_page = LEDS, usage_min = 0x01, usage_max = 0x05) = {
            #[packed_bits 5] #[item_settings data,variable,absolute] leds=output;
        };
        (usage_page = KEYBOARD, usage_min = 0x00, usage_max = 0xDD) = {
            #[item_settings data,array,absolute] keycodes=input;
        };
        (usage_page = 0xFF17, usage_min = 0x01, usage_max = 0xFF) = {
            #[item_settings data,variable,absolute] command=output;
        };
        (usage_page = 0xFF17, usage_min = 0x01, usage_max = 0xFF) = {
            #[item_settings data,variable,absolute] data=output;
        };
    }
)]
#[allow(dead_code)]
pub struct CustomKeyboardReport {
    pub modifier: u8,
    pub reserved: u8,
    pub leds: u8,
    pub keycodes: [u8; 6],
    pub command: u8,
    pub data: u8,
}

Which produces a descriptor that looks like this

0x05 0x01 0x09 0x06 0xA1 0x01 0x05 0x07
0x19 0xE0 0x29 0xE7 0x15 0x00 0x25 0x01
0x75 0x01 0x95 0x08 0x81 0x02 0x19 0x00
0x29 0xFF 0x26 0xFF 0x00 0x75 0x08 0x95
0x01 0x81 0x03 0x05 0x08 0x19 0x01 0x29
0x05 0x25 0x01 0x75 0x01 0x95 0x05 0x91
0x02 0x95 0x03 0x91 0x03 0x05 0x07 0x19
0x00 0x29 0xDD 0x26 0xFF 0x00 0x75 0x08
0x95 0x06 0x81 0x00 0x06 0x17 0xFF 0x19
0x01 0x29 0xFF 0x95 0x01 0x91 0x02 0x06
0x17 0xFF 0x19 0x01 0x29 0xFF 0x91 0x02
0xC0

Related: 1

will-hart
  • 3,742
  • 2
  • 38
  • 48

1 Answers1

0

I resolved this by creating a separate report:


#[gen_hid_descriptor(
    (collection = LOGICAL, usage_page = VENDOR_DEFINED_START, usage = 0x00) = {
        (usage_page = 0xFF17, usage_min = 0x01, usage_max = 0xFF) = {
            #[item_settings data,array,absolute] command=output;
        };
    }
)]
pub struct CommandReport {
    pub command: [u8; 2],
}

Then I created a separate HID interface with only an OUT endoint:

// for the keyboard
let hid = HIDClass::new(&alloc, CustomKeyboardReport::desc(), 10);

// for comms from the host -> device
let command = HIDClass::new_ep_out(&alloc, CommandReport::desc(), 10); 

In my poll function I check both:

bus.poll(&mut [&mut hid, &mut command])

And then I can read the data by pulling raw output:

let mut buffer: [u8; 64] = [0; 64];
match command.pull_raw_output(&mut buffer) {
     Ok(size) => handleCommand(buffer, size),
     Err(UsbError::WouldBlock) => {
         // no pending data
     }
     Err(err) => panic!("Error receiving data {:?}", err),
}

As there is only 1 report ID, I did not have to prepend the reportId in the data I sent from the host using libusb.

will-hart
  • 3,742
  • 2
  • 38
  • 48