Browse Source

initial import of hidapi netbsd uhid native backend from mainstream

https://github.com/libusb/hidapi/commit/c19ae126d8856b55af34d64ee11641920475961c
https://github.com/libusb/hidapi/pull/612
Ozkan Sezer 1 year ago
parent
commit
2f806c89b5

+ 3 - 0
src/hidapi/CMakeLists.txt

@@ -42,6 +42,9 @@ elseif(NOT WIN32)
         option(HIDAPI_WITH_HIDRAW "Build HIDRAW-based implementation of HIDAPI" ON)
         option(HIDAPI_WITH_LIBUSB "Build LIBUSB-based implementation of HIDAPI" ON)
     endif()
+    if(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
+        option(HIDAPI_WITH_NETBSD "Build NetBSD/UHID implementation of HIDAPI" ON)
+    endif()
 endif()
 
 option(BUILD_SHARED_LIBS "Build shared version of the libraries, otherwise build statically" ON)

+ 2 - 0
src/hidapi/SDL_hidapi.c

@@ -573,6 +573,8 @@ typedef struct PLATFORM_hid_device_ PLATFORM_hid_device;
 
 #ifdef __LINUX__
 #include "SDL_hidapi_linux.h"
+#elif defined(__NETBSD__)
+#include "SDL_hidapi_netbsd.h"
 #elif defined(__MACOS__)
 #include "SDL_hidapi_mac.h"
 #elif defined(__WINDOWS__) || defined(__WINGDK__)

+ 25 - 0
src/hidapi/SDL_hidapi_netbsd.h

@@ -0,0 +1,25 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#undef HIDAPI_H__
+#include "netbsd/hid.c"
+#define HAVE_PLATFORM_BACKEND 1
+#define udev_ctx              1

+ 35 - 0
src/hidapi/netbsd/CMakeLists.txt

@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.6.3 FATAL_ERROR)
+
+add_library(hidapi_netbsd
+    ${HIDAPI_PUBLIC_HEADERS}
+    hid.c
+)
+target_link_libraries(hidapi_netbsd PUBLIC hidapi_include)
+
+find_package(Threads REQUIRED)
+
+target_link_libraries(hidapi_netbsd PRIVATE Threads::Threads)
+
+set_target_properties(hidapi_netbsd
+    PROPERTIES
+        EXPORT_NAME "netbsd"
+        OUTPUT_NAME "hidapi-netbsd"
+        VERSION ${PROJECT_VERSION}
+        SOVERSION ${PROJECT_VERSION_MAJOR}
+        PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}"
+)
+
+# compatibility with find_package()
+add_library(hidapi::netbsd ALIAS hidapi_netbsd)
+# compatibility with raw library link
+add_library(hidapi-netbsd ALIAS hidapi_netbsd)
+
+if(HIDAPI_INSTALL_TARGETS)
+    install(TARGETS hidapi_netbsd EXPORT hidapi
+        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+        PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/hidapi"
+    )
+endif()
+
+hidapi_configure_pc("${PROJECT_ROOT}/pc/hidapi-netbsd.pc.in")

+ 29 - 0
src/hidapi/netbsd/README.md

@@ -0,0 +1,29 @@
+Implementation Notes
+--------------------
+NetBSD maps every `uhidev` device to one or more `uhid`
+devices. Each `uhid` device only supports one report ID.
+The parent device `uhidev` creates one `uhid` device per
+report ID found in the hardware's report descriptor.
+
+In the event there are no report ID(s) found within the
+report descriptor, only one `uhid` device with a report ID
+of `0` is created.
+
+In order to remain compatible with existing `hidapi` APIs,
+all the `uhid` devices created by the parent `uhidev` device
+must be opened under the same `hid_device` instance to ensure
+that we can route reports to their appropriate `uhid` device.
+
+Internally the `uhid` driver will insert the report ID as
+needed so we must also omit the report ID in any situation
+where the `hidapi` API expects it to be included in the
+report data stream.
+
+Given the design of `uhid`, it must be augmented with extra
+platform specific APIs to ensure that the exact relationship
+between `uhidev` devices and `uhid` devices can be determined.
+
+The NetBSD implementation does this via the `drvctl` kernel
+driver. At present there is no known way to do this on OpenBSD
+for a `uhid` implementation to be at the same level as the
+NetBSD one.

+ 1173 - 0
src/hidapi/netbsd/hid.c

