NicoHood/HID

No OUT endpoint in RawHID on SAMD21

Opened this issue · 22 comments

It seems RawHID does not advertise an OUT endpoint. At least on SAMD.
I tried it with the RawHID example on a SAMD21 board. I was able to receive data but it crashes as soon as I tried writing to it.
My plattform is Ubuntu 18.04
Running lsusb gave this which seems to indicated that there is simply no endpoint to send the data to.

Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.01
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      28
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x84  EP 4 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
Device Status:     0x0000
  (Bus Powered)

@facchinm maybe you have any insight? It seems you did all the porting work.

From @NicoHood 's code RawHID only exposes an IN endpoint (see https://github.com/NicoHood/HID/blob/master/src/SingleReport/RawHID.cpp#L52) also on AVR.
Some minor modifications to that file should exposes the OUT EP, but I don't know if it will work since I never tested it 😉

Ah yes, I was just trying to understand where this is constructed.
Thanks for the input!
I will have a further look but I guess @NicoHood might need to given an insight on how this is supposed to work.

It was never working relyably, and its been years since I wrote all this USB stuff. I am sorry, I cannot help here.

I see, looking at the commits it seems this was all done in a small sprint back in 2015 :)

I would be pretty interested in getting a reliable RawHID library going for Arduino.
Do you maybe know someone (or yourself but it sounds you are not that interested) who might be interested to fix or re-write the RawHID part to a reliable state?
The company I work for could allocate some funds to sponsor that development.

I have bidirectional usb hid working on an arduino due, by modifying HID.h / HID.cpp. I can post it here if you'd like to take a look, but it's pretty simple to get working.

Sure! Every little bit would be great. If no one else has interest in making a cross plattform raw hid I would take it on but I have a steep learning curve ahead so a point to start would certainly be great :)

Here you are, there is a platformio project for the due that will echo usb packets, and a java application that will write to and then read from that device.

Sorry I don't have much time to clean it up or comment it, but you can see the changes I applied to HID.h / HID.cpp and maybe get it working properly.

https://github.com/PhilipAnderson/due-raw-usb-hid-test

I should note that I don't really know what I'm doing here, but what I have done is working and is useful for me at least.

Thank you! Very helpful.

That's fascinating that it isn't working for you guys. I do note that lsusb does NOT list an output endpoint, yet despite that, it appears to work anyway.

The example program, "RawHID" works as-is, including the RawHID.write(...) lines. I am able to successfully receive the data sent by RawHID.write on the host. In fact, it even works when I run 2 instances of RawHID, i.e.,

RawHID_ hidB;
...
hidB.begin(...);
...
hidB.write(...);

It even keeps all the reads/writes to both RawHID and hidB properly separated.

  • This is on a SAMD21.

So you are able to receive data sent to the host or you are able to receive data on the MCU sent by the host?

MCU to host worked fine, host to mcu just failed.
lsusb does report outputs on other devices for me, so I'm not sure that is correct.

Just as I said..
BOTH.

@PTS93

What crashed for you? Was it the host? Or the MCU? If it was the MCU, I wonder if "megabuf" might be too long for something. I haven't actually tried sending any more than a 4-byte array, which is all that my project needs. If it was the host side, it might be helpful to start off with something fairly simple;

Edit: without really understanding this stuff entirely, tracing back the TX and RX size from the report description here; https://github.com/NicoHood/HID/blob/master/src/SingleReport/RawHID.cpp#L40 leads to 64 bytes, ultimately here; https://github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/USB/USBAPI.h#L28

Compile with gcc, and when you run it, it expects a parameter /dev/hidrawX

#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/ioctl.h>
#include <sys/types.h>

#include <sys/socket.h>
#include <sys/un.h>

#include <linux/types.h>
#include <linux/input.h>
#include <linux/hidraw.h>

#include <sys/stat.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int hid_fd = -1;

int openhid(char *device){
    int fd;
    int i, res, desc_size = 0;
    char buf[256];
    struct hidraw_report_descriptor rpt_desc;
    struct hidraw_devinfo info;

    /* Open the Device with non-blocking reads. In real life,
       don't use a hard coded path; use libudev instead. */
    fd = open(device, O_RDWR|O_NONBLOCK);

    if (fd < 0) {
        printf("Unable to open device\n");
        return -1;
    }

    memset(&rpt_desc, 0x0, sizeof(rpt_desc));
    memset(&info, 0x0, sizeof(info));
    memset(buf, 0x0, sizeof(buf));

    /* Get Report Descriptor Size */
    res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size);
    if (res < 0)
        printf("Failed to get HIDIOCGRDESCSIZE\n");
    else
        printf("Report Descriptor Size: %d\n", desc_size);

    /* Get Raw Info */
    res = ioctl(fd, HIDIOCGRAWINFO, &info);
    if (res < 0) {
        printf("Failed to get HIDIOCGRAWINFO");
    } else {
        printf("Raw Info:\n");
        printf("\tbustype: %d\n", info.bustype);
        printf("\tvendor: 0x%04hx\n", info.vendor);
        printf("\tproduct: 0x%04hx\n", info.product);
    }

    if ((int16_t)info.vendor != (int16_t)0x239a){
        printf("INFO.VENDOR: 0x%04hx\n", info.vendor);
        close(fd);
        return -1;
    }

    if ((int16_t)info.product != (int16_t)0x801e){
        printf("INFO.PRODUCT: 0x%04hx\n", (int16_t)info.product);
        close(fd);
        return -1;
    }

    return fd;
}

