This patch has been cleaned up with suggestions from Greg KH (https://lore.kernel.org/lkml/Y+4Wwm1kFTUEED89@xxxxxxxxx/T/#r12e1bcf3ec45e19d7e2247f7070faac19e0fb60d) Additional changes since v1: * Fixes race conditions during gadget unbind * 32/64 bit compatibility fixes (compat ioctl and uapi structure padding) Additional testing has also been done on Zynq UltraScale+ with the DWC3 driver. Example userspace code to respond to GET_REPORT --- #include <stdint.h> #include <poll.h> #include <linux/usb/g_hid.h> #include <sys/ioctl.h> #include <fcntl.h> #include <unistd.h> void handleGetReport(int hidgfd) { uint8_t reportId = 0; int ret = ioctl(hidgfd, GADGET_HID_READ_GET_REPORT_ID, &reportId); if (ret >= 0 && reportId > 0) { struct usb_hidg_report report = {}; report.report_id = reportId; report.userspace_req = 1; report.length = 0; switch (reportId) { case 0x01: // report 0x01 requested report.length = 2; report.data[0] = reportId, // Report ID report.data[1] = 0x00; // Report Data break; default: // Unknown report (return empty report so the host doesn't have to wait for the timeout) break; } ioctl(hidgfd, GADGET_HID_WRITE_GET_REPORT, &report); } } int main(int argc, char **argv) { int hidgfd = open("/dev/hidg0", O_RDWR | O_NONBLOCK); if (hidgfd < 0) { return -1; } struct pollfd fdset[1]; fdset[0].fd = hidgfd; fdset[0].events = POLLIN | POLLPRI; int running = 1; while (running) { int rc, fd; fdset[0].revents = 0; rc = poll(fdset, 1, 1000); // Wait up to a second if (rc > 0) { if (fdset[0].revents & POLLPRI) { // Process Get Report handleGetReport(hidgfd); } /* POLLIN can be processed here for output reports */ } } close(hidgfd); return 0; }