Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions hidapi/hidapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,55 @@ extern "C" {
*/
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock);

/** @brief Upper bound for hid_set_num_input_buffers().

Values passed above this limit are rejected by
hid_set_num_input_buffers(). Guards against
memory-exhaustion via unbounded input report queue growth.

May be overridden at build time via
-DHID_API_MAX_NUM_INPUT_BUFFERS=<value>.
*/
#ifndef HID_API_MAX_NUM_INPUT_BUFFERS
#define HID_API_MAX_NUM_INPUT_BUFFERS 1024
Comment thread
Youw marked this conversation as resolved.
#endif

/** @brief Set the number of input report buffers queued per device.

Some HID devices emit input reports in bursts at rates
that exceed the default internal queue capacity, causing
silent report drops on macOS and the libusb Linux backend.
This function allows callers to change how many input
report buffers are retained per device.

Call after hid_open() and before the first hid_read() to
avoid losing reports buffered at open time.

@note Per-backend behavior:
- **macOS (IOKit)** and **Linux libusb**: resizes the
userspace input-report queue. Default: 30 reports.
- **Windows**: forwards to HidD_SetNumInputBuffers(),
which resizes the kernel HID ring buffer. The kernel
accepts values in the range [2, 512]; requests outside
this range return -1. Default: 64 reports.
- **Linux hidraw** and **NetBSD uhid**: the call is
accepted (returns 0) and validated against
HID_API_MAX_NUM_INPUT_BUFFERS, but has no effect (no-op) —
these kernels manage the input report buffer internally
and expose no userspace resize.

@ingroup API
@param dev A device handle returned from hid_open().
@param num_buffers The desired number of input report buffers.
Must be in range [1, HID_API_MAX_NUM_INPUT_BUFFERS].

@returns
0 on success, -1 on failure (invalid parameters or
backend-specific error). Call hid_error(dev) for
details where supported.
*/
int HID_API_EXPORT HID_API_CALL hid_set_num_input_buffers(hid_device *dev, int num_buffers);
Comment thread
Youw marked this conversation as resolved.

/** @brief Send a Feature report to the device.

Feature reports are sent over the Control endpoint as a
Expand Down
10 changes: 10 additions & 0 deletions hidtest/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ int main(int argc, char* argv[])
(void)&hid_get_input_report;
#if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0)
(void)&hid_send_output_report;
#endif
#if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)
(void)&hid_set_num_input_buffers;
#endif
(void)&hid_get_feature_report;
(void)&hid_send_feature_report;
Expand Down Expand Up @@ -198,6 +201,13 @@ int main(int argc, char* argv[])
return 1;
}

#if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)
res = hid_set_num_input_buffers(handle, 500);
if (res < 0) {
printf("Unable to set input buffers: %ls\n", hid_error(handle));
}
#endif

#if defined(_WIN32) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0)
hid_winapi_set_write_timeout(handle, 5000);
#endif
Expand Down
123 changes: 60 additions & 63 deletions libusb/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#endif

#include "hidapi_libusb.h"
#include "hidapi_input_ring.h"

#ifndef HIDAPI_THREAD_MODEL_INCLUDE
#define HIDAPI_THREAD_MODEL_INCLUDE "hidapi_thread_pthread.h"
Expand Down Expand Up @@ -77,13 +78,6 @@ libusb HIDAPI programs are encouraged to use the interface number
instead to differentiate between interfaces on a composite HID device. */
/*#define INVASIVE_GET_USAGE*/

/* Linked List of input reports received from the device. */
struct input_report {
uint8_t *data;
size_t len;
struct input_report *next;
};


