From: Jonathan Matthew Subject: uhidev: attach to devices with no input interrupt endpoint To: tech@openbsd.org Date: Sun, 7 Sep 2025 21:51:31 +1000 While the USB HID spec says an input interrupt endpoint is required, (https://usb.org/sites/default/files/hid1_11.pdf section 4.4) there exist devices that don't have one, like this: Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: [skipped] Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 This device is a small lcd screen on the side of the AceMagic S1 mini pc. See https://github.com/tjaworski/AceMagic-S1-LED-TFT-Linux?tab=readme-ov-file#tft-lcd-display for a bit more about the screen and how it's controlled. The diff below allows uhidev to attach to devices like this, and changes uhidev_open() so it'll open an output pipe if that's the only endpoint available. Most of the diff is just moving the code that opens the input pipe inside an if block, so it's much less involved than it might initially appear. Attempting to read from a device with no input endpoint just blocks forever, because the device can never produce any data to read. ok? or does anyone feel strongly about following the USB device specs to the letter? Index: uhidev.c =================================================================== RCS file: /cvs/src/sys/dev/usb/uhidev.c,v diff -u -p -r1.110 uhidev.c --- uhidev.c 23 May 2024 03:21:09 -0000 1.110 +++ uhidev.c 30 Aug 2025 12:42:32 -0000 @@ -200,15 +200,6 @@ uhidev_attach(struct device *parent, str } } - /* - * Check that we found an input interrupt endpoint. - * The output interrupt endpoint is optional - */ - if (sc->sc_iep_addr == -1) { - printf("%s: no input interrupt endpoint\n", DEVNAME(sc)); - return; - } - #ifndef SMALL_KERNEL if (uhidev_use_rdesc(sc, id, uaa->vendor, uaa->product, &desc, &size)) return; @@ -575,32 +566,31 @@ uhidev_open(struct uhidev *scd) if (sc->sc_refcnt++) return (0); - if (sc->sc_isize == 0) - return (0); + if (sc->sc_isize != 0) { + sc->sc_ibuf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); - sc->sc_ibuf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); + /* Set up input interrupt pipe. */ + DPRINTF(("uhidev_open: isize=%d, ep=0x%02x\n", sc->sc_isize, + sc->sc_iep_addr)); + + err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep_addr, + USBD_SHORT_XFER_OK, &sc->sc_ipipe, sc, sc->sc_ibuf, + sc->sc_isize, uhidev_intr, USBD_DEFAULT_INTERVAL); + if (err != USBD_NORMAL_COMPLETION) { + DPRINTF(("uhidopen: usbd_open_pipe_intr failed, " + "error=%d\n", err)); + error = EIO; + goto out1; + } - /* Set up input interrupt pipe. */ - DPRINTF(("uhidev_open: isize=%d, ep=0x%02x\n", sc->sc_isize, - sc->sc_iep_addr)); - - err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep_addr, - USBD_SHORT_XFER_OK, &sc->sc_ipipe, sc, sc->sc_ibuf, - sc->sc_isize, uhidev_intr, USBD_DEFAULT_INTERVAL); - if (err != USBD_NORMAL_COMPLETION) { - DPRINTF(("uhidopen: usbd_open_pipe_intr failed, " - "error=%d\n", err)); - error = EIO; - goto out1; - } - - DPRINTF(("uhidev_open: sc->sc_ipipe=%p\n", sc->sc_ipipe)); - - sc->sc_ixfer = usbd_alloc_xfer(sc->sc_udev); - if (sc->sc_ixfer == NULL) { - DPRINTF(("uhidev_open: couldn't allocate an xfer\n")); - error = ENOMEM; - goto out1; // xxxx + DPRINTF(("uhidev_open: sc->sc_ipipe=%p\n", sc->sc_ipipe)); + + sc->sc_ixfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_ixfer == NULL) { + DPRINTF(("uhidev_open: couldn't allocate an xfer\n")); + error = ENOMEM; + goto out1; // xxxx + } } /* @@ -662,7 +652,8 @@ out3: usbd_close_pipe(sc->sc_opipe); out2: /* Abort input pipe */ - usbd_close_pipe(sc->sc_ipipe); + if (sc->sc_ipipe != NULL) + usbd_close_pipe(sc->sc_ipipe); out1: DPRINTF(("uhidev_open: failed in someway")); free(sc->sc_ibuf, M_USBDEV, sc->sc_isize);