@@ -0,0 +1,1173 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ James Buren
+ libusb/hidapi Team
+
+ Copyright 2023, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+        https://github.com/libusb/hidapi .
+********************************************************/
+
+/* C */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+#include <errno.h>
+
+/* Unix */
+#include <unistd.h>
+#include <fcntl.h>
+#include <iconv.h>
+#include <poll.h>
+
+/* NetBSD */
+#include <sys/drvctlio.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+
+#include "../hidapi/hidapi.h"
+
+#define HIDAPI_MAX_CHILD_DEVICES 256
+
+struct hid_device_ {
+	int device_handle;
+	int blocking;
+	wchar_t *last_error_str;
+	struct hid_device_info *device_info;
+	size_t poll_handles_length;
+	struct pollfd poll_handles[256];
+	int report_handles[256];
+	char path[USB_MAX_DEVNAMELEN];
+};
+
+struct hid_enumerate_data {
+	struct hid_device_info *root;
+	struct hid_device_info *end;
+	int drvctl;
+	uint16_t vendor_id;
+	uint16_t product_id;
+};
+
+typedef void (*enumerate_devices_callback) (const struct usb_device_info *, void *);
+
+static wchar_t *last_global_error_str = NULL;
+
+/* The caller must free the returned string with free(). */
+static wchar_t *utf8_to_wchar_t(const char *utf8)
+{
+	wchar_t *ret = NULL;
+
+	if (utf8) {
+		size_t wlen = mbstowcs(NULL, utf8, 0);
+		if ((size_t) -1 == wlen) {
+			return wcsdup(L"");
+		}
+		ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t));
+		if (ret == NULL) {
+			/* as much as we can do at this point */
+			return NULL;
+		}
+		mbstowcs(ret, utf8, wlen+1);
+		ret[wlen] = 0x0000;
+	}
+
+	return ret;
+}
+
+/* Makes a copy of the given error message (and decoded according to the
+ * currently locale) into the wide string pointer pointed by error_str.
+ * The last stored error string is freed.
+ * Use register_error_str(NULL) to free the error message completely. */
+static void register_error_str(wchar_t **error_str, const char *msg)
+{
+	free(*error_str);
+	*error_str = utf8_to_wchar_t(msg);
+}
+
+/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */
+static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args)
+{
+	char msg[256];
+	vsnprintf(msg, sizeof(msg), format, args);
+
+	register_error_str(error_str, msg);
+}
+
+/* Set the last global error to be reported by hid_error(NULL).
+ * The given error message will be copied (and decoded according to the
+ * currently locale, so do not pass in string constants).
+ * The last stored global error message is freed.
+ * Use register_global_error(NULL) to indicate "no error". */
+static void register_global_error(const char *msg)
+{
+	register_error_str(&last_global_error_str, msg);
+}
+
+/* Similar to register_global_error, but allows passing a format string into this function. */
+static void register_global_error_format(const char *format, ...)
+{
+	va_list args;
+	va_start(args, format);
+	register_error_str_vformat(&last_global_error_str, format, args);
+	va_end(args);
+}
+
+/* Set the last error for a device to be reported by hid_error(dev).
+ * The given error message will be copied (and decoded according to the
+ * currently locale, so do not pass in string constants).
+ * The last stored device error message is freed.
+ * Use register_device_error(dev, NULL) to indicate "no error". */
+static void register_device_error(hid_device *dev, const char *msg)
+{
+	register_error_str(&dev->last_error_str, msg);
+}
+
+/* Similar to register_device_error, but you can pass a format string into this function. */
+static void register_device_error_format(hid_device *dev, const char *format, ...)
+{
+	va_list args;
+	va_start(args, format);
+	register_error_str_vformat(&dev->last_error_str, format, args);
+	va_end(args);
+}
+
+
+/*
+ * Gets the size of the HID item at the given position
+ * Returns 1 if successful, 0 if an invalid key
+ * Sets data_len and key_size when successful
+ */
+static int get_hid_item_size(const uint8_t *report_descriptor, uint32_t size, unsigned int pos, int *data_len, int *key_size)
+{
+	int key = report_descriptor[pos];
+	int size_code;
+
+	/*
+	 * This is a Long Item. The next byte contains the
+	 * length of the data section (value) for this key.
+	 * See the HID specification, version 1.11, section
+	 * 6.2.2.3, titled "Long Items."
+	 */
+	if ((key & 0xf0) == 0xf0) {
+		if (pos + 1 < size)
+		{
+			*data_len = report_descriptor[pos + 1];
+			*key_size = 3;
+			return 1;
+		}
+		*data_len = 0; /* malformed report */
+		*key_size = 0;
+	}
+
+	/*
+	 * This is a Short Item. The bottom two bits of the
+	 * key contain the size code for the data section
+	 * (value) for this key. Refer to the HID
+	 * specification, version 1.11, section 6.2.2.2,
+	 * titled "Short Items."
+	 */
+	size_code = key & 0x3;
+	switch (size_code) {
+	case 0:
+	case 1:
+	case 2:
+		*data_len = size_code;
+		*key_size = 1;
+		return 1;
+	case 3:
+		*data_len = 4;
+		*key_size = 1;
+		return 1;
+	default:
+		/* Can't ever happen since size_code is & 0x3 */
+		*data_len = 0;
+		*key_size = 0;
+		break;
+	};
+
+	/* malformed report */
+	return 0;
+}
+
+/*
+ * Get bytes from a HID Report Descriptor.
+ * Only call with a num_bytes of 0, 1, 2, or 4.
+ */
+static uint32_t get_hid_report_bytes(const uint8_t *rpt, size_t len, size_t num_bytes, size_t cur)
+{
+	/* Return if there aren't enough bytes. */
+	if (cur + num_bytes >= len)
+		return 0;
+
+	if (num_bytes == 0)
+		return 0;
+	else if (num_bytes == 1)
+		return rpt[cur + 1];
+	else if (num_bytes == 2)
+		return (rpt[cur + 2] * 256 + rpt[cur + 1]);
+	else if (num_bytes == 4)
+		return (
+			rpt[cur + 4] * 0x01000000 +
+			rpt[cur + 3] * 0x00010000 +
+			rpt[cur + 2] * 0x00000100 +
+			rpt[cur + 1] * 0x00000001
+		);
+	else
+		return 0;
+}
+
+/*
+ * Iterates until the end of a Collection.
+ * Assumes that *pos is exactly at the beginning of a Collection.
+ * Skips all nested Collection, i.e. iterates until the end of current level Collection.
+ *
+ * The return value is non-0 when an end of current Collection is found,
+ * 0 when error is occured (broken Descriptor, end of a Collection is found before its begin,
+ *  or no Collection is found at all).
+ */
+static int hid_iterate_over_collection(const uint8_t *report_descriptor, uint32_t size, unsigned int *pos, int *data_len, int *key_size)
+{
+	int collection_level = 0;
+
+	while (*pos < size) {
+		int key = report_descriptor[*pos];
+		int key_cmd = key & 0xfc;
+
+		/* Determine data_len and key_size */
+		if (!get_hid_item_size(report_descriptor, size, *pos, data_len, key_size))
+			return 0; /* malformed report */
+
+		switch (key_cmd) {
+		case 0xa0: /* Collection 6.2.2.4 (Main) */
+			collection_level++;
+			break;
+		case 0xc0: /* End Collection 6.2.2.4 (Main) */
+			collection_level--;
+			break;
+		}
+
+		if (collection_level < 0) {
+			/* Broken descriptor or someone is using this function wrong,
+			 * i.e. should be called exactly at the collection start */
+			return 0;
+		}
+
+		if (collection_level == 0) {
+			/* Found it!
+			 * Also possible when called not at the collection start, but should not happen if used correctly */
+			return 1;
+		}
+
+		*pos += *data_len + *key_size;
+	}
+
+	return 0; /* Did not find the end of a Collection */
+}
+
+struct hid_usage_iterator {
+	unsigned int pos;
+	int usage_page_found;
+	unsigned short usage_page;
+};
+
+/*
+ * Retrieves the device's Usage Page and Usage from the report descriptor.
+ * The algorithm returns the current Usage Page/Usage pair whenever a new
+ * Collection is found and a Usage Local Item is currently in scope.
+ * Usage Local Items are consumed by each Main Item (See. 6.2.2.8).
+ * The algorithm should give similar results as Apple's:
+ *   https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc
+ * Physical Collections are also matched (macOS does the same).
+ *
+ * This function can be called repeatedly until it returns non-0
+ * Usage is found. pos is the starting point (initially 0) and will be updated
+ * to the next search position.
+ *
+ * The return value is 0 when a pair is found.
+ * 1 when finished processing descriptor.
+ * -1 on a malformed report.
+ */
+static int get_next_hid_usage(const uint8_t *report_descriptor, uint32_t size, struct hid_usage_iterator *ctx, unsigned short *usage_page, unsigned short *usage)
+{
+	int data_len, key_size;
+	int initial = ctx->pos == 0; /* Used to handle case where no top-level application collection is defined */
+
+	int usage_found = 0;
+
+	while (ctx->pos < size) {
+		int key = report_descriptor[ctx->pos];
+		int key_cmd = key & 0xfc;
+
+		/* Determine data_len and key_size */
+		if (!get_hid_item_size(report_descriptor, size, ctx->pos, &data_len, &key_size))
+			return -1; /* malformed report */
+
+		switch (key_cmd) {
+		case 0x4: /* Usage Page 6.2.2.7 (Global) */
+			ctx->usage_page = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos);
+			ctx->usage_page_found = 1;
+			break;
+
+		case 0x8: /* Usage 6.2.2.8 (Local) */
+			if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */
+				ctx->usage_page = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos + 2);
+				ctx->usage_page_found = 1;
+				*usage = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos);
+				usage_found = 1;
+			}
+			else {
+				*usage = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos);
+				usage_found = 1;
+			}
+			break;
+
+		case 0xa0: /* Collection 6.2.2.4 (Main) */
+			if (!hid_iterate_over_collection(report_descriptor, size, &ctx->pos, &data_len, &key_size)) {
+				return -1;
+			}
+
+			/* A pair is valid - to be reported when Collection is found */
+			if (usage_found && ctx->usage_page_found) {
+				*usage_page = ctx->usage_page;
+				return 0;
+			}
+
+			break;
+		}
+
+		/* Skip over this key and its associated data */
+		ctx->pos += data_len + key_size;
+	}
+
+	/* If no top-level application collection is found and usage page/usage pair is found, pair is valid
+	   https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */
+	if (initial && usage_found && ctx->usage_page_found) {
+			*usage_page = ctx->usage_page;
+			return 0; /* success */
+	}
+
+	return 1; /* finished processing */
+}
+
+static struct hid_device_info *create_device_info(const struct usb_device_info *udi, const char *path, const struct usb_ctl_report_desc *ucrd)
+{
+	struct hid_device_info *root;
+	struct hid_device_info *end;
+
+	root = (struct hid_device_info *) calloc(1, sizeof(struct hid_device_info));
+	if (!root)
+		return NULL;
+
+	end = root;
+
+	/* Path */
+	end->path = (path) ? strdup(path) : NULL;
+
+	/* Vendor Id */
+	end->vendor_id = udi->udi_vendorNo;
+
+	/* Product Id */
+	end->product_id = udi->udi_productNo;
+
+	/* Serial Number */
+	end->serial_number = utf8_to_wchar_t(udi->udi_serial);
+
+	/* Release Number */
+	end->release_number = udi->udi_releaseNo;
+
+	/* Manufacturer String */
+	end->manufacturer_string = utf8_to_wchar_t(udi->udi_vendor);
+
+	/* Product String */
+	end->product_string = utf8_to_wchar_t(udi->udi_product);
+
+	/* Usage Page */
+	end->usage_page = 0;
+
+	/* Usage */
+	end->usage = 0;
+
+	/* Interface Number */
+	end->interface_number = -1;
+
+	/* Next Device Info */
+	end->next = NULL;
+
+	/* Bus Type */
+	end->bus_type = HID_API_BUS_USB;
+
+	if (ucrd) {
+		uint16_t page;
+		uint16_t usage;
+		struct hid_usage_iterator usage_iterator;
+
+		page = usage = 0;
+		memset(&usage_iterator, 0, sizeof(usage_iterator));
+
+		/*
+		 * Parse the first usage and usage page
+		 * out of the report descriptor.
+		 */
+		if (get_next_hid_usage(ucrd->ucrd_data, ucrd->ucrd_size, &usage_iterator, &page, &usage) == 0) {
+			end->usage_page = page;
+			end->usage = usage;
+		}
+
+		/*
+		 * Parse any additional usage and usage pages
+		 * out of the report descriptor.
+		 */
+		while (get_next_hid_usage(ucrd->ucrd_data, ucrd->ucrd_size, &usage_iterator, &page, &usage) == 0) {
+			/* Create new record for additional usage pairs */
+			struct hid_device_info *node = (struct hid_device_info *) calloc(1, sizeof(struct hid_device_info));
+
+			if (!node)
+				continue;
+
+			/* Update fields */
+			node->path = (end->path) ? strdup(end->path) : NULL;
+			node->vendor_id = end->vendor_id;
+			node->product_id = end->product_id;
+			node->serial_number = (end->serial_number) ? wcsdup(end->serial_number) : NULL;
+			node->release_number = end->release_number;
+			node->manufacturer_string = (end->manufacturer_string) ? wcsdup(end->manufacturer_string) : NULL;
+			node->product_string = (end->product_string) ? wcsdup(end->product_string) : NULL;
+			node->usage_page = page;
+			node->usage = usage;
+			node->interface_number = end->interface_number;
+			node->next = NULL;
+			node->bus_type = end->bus_type;
+
+			/* Insert node */
+			end->next = node;
+			end = node;
+		}
+	}
+
+	return root;
+}
+
+static int is_usb_controller(const char *s)
+{
+	return (!strncmp(s, "usb", 3) && isdigit((int) s[3]));
+}
+
+static int is_uhid_parent_device(const char *s)
+{
+	return (!strncmp(s, "uhidev", 6) && isdigit((int) s[6]));
+}
+
+static int is_uhid_device(const char *s)
+{
+	return (!strncmp(s, "uhid", 4) && isdigit((int) s[4]));
+}
+
+static void walk_device_tree(int drvctl, const char *dev, int depth, char arr[static HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN], size_t *len, int (*cmp) (const char *))
+{
+	int res;
+	char childname[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN];
+	struct devlistargs dla;
+
+	if (depth && (!dev || !*dev))
+		return;
+
+	if (cmp(dev) && *len < HIDAPI_MAX_CHILD_DEVICES)
+		strlcpy(arr[(*len)++], dev, sizeof(*arr));
+
+	strlcpy(dla.l_devname, dev, sizeof(dla.l_devname));
+	dla.l_childname = childname;
+	dla.l_children = HIDAPI_MAX_CHILD_DEVICES;
+
+	res = ioctl(drvctl, DRVLISTDEV, &dla);
+	if (res == -1)
+		return;
+
+	/*
+	 * DO NOT CHANGE THIS. This is a fail-safe check
+	 * for the unlikely event that a parent device has
+	 * more than HIDAPI_MAX_CHILD_DEVICES child devices
+	 * to prevent iterating over uninitialized data.
+	 */
+	if (dla.l_children > HIDAPI_MAX_CHILD_DEVICES)
+		return;
+
+	for (size_t i = 0; i < dla.l_children; i++)
+		walk_device_tree(drvctl, dla.l_childname[i], depth + 1, arr, len, cmp);
+}
+
+static void enumerate_usb_devices(int bus, uint8_t addr, enumerate_devices_callback func, void *data)
+{
+	int res;
+	struct usb_device_info udi;
+
+	udi.udi_addr = addr;
+
+	res = ioctl(bus, USB_DEVICEINFO, &udi);
+	if (res == -1)
+		return;
+
+	for (int port = 0; port < udi.udi_nports; port++) {
+		addr = udi.udi_ports[port];
+		if (addr >= USB_MAX_DEVICES)
+			continue;
+
+		enumerate_usb_devices(bus, addr, func, data);
+	}
+
+	func(&udi, data);
+}
+
+static void hid_enumerate_callback(const struct usb_device_info *udi, void *data)
+{
+	struct hid_enumerate_data *hed;
+
+	hed = (struct hid_enumerate_data *) data;
+
+	if (hed->vendor_id != 0 && hed->vendor_id != udi->udi_vendorNo)
+		return;
+
+	if (hed->product_id != 0 && hed->product_id != udi->udi_productNo)
+		return;
+
+	for (size_t i = 0; i < USB_MAX_DEVNAMES; i++) {
+		const char *parent_dev;
+		char arr[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN];
+		size_t len;
+		const char *child_dev;
+		char devpath[USB_MAX_DEVNAMELEN];
+		int uhid;
+		struct usb_ctl_report_desc ucrd;
+		int use_ucrd;
+		struct hid_device_info *node;
+
+		parent_dev = udi->udi_devnames[i];
+		if (!is_uhid_parent_device(parent_dev))
+			continue;
+
+		len = 0;
+		walk_device_tree(hed->drvctl, parent_dev, 0, arr, &len, is_uhid_device);
+
+		if (len == 0)
+			continue;
+
+		child_dev = arr[0];
+		strlcpy(devpath, "/dev/", sizeof(devpath));
+		strlcat(devpath, child_dev, sizeof(devpath));
+
+		uhid = open(devpath, O_RDONLY | O_CLOEXEC);
+		if (uhid >= 0) {
+			use_ucrd = (ioctl(uhid, USB_GET_REPORT_DESC, &ucrd) != -1);
+			close(uhid);
+		} else {
+			use_ucrd = 0;
+		}
+
+		node = create_device_info(udi, parent_dev, (use_ucrd) ? &ucrd : NULL);
+		if (!node)
+			continue;
+
+		if (!hed->root) {
+			hed->root = node;
+			hed->end = node;
+		} else {
+			hed->end->next = node;
+			hed->end = node;
+		}
+
+		while (hed->end->next)
+			hed->end = hed->end->next;
+	}
+}
+
+static int set_report(hid_device *dev, const uint8_t *data, size_t length, int report)
+{
+	int res;
+	int device_handle;
+	struct usb_ctl_report ucr;
+
+	if (length < 1) {
+		register_device_error(dev, "report must be greater than 1 byte");
+		return -1;
+	}
+
+	device_handle = dev->report_handles[*data];
+	if (device_handle < 0) {
+		register_device_error_format(dev, "unsupported report id: %hhu", *data);
+		return -1;
+	}
+
+	length--;
+	data++;
+
+	if (length > sizeof(ucr.ucr_data)) {
+		register_device_error_format(dev, "report must be less than or equal to %zu bytes", sizeof(ucr.ucr_data));
+		return -1;
+	}
+
+	ucr.ucr_report = report;
+	memcpy(ucr.ucr_data, data, length);
+
+	res = ioctl(device_handle, USB_SET_REPORT, &ucr);
+	if (res == -1) {
+		register_device_error_format(dev, "ioctl (USB_SET_REPORT): %s", strerror(errno));
+		return -1;
+	}
+
+	return (int) (length + 1);
+}
+
+static int get_report(hid_device *dev, uint8_t *data, size_t length, int report)
+{
+	int res;
+	int device_handle;
+	struct usb_ctl_report ucr;
+
+	if (length < 1) {
+		register_device_error(dev, "report must be greater than 1 byte");
+		return -1;
+	}
+
+	device_handle = dev->report_handles[*data];
+	if (device_handle < 0) {
+		register_device_error_format(dev, "unsupported report id: %hhu", *data);
+		return -1;
+	}
+
+	length--;
+	data++;
+
+	if (length > sizeof(ucr.ucr_data)) {
+		length = sizeof(ucr.ucr_data);
+	}
+
+	ucr.ucr_report = report;
+
+	res = ioctl(device_handle, USB_GET_REPORT, &ucr);
+	if (res == -1) {
+		register_device_error_format(dev, "ioctl (USB_GET_REPORT): %s", strerror(errno));
+		return -1;
+	}
+
+	memcpy(data, ucr.ucr_data, length);
+
+	return (int) (length + 1);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_init(void)
+{
+	/* indicate no error */
+	register_global_error(NULL);
+
+	return 0;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_exit(void)
+{
+	/* Free global error message */
+	register_global_error(NULL);
+
+	return 0;
+}
+
+struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+{
+	int res;
+	int drvctl;
+	char arr[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN];
+	size_t len;
+	struct hid_enumerate_data hed;
+
+	res = hid_init();
+	if (res == -1)
+		return NULL;
+
+	drvctl = open(DRVCTLDEV, O_RDONLY | O_CLOEXEC);
+	if (drvctl == -1) {
+		register_global_error_format("failed to open drvctl: %s", strerror(errno));
+		return NULL;
+	}
+
+	len = 0;
+	walk_device_tree(drvctl, "", 0, arr, &len, is_usb_controller);
+
+	hed.root = NULL;
+	hed.end = NULL;
+	hed.drvctl = drvctl;
+	hed.vendor_id = vendor_id;
+	hed.product_id = product_id;
+
+	for (size_t i = 0; i < len; i++) {
+		char devpath[USB_MAX_DEVNAMELEN];
+		int bus;
+
+		strlcpy(devpath, "/dev/", sizeof(devpath));
+		strlcat(devpath, arr[i], sizeof(devpath));
+
+		bus = open(devpath, O_RDONLY | O_CLOEXEC);
+		if (bus == -1)
+			continue;
+
+		enumerate_usb_devices(bus, 0, hid_enumerate_callback, &hed);
+
+		close(bus);
+	}
+
+	close(drvctl);
+
+	return hed.root;
+}
+
+void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
+{
+	while (devs) {
+		struct hid_device_info *next = devs->next;
+		free(devs->path);
+		free(devs->serial_number);
+		free(devs->manufacturer_string);
+		free(devs->product_string);
+		free(devs);
+		devs = next;
+	}
+}
+
+HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
+{
+	struct hid_device_info *devs;
+	struct hid_device_info *dev;
+	char path[USB_MAX_DEVNAMELEN];
+
+	devs = hid_enumerate(vendor_id, product_id);
+	if (!devs)
+		return NULL;
+
+	*path = '\0';
+
+	for (dev = devs; dev; dev = dev->next) {
+		if (dev->vendor_id != vendor_id)
+			continue;
+
+		if (dev->product_id != product_id)
+			continue;
+
+		if (serial_number && wcscmp(dev->serial_number, serial_number))
+			continue;
+
+		strlcpy(path, dev->path, sizeof(path));
+
+		break;
+	}
+
+	hid_free_enumeration(devs);
+
+	if (*path == '\0') {
+		register_global_error("Device with requested VID/PID/(SerialNumber) not found");
+		return NULL;
+	}
+
+	return hid_open_path(path);
+}
+
+HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
+{
+	int res;
+	hid_device *dev;
+	int drvctl;
+	char arr[HIDAPI_MAX_CHILD_DEVICES][USB_MAX_DEVNAMELEN];
+	size_t len;
+
+	res = hid_init();
+	if (res == -1)
+		goto err_0;
+
+	dev = (hid_device *) calloc(1, sizeof(hid_device));
+	if (!dev) {
+		register_global_error("could not allocate hid_device");
+		goto err_0;
+	}
+
+	drvctl = open(DRVCTLDEV, O_RDONLY | O_CLOEXEC);
+	if (drvctl == -1) {
+		register_global_error_format("failed to open drvctl: %s", strerror(errno));
+		goto err_1;
+	}
+
+	if (!is_uhid_parent_device(path)) {
+		register_global_error("not a uhidev device");
+		goto err_2;
+	}
+
+	len = 0;
+	walk_device_tree(drvctl, path, 0, arr, &len, is_uhid_device);
+
+	dev->poll_handles_length = 0;
+	memset(dev->poll_handles, 0x00, sizeof(dev->poll_handles));
+	memset(dev->report_handles, 0xff, sizeof(dev->report_handles));
+
+	for (size_t i = 0; i < len; i++) {
+		const char *child_dev;
+		char devpath[USB_MAX_DEVNAMELEN];
+		int uhid;
+		int rep_id;
+		struct pollfd *ph;
+
+		child_dev = arr[i];
+		strlcpy(devpath, "/dev/", sizeof(devpath));
+		strlcat(devpath, child_dev, sizeof(devpath));
+
+		uhid = open(devpath, O_RDWR | O_CLOEXEC);
+		if (uhid == -1) {
+			register_global_error_format("failed to open %s: %s", child_dev, strerror(errno));
+			goto err_3;
+		}
+
+		res = ioctl(uhid, USB_GET_REPORT_ID, &rep_id);
+		if (res == -1) {
+			close(uhid);
+			register_global_error_format("failed to get report id %s: %s", child_dev, strerror(errno));
+			goto err_3;
+		}
+
+		ph = &dev->poll_handles[dev->poll_handles_length++];
+		ph->fd = uhid;
+		ph->events = POLLIN;
+		ph->revents = 0;
+		dev->report_handles[rep_id] = uhid;
+		dev->device_handle = uhid;
+	}
+
+	dev->blocking = 1;
+	dev->last_error_str = NULL;
+	dev->device_info = NULL;
+	strlcpy(dev->path, path, sizeof(dev->path));
+
+	register_global_error(NULL);
+	return dev;
+
+err_3:
+	for (size_t i = 0; i < dev->poll_handles_length; i++)
+		close(dev->poll_handles[i].fd);
+err_2:
+	close(drvctl);
+err_1:
+	free(dev);
+err_0:
+	return NULL;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
+{
+	return set_report(dev, data, length, UHID_OUTPUT_REPORT);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
+{
+	int res;
+	size_t i;
+	struct pollfd *ph;
+	ssize_t n;
+
+	res = poll(dev->poll_handles, dev->poll_handles_length, milliseconds);
+	if (res == -1) {
+		register_device_error_format(dev, "error while polling: %s", strerror(errno));
+		return -1;
+	}
+
+	if (res == 0)
+		return 0;
+
+	for (i = 0; i < dev->poll_handles_length; i++) {
+		ph = &dev->poll_handles[i];
+
+		if (ph->revents & (POLLERR | POLLHUP | POLLNVAL)) {
+			register_device_error(dev, "device IO error while polling");
+			return -1;
+		}
+
+		if (ph->revents & POLLIN)
+			break;
+	}
+
+	if (i == dev->poll_handles_length)
+		return 0;
+
+	n = read(ph->fd, data, length);
+	if (n == -1) {
+		if (errno == EAGAIN || errno == EINPROGRESS)
+			n = 0;
+		else
+			register_device_error_format(dev, "error while reading: %s", strerror(errno));
+	}
+
+	return n;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)
+{
+	return hid_read_timeout(dev, data, length, (dev->blocking) ? -1 : 0);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock)
+{
+	dev->blocking = !nonblock;
+	return 0;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
+{
+	return set_report(dev, data, length, UHID_FEATURE_REPORT);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
+{
+	return get_report(dev, data, length, UHID_FEATURE_REPORT);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
+{
+	return get_report(dev, data, length, UHID_INPUT_REPORT);
+}
+
+void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
+{
+	if (!dev)
+		return;
+
+	/* Free the device error message */
+	register_device_error(dev, NULL);
+
+	hid_free_enumeration(dev->device_info);
+
+	for (size_t i = 0; i < dev->poll_handles_length; i++)
+		close(dev->poll_handles[i].fd);
+
+	free(dev);
+}
+
+int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+	struct hid_device_info *hdi;
+
+	if (!string || !maxlen) {
+		register_device_error(dev, "Zero buffer/length");
+		return -1;
+	}
+
+	hdi = hid_get_device_info(dev);
+	if (!dev)
+		return -1;
+
+	if (hdi->manufacturer_string) {
+		wcsncpy(string, hdi->manufacturer_string, maxlen);
+		string[maxlen - 1] = L'\0';
+	} else {
+		string[0] = L'\0';
+	}
+
+	return 0;
+}
+
+int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+	struct hid_device_info *hdi;
+
+	if (!string || !maxlen) {
+		register_device_error(dev, "Zero buffer/length");
+		return -1;
+	}
+
+	hdi = hid_get_device_info(dev);
+	if (!dev)
+		return -1;
+
+	if (hdi->product_string) {
+		wcsncpy(string, hdi->product_string, maxlen);
+		string[maxlen - 1] = L'\0';
+	} else {
+		string[0] = L'\0';
+	}
+
+	return 0;
+}
+
+int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+	struct hid_device_info *hdi;
+
+	if (!string || !maxlen) {
+		register_device_error(dev, "Zero buffer/length");
+		return -1;
+	}
+
+	hdi = hid_get_device_info(dev);
+	if (!dev)
+		return -1;
+
+	if (hdi->serial_number) {
+		wcsncpy(string, hdi->serial_number, maxlen);
+		string[maxlen - 1] = L'\0';
+	} else {
+		string[0] = L'\0';
+	}
+
+	return 0;
+}
+
+struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev)
+{
+	int res;
+	struct usb_device_info udi;
+	struct usb_ctl_report_desc ucrd;
+	int use_ucrd;
+	struct hid_device_info *hdi;
+
+	if (dev->device_info)
+		return dev->device_info;
+
+	res = ioctl(dev->device_handle, USB_GET_DEVICEINFO, &udi);
+	if (res == -1) {
+		register_device_error_format(dev, "ioctl (USB_GET_DEVICEINFO): %s", strerror(errno));
+		return NULL;
+	}
+
+	use_ucrd = (ioctl(dev->device_handle, USB_GET_REPORT_DESC, &ucrd) != -1);
+
+	hdi = create_device_info(&udi, dev->path, (use_ucrd) ? &ucrd : NULL);
+	if (!hdi) {
+		register_device_error(dev, "failed to create device info");
+		return NULL;
+	}
+
+	dev->device_info = hdi;
+
+	return hdi;
+}
+
+int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
+{
+	int res;
+	struct usb_string_desc usd;
+	usb_string_descriptor_t *str;
+	iconv_t ic;
+	const char *src;
+	size_t srcleft;
+	char *dst;
+	size_t dstleft;
+	size_t ic_res;
+
+	/* First let us get the supported language IDs. */
+	usd.usd_string_index = 0;
+	usd.usd_language_id = 0;
+
+	res = ioctl(dev->device_handle, USB_GET_STRING_DESC, &usd);
+	if (res == -1) {
+		register_device_error_format(dev, "ioctl (USB_GET_STRING_DESC): %s", strerror(errno));
+		return -1;
+	}
+
+	str = &usd.usd_desc;
+
+	if (str->bLength < 4) {
+		register_device_error(dev, "failed to get supported language IDs");
+		return -1;
+	}
+
+	/* Now we can get the requested string. */
+	usd.usd_string_index = string_index;
+	usd.usd_language_id = UGETW(str->bString[0]);
+
+	res = ioctl(dev->device_handle, USB_GET_STRING_DESC, &usd);
+	if (res == -1) {
+		register_device_error_format(dev, "ioctl (USB_GET_STRING_DESC): %s", strerror(errno));
+		return -1;
+	}
+
+	/* Now we need to convert it, using iconv. */
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+	ic = iconv_open("utf-32le", "utf-16le");
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+	ic = iconv_open("utf-32be", "utf-16le");
+#endif
+	if (ic == (iconv_t) -1) {
+		register_device_error_format(dev, "iconv_open failed: %s", strerror(errno));
+		return -1;
+	}
+
+	src = (const char *) str->bString;
+	srcleft = str->bLength - 2;
+	dst = (char *) string;
+	dstleft = sizeof(wchar_t[maxlen]);
+
+	ic_res = iconv(ic, &src, &srcleft, &dst, &dstleft);
+	iconv_close(ic);
+	if (ic_res == (size_t) -1) {
+		register_device_error_format(dev, "iconv failed: %s", strerror(errno));
+		return -1;
+	}
+
+	/* Write the terminating NULL. */
+	string[maxlen - 1] = L'\0';
+	if (dstleft >= sizeof(wchar_t))
+		*((wchar_t *) dst) = L'\0';
+
+	return 0;
+}
+
+int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size)
+{
+	int res;
+	struct usb_ctl_report_desc ucrd;
+
+	res = ioctl(dev->device_handle, USB_GET_REPORT_DESC, &ucrd);
+	if (res == -1) {
+		register_device_error_format(dev, "ioctl (USB_GET_REPORT_DESC): %s", strerror(errno));
+		return -1;
+	}
+
+	if ((size_t) ucrd.ucrd_size < buf_size)
+		buf_size = (size_t) ucrd.ucrd_size;
+
+	memcpy(buf, ucrd.ucrd_data, buf_size);
+
+	return (int) buf_size;
+}
+
+HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev)
+{
+	if (dev) {
+		if (dev->last_error_str == NULL)
+			return L"Success";
+		return dev->last_error_str;
+	}
+
+	if (last_global_error_str == NULL)
+		return L"Success";
+	return last_global_error_str;
+}
+
+HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void)
+{
+	static const struct hid_api_version api_version = {
+		.major = HID_API_VERSION_MAJOR,
+		.minor = HID_API_VERSION_MINOR,
+		.patch = HID_API_VERSION_PATCH
+	};
+
+	return &api_version;
+}
+
+HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
+{
+	return HID_API_VERSION_STR;
+}

+ 11 - 0
src/hidapi/pc/hidapi-netbsd.pc.in

@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: hidapi-netbsd
+Description: C Library for USB/Bluetooth HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the netbsd implementation.
+URL: https://github.com/libusb/hidapi
+Version: @VERSION@
+Libs: -L${libdir} -lhidapi-netbsd
+Cflags: -I${includedir}/hidapi

+ 12 - 0
src/hidapi/src/CMakeLists.txt

@@ -141,6 +141,18 @@ else()
                 set(HIDAPI_NEED_EXPORT_LIBUDEV TRUE)
             endif()
         endif()
+    elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
+        if(NOT DEFINED HIDAPI_WITH_NETBSD)
+            set(HIDAPI_WITH_NETBSD ON)
+        endif()
+        if(HIDAPI_WITH_NETBSD)
+            add_subdirectory("${PROJECT_ROOT}/netbsd" netbsd)
+            list(APPEND EXPORT_COMPONENTS netbsd)
+            set(EXPORT_ALIAS netbsd)
+            if(NOT BUILD_SHARED_LIBS)
+                set(HIDAPI_NEED_EXPORT_THREADS TRUE)
+            endif()
+        endif()
     else()
         set(HIDAPI_WITH_LIBUSB ON)
     endif()