void writehid(char* value, int len){
    unsigned char buf[16];
    int res,i;

    buf[0] = 0x0;   // Report ID (0)
    buf[1] = 0x20;  // Command (0x20)

    for (i=0; i<len; i++)
	    buf[i+2] = value[i];

    res = write(hid_fd, buf, 2+len);
    if (res < 0) {
        printf("Write error: %d\n", errno);
    } else {
        printf("write() wrote %d bytes\n", res);
    }

    // Give the device some time to respond.
    sleep(1);

    // Get a report from the device * /
    res = read(hid_fd, buf, 16);

    if (res < 0) {
        printf("Read error\n");
    } else {
        printf("read() read %d bytes:  ", res);
        for (i = 0; i < res; i++)
            printf("%hhx ", buf[i]);
	printf("\n");
    }
}

int main(int argc, char ** argv){
	int i;
	hid_fd = openhid(argv[1]);
	printf("hid_fd: %d\n", hid_fd);
	if (hid_fd >= 0) writehid("test", 4);
	else printf("No filedes, not writing.\n");
}

And note that I have it set up as a request/response communications pattern.
So in your sketch, you can do something like putting a RawHID.write(...) at this point;
https://github.com/NicoHood/HID/blob/master/examples/RawHID/RawHID/RawHID.ino#L67

Otherwise you will want to move the read in the host side program from the bottom of the writehid function into a loop at the bottom of the main, or change it to blocking reads.

Looking at the code again...
We do not need an out endpoint, as you can handle this with the control endpoint as well. Thatswhy I did not use it.
https://github.com/NicoHood/HID/blob/master/src/SingleReport/RawHID.cpp#L52
https://github.com/NicoHood/HID/blob/master/src/SingleReport/RawHID.cpp#L134-L142

Ooh, weird ok, cool :)
So technically I guess it should work?
@lbdroid I only tested it with node-hid a node.js library for working with HID devices.
It works fine with Teensy so I assumed it shouldn't make a difference with this one.
I will try it again with the C examples.

One thing I just realized that you want to keep track of... apparently I hardcoded a vid/pid in there. You won't want to forget to change that to match your hardware, OR, just delete the checks.

And start with a 1-byte send.

#116 is related. Just linking it since I found that answer first.

As a point of comparison, here is the result from the Teensy RawHID implementation:

# lsusb -vvvv 
Bus 001 Device 057: ID 16c0:0486 Van Ooijen Technische Informatica Teensyduino RawHID
Device Descriptor:
 bLength                18
 bDescriptorType         1
 bcdUSB               1.10
 bDeviceClass            0 (Defined at Interface level)
 bDeviceSubClass         0
 bDeviceProtocol         0
 bMaxPacketSize0        64
 idVendor           0x16c0 Van Ooijen Technische Informatica
 idProduct          0x0486 Teensyduino RawHID
 bcdDevice            2.76
 iManufacturer           1 Teensyduino
 iProduct                2 Teensyduino RawHID
 iSerial                 3 3284590
 bNumConfigurations      1
 Configuration Descriptor:
   bLength                 9
   bDescriptorType         2
   wTotalLength           73
   bNumInterfaces          2
   bConfigurationValue     1
   iConfiguration          0
   bmAttributes         0xc0
     Self Powered
   MaxPower              100mA
   Interface Descriptor:
     bLength                 9
     bDescriptorType         4
     bInterfaceNumber        0
     bAlternateSetting       0
     bNumEndpoints           2
     bInterfaceClass         3 Human Interface Device
     bInterfaceSubClass      0 No Subclass
     bInterfaceProtocol      0 None
     iInterface              0
       HID Device Descriptor:
         bLength                 9
         bDescriptorType        33
         bcdHID               1.11
         bCountryCode            0 Not supported
         bNumDescriptors         1
         bDescriptorType        34 Report
         wDescriptorLength      28
        Report Descriptors:
          ** UNAVAILABLE **
     Endpoint Descriptor:
       bLength                 7
       bDescriptorType         5
       bEndpointAddress     0x83  EP 3 IN
       bmAttributes            3
         Transfer Type            Interrupt
         Synch Type               None
         Usage Type               Data
       wMaxPacketSize     0x0040  1x 64 bytes
       bInterval               1
     Endpoint Descriptor:
       bLength                 7
       bDescriptorType         5
       bEndpointAddress     0x04  EP 4 OUT
       bmAttributes            3
         Transfer Type            Interrupt
         Synch Type               None
         Usage Type               Data
       wMaxPacketSize     0x0040  1x 64 bytes
       bInterval               1
   Interface Descriptor:
     bLength                 9
     bDescriptorType         4

I used this Java/Groovy program to test:

https://gist.github.com/madhephaestus/2f03c9588113d58e22d23a30e1eaaecc

I tested this using an official Arduino Zero, and connected over the native USB port

The java program can open the Teensy RawHID, send bytes, and received bytes back. Using the Example, i modified it to send bytes after receiving. It was able to open and receive bytes, but could not send data back from the Zero to the java program. On the teensy the java program receives data.

jfyi I'm using tinyusb for this now, its a very expansive USB abstraction layer that exposes raw hid besides many other things
https://github.com/adafruit/Adafruit_TinyUSB_Arduino/tree/master/examples/HID/hid_generic_inout

@PTS93 do you know how to get that running on the Arduino Zero?

I think the Adafruit SAMD core still includes the Arduino Zero.
If not all you have to do is copy the entry from the Arduino SAMD core in boards.txt over and copy the arduino_zero folder found in the variant folder of the core.

@madhephaestus have you looked into using zephyr instead of arduino? It's a proper rtos and infinitely more capable, and hidraw works flawlessly.