|
| 1 | +/******************************************************* |
| 2 | + HIDAPI - Multi-Platform library for |
| 3 | + communication with HID devices. |
| 4 | +
|
| 5 | + FreeBSD implementation of the virtual HID device test interface. |
| 6 | +
|
| 7 | + The contents of this file may be used by anyone for any |
| 8 | + reason without any conditions and may be used as a |
| 9 | + starting point for your own applications which use HIDAPI. |
| 10 | +********************************************************/ |
| 11 | + |
| 12 | +/* |
| 13 | + * STATUS: self-skipping. |
| 14 | + * |
| 15 | + * Unlike Linux, FreeBSD currently has no userspace facility to create a HID |
| 16 | + * device that the HIDAPI backends would enumerate, so this provider returns |
| 17 | + * TEST_VDEV_UNAVAILABLE and the device-I/O test is reported as skipped (CTest |
| 18 | + * code 77) rather than failed - mirroring how the macOS and Linux raw-gadget |
| 19 | + * providers self-skip when their device can't be created. |
| 20 | + * |
| 21 | + * Why there is no virtual device today: |
| 22 | + * |
| 23 | + * - HIDAPI on FreeBSD uses the libusb backend, and FreeBSD's libusb |
| 24 | + * (libusb20 / ugen20) only enumerates *real* kernel-backed USB devices: it |
| 25 | + * lists /dev/usb via USB_READ_DIR and validates each /dev/ugenX.Y with |
| 26 | + * USB_GET_PLUGTIME / USB_GET_DEVICEINFO. A character device synthesized |
| 27 | + * with cuse(3) has no kernel usb_device behind it, so those ioctls fail and |
| 28 | + * it is invisible to libusb - cuse cannot fake a libusb-visible USB device. |
| 29 | + * |
| 30 | + * - FreeBSD's device-side USB stack (usb_template(4), "USB device mode") can |
| 31 | + * present a real virtual USB device, but it needs a hardware USB Device |
| 32 | + * Controller (an OTG/client port on an embedded board); there is no |
| 33 | + * software/dummy UDC equivalent to Linux's dummy_hcd on FreeBSD, so it is |
| 34 | + * unusable on amd64 servers / CI VMs. |
| 35 | + * |
| 36 | + * - The FreeBSD 13+ hid/hidraw stack (hidbus, usbhid, iichid, hidraw) is |
| 37 | + * consumer-side only; the planned userspace HID *creator* "usrhid(4)" (the |
| 38 | + * analogue of Linux /dev/uhid) is not yet in-tree. |
| 39 | + * |
| 40 | + * This file is the drop-in point for a real implementation once a mechanism is |
| 41 | + * available: |
| 42 | + * (a) On a self-hosted FreeBSD host with a USB Device Controller: implement |
| 43 | + * create()/destroy() via usb_template(4) / libusbgx, mirroring |
| 44 | + * test_virtual_device_rawgadget.c (same vendor-defined report descriptor; |
| 45 | + * on a SET_REPORT whose first byte is TEST_VDEV_CMD_EMIT_A/B, push the |
| 46 | + * matching canned report on the interrupt IN endpoint). |
| 47 | + * (b) If/when usrhid(4) lands: port test_virtual_device_uhid.c to its ioctl |
| 48 | + * protocol (intended to follow the Linux uhid protocol). |
| 49 | + * open_hidapi()/trigger()/destroy() below already work unchanged. |
| 50 | + */ |
| 51 | + |
| 52 | +#include "test_virtual_device.h" |
| 53 | + |
| 54 | +#include <stdlib.h> |
| 55 | +#include <string.h> |
| 56 | +#include <time.h> |
| 57 | +#include <wchar.h> |
| 58 | + |
| 59 | +struct test_virtual_device { |
| 60 | + unsigned short vendor_id; |
| 61 | + unsigned short product_id; |
| 62 | + char serial[64]; |
| 63 | +}; |
| 64 | + |
| 65 | +int test_virtual_device_create(test_virtual_device **out_dev, |
| 66 | + unsigned short vendor_id, |
| 67 | + unsigned short product_id, |
| 68 | + const char *serial) |
| 69 | +{ |
| 70 | + (void)vendor_id; |
| 71 | + (void)product_id; |
| 72 | + (void)serial; |
| 73 | + |
| 74 | + if (out_dev) |
| 75 | + *out_dev = NULL; |
| 76 | + |
| 77 | + /* No userspace virtual-HID/USB-device mechanism on FreeBSD (see top of |
| 78 | + file): report "unavailable" so the test self-skips instead of failing. |
| 79 | + A real implementation would allocate the struct, bring up the device, |
| 80 | + store the ids/serial, set *out_dev and return TEST_VDEV_OK. */ |
| 81 | + return TEST_VDEV_UNAVAILABLE; |
| 82 | +} |
| 83 | + |
| 84 | +hid_device *test_virtual_device_open_hidapi(test_virtual_device *dev, int timeout_ms) |
| 85 | +{ |
| 86 | + /* Backend-agnostic open-by-enumeration (same as every other provider); |
| 87 | + reachable only once create() actually produces a device. */ |
| 88 | + wchar_t wserial[64]; |
| 89 | + int waited = 0; |
| 90 | + size_t i; |
| 91 | + |
| 92 | + if (!dev) |
| 93 | + return NULL; |
| 94 | + |
| 95 | + for (i = 0; i + 1 < (sizeof(wserial) / sizeof(wserial[0])) && dev->serial[i]; i++) |
| 96 | + wserial[i] = (wchar_t)(unsigned char)dev->serial[i]; |
| 97 | + wserial[i] = L'\0'; |
| 98 | + |
| 99 | + for (;;) { |
| 100 | + struct hid_device_info *infos = hid_enumerate(dev->vendor_id, dev->product_id); |
| 101 | + struct hid_device_info *cur; |
| 102 | + struct hid_device_info *first = NULL; |
| 103 | + struct hid_device_info *match = NULL; |
| 104 | + hid_device *h = NULL; |
| 105 | + |
| 106 | + for (cur = infos; cur; cur = cur->next) { |
| 107 | + if (!first) |
| 108 | + first = cur; |
| 109 | + if (cur->serial_number && wcscmp(cur->serial_number, wserial) == 0) { |
| 110 | + match = cur; |
| 111 | + break; |
| 112 | + } |
| 113 | + } |
| 114 | + if (match || first) |
| 115 | + h = hid_open_path((match ? match : first)->path); |
| 116 | + hid_free_enumeration(infos); |
| 117 | + if (h) |
| 118 | + return h; |
| 119 | + |
| 120 | + if (waited >= timeout_ms) |
| 121 | + return NULL; |
| 122 | + { |
| 123 | + struct timespec ts = { 0, 50 * 1000000L }; |
| 124 | + nanosleep(&ts, NULL); |
| 125 | + } |
| 126 | + waited += 50; |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +int test_virtual_device_trigger(test_virtual_device *dev, hid_device *handle, |
| 131 | + unsigned char command) |
| 132 | +{ |
| 133 | + unsigned char feature[1 + TEST_VDEV_REPORT_SIZE]; |
| 134 | + (void)dev; |
| 135 | + memset(feature, 0, sizeof(feature)); |
| 136 | + feature[0] = 0x00; /* Report ID (the device has no numbered reports) */ |
| 137 | + feature[1] = command; /* scenario command, first byte of the payload */ |
| 138 | + return hid_send_feature_report(handle, feature, sizeof(feature)); |
| 139 | +} |
| 140 | + |
| 141 | +void test_virtual_device_destroy(test_virtual_device *dev) |
| 142 | +{ |
| 143 | + free(dev); |
| 144 | +} |
0 commit comments