struct hid_device_ {
/* Handle to the actual device. */
Expand All @@ -110,14 +104,19 @@ struct hid_device_ {
/* Whether blocking reads are used */
int blocking; /* boolean */

/* Maximum number of input reports to queue before dropping oldest. */
int num_input_buffers;

/* Read thread objects */
hidapi_thread_state thread_state;
int shutdown_thread;
int transfer_loop_finished;
struct libusb_transfer *transfer;

/* List of received input reports. */
struct input_report *input_reports;
/* Input report ring buffer. Backing array sized at
HID_API_MAX_NUM_INPUT_BUFFERS at device open; logical cap
(drop-oldest threshold) is dev->num_input_buffers. */
struct hidapi_input_ring input_ring;

/* Was kernel driver detached by libusb */
#ifdef DETACH_KERNEL_DRIVER
Expand All @@ -134,15 +133,20 @@ static struct hid_api_version api_version = {
static libusb_context *usb_context = NULL;

uint16_t get_usb_code_for_current_locale(void);
static int return_data(hid_device *dev, unsigned char *data, size_t length);

static hid_device *new_hid_device(void)
{
hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
if (!dev)
return NULL;

if (hidapi_input_ring_init(&dev->input_ring, HID_API_MAX_NUM_INPUT_BUFFERS) != 0) {
free(dev);
return NULL;
}

dev->blocking = 1;
dev->num_input_buffers = 30;

hidapi_thread_state_init(&dev->thread_state);

Expand All @@ -156,6 +160,9 @@ static void free_hid_device(hid_device *dev)

hid_free_enumeration(dev->device_info);

/* Free any queued input reports and the ring backing array. */
hidapi_input_ring_destroy(&dev->input_ring);

/* Free the device itself */
free(dev);
}
Expand Down Expand Up @@ -958,37 +965,18 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)

if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {

struct input_report *rpt = (struct input_report*) malloc(sizeof(*rpt));
rpt->data = (uint8_t*) malloc(transfer->actual_length);
memcpy(rpt->data, transfer->buffer, transfer->actual_length);
rpt->len = transfer->actual_length;
rpt->next = NULL;

hidapi_thread_mutex_lock(&dev->thread_state);

/* Attach the new report object to the end of the list. */
if (dev->input_reports == NULL) {
/* The list is empty. Put it at the root. */
dev->input_reports = rpt;
int push_rc = hidapi_input_ring_push(&dev->input_ring,
dev->num_input_buffers,
transfer->buffer,
(size_t)transfer->actual_length);
if (push_rc == 0) {
hidapi_thread_cond_signal(&dev->thread_state);
}
else {
/* Find the end of the list and attach. */
struct input_report *cur = dev->input_reports;
int num_queued = 0;
while (cur->next != NULL) {
cur = cur->next;
num_queued++;
}
cur->next = rpt;
/* Allocation failed; libusb has no active error channel here, so the
* report is dropped silently. */

/* Pop one off if we've reached 30 in the queue. This
way we don't grow forever if the user never reads
anything from the device. */
if (num_queued > 30) {
return_data(dev, NULL, 0);
}
}
hidapi_thread_mutex_unlock(&dev->thread_state);
}
else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
Expand Down Expand Up @@ -1454,19 +1442,18 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t
return actual_length;
}

/* Helper function, to simplify hid_read().
This should be called with dev->mutex locked. */
static int return_data(hid_device *dev, unsigned char *data, size_t length)
/* Pop one report from the ring into (data, length). Caller must
hold dev->thread_state. Returns bytes copied, or -1 if empty. */
static int ring_pop_into(hid_device *dev, unsigned char *data, size_t length)
{
/* Copy the data out of the linked list item (rpt) into the
return buffer (data), and delete the liked list item. */
struct input_report *rpt = dev->input_reports;
size_t len = (length < rpt->len)? length: rpt->len;
if (len > 0)
memcpy(data, rpt->data, len);
dev->input_reports = rpt->next;
free(rpt->data);
free(rpt);
uint8_t *rpt_data;
size_t rpt_len;
if (hidapi_input_ring_pop(&dev->input_ring, &rpt_data, &rpt_len) != 0)
return -1;
size_t len = (length < rpt_len) ? length : rpt_len;
if (len > 0 && data)
memcpy(data, rpt_data, len);
free(rpt_data);
return (int)len;
}

Expand Down Expand Up @@ -1495,9 +1482,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
bytes_read = -1;

/* There's an input report queued up. Return it. */
if (dev->input_reports) {
/* Return the first one */
bytes_read = return_data(dev, data, length);
if (dev->input_ring.count > 0) {
bytes_read = ring_pop_into(dev, data, length);
goto ret;
}

Expand All @@ -1510,11 +1496,11 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t

if (milliseconds == -1) {
/* Blocking */
while (!dev->input_reports && !dev->shutdown_thread) {
while (dev->input_ring.count == 0 && !dev->shutdown_thread) {
hidapi_thread_cond_wait(&dev->thread_state);
}
if (dev->input_reports) {
bytes_read = return_data(dev, data, length);
if (dev->input_ring.count > 0) {
bytes_read = ring_pop_into(dev, data, length);
}
}
else if (milliseconds > 0) {
Expand All @@ -1524,11 +1510,11 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
hidapi_thread_gettime(&ts);
hidapi_thread_addtime(&ts, milliseconds);

while (!dev->input_reports && !dev->shutdown_thread) {
while (dev->input_ring.count == 0 && !dev->shutdown_thread) {
res = hidapi_thread_cond_timedwait(&dev->thread_state, &ts);
if (res == 0) {
if (dev->input_reports) {
bytes_read = return_data(dev, data, length);
if (dev->input_ring.count > 0) {
bytes_read = ring_pop_into(dev, data, length);
break;
}

Expand Down Expand Up @@ -1574,6 +1560,21 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev)
}



int HID_API_EXPORT hid_set_num_input_buffers(hid_device *dev, int num_buffers)
{
if (num_buffers <= 0 || num_buffers > HID_API_MAX_NUM_INPUT_BUFFERS)
return -1;
hidapi_thread_mutex_lock(&dev->thread_state);
dev->num_input_buffers = num_buffers;
if (dev->input_ring.count > num_buffers) {
hidapi_input_ring_drop_oldest(&dev->input_ring,
dev->input_ring.count - num_buffers);
}
hidapi_thread_mutex_unlock(&dev->thread_state);
return 0;
}

int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
{
dev->blocking = !nonblock;
Expand Down Expand Up @@ -1734,12 +1735,8 @@ void HID_API_EXPORT hid_close(hid_device *dev)
/* Close the handle */
libusb_close(dev->device_handle);

/* Clear out the queue of received reports. */
hidapi_thread_mutex_lock(&dev->thread_state);
while (dev->input_reports) {
return_data(dev, NULL, 0);
}
hidapi_thread_mutex_unlock(&dev->thread_state);
/* Queued reports are freed inside free_hid_device via
hidapi_input_ring_destroy. */

free_hid_device(dev);
}
Expand Down
Loading
Loading