From 7eea384cbb3747f8cf393a45a273778b099bad05 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Fri, 24 Apr 2026 17:11:04 +0300 Subject: [PATCH 1/4] Document hotplug callback thread-safety contract Expand the doc comments on hid_hotplug_callback_fn, hid_hotplug_register_callback, and hid_hotplug_deregister_callback to make the hotplug API's thread-safety contract explicit: - The hotplug API is thread-safe; this is a documented exception to HIDAPI's general "not thread-safe" rule. - The callback runs on an internal HIDAPI thread that is distinct from the application's threads, and holds an internal mutex for the duration of each call. - Enumerate safe-to-call functions from within the callback (hid_hotplug_register/deregister_callback, hid_error(dev)) and explain why others require application-level serialisation, matching the wording style in the Multi-threading Notes wiki. - Spell out the lifetime of the device pointer, the string ownership rules, and the requirement that device->next is always NULL inside the callback. - Document hid_exit() as UB from within the callback (self-join). - Define the return-value semantics consistently for both live events and the synthetic "arrived" events delivered during HID_API_HOTPLUG_ENUMERATE. Supersedes PR #784. Behaviour changes implied by this doc (device->next=NULL, ENUMERATE honoring return value) are tracked as follow-up issues. Co-Authored-By: Claude Opus 4.7 (1M context) --- hidapi/hidapi.h | 104 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index d7e89aabe..df7764b64 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -297,31 +297,77 @@ extern "C" { HID_API_HOTPLUG_ENUMERATE = (1 << 0) } hid_hotplug_flag; - /** @brief Hotplug callback function type. When requesting hotplug event notifications, - you pass a pointer to a callback function of this type. + /** @brief Hotplug callback function type. - This callback may be called by an internal event thread and as such it is - recommended the callback do minimal processing before returning. + Called by HIDAPI when a device matching the registration filter + is connected or disconnected. See #hid_hotplug_register_callback. - hidapi will call this function later, when a matching event had happened on - a matching device. + ## Execution context - Note that when callbacks are called from hid_hotplug_register_callback() - because of the \ref HID_API_HOTPLUG_ENUMERATE flag, the callback return - value is ignored. In other words, you cannot cause a callback to be - deregistered by returning 1 when it is called from hid_hotplug_register_callback(). + The callback is invoked on an internal HIDAPI thread that is + distinct from any thread the application creates. Every callback + invocation holds an internal hotplug mutex for its duration, which + means that any other thread calling hid_hotplug_register_callback() + or hid_hotplug_deregister_callback() will block until the callback + returns. Keep the callback short. + + ## What the callback may call + + The hotplug API itself is thread-safe (see "Thread safety" under + #hid_hotplug_register_callback) and the following calls are always + safe from within the callback: + + - hid_hotplug_register_callback() + - hid_hotplug_deregister_callback() (including on its own handle) + - hid_error(dev) with a non-NULL device handle + + Any other HIDAPI function follows HIDAPI's general thread-safety + rule (see the Multi-threading Notes in the project wiki): it is + the application's responsibility to serialize hid_init / hid_exit / + hid_enumerate / hid_open* / hid_close / hid_error(NULL) across all + threads, including the hotplug callback thread. If your application + already calls those functions only from one thread, calling them + from the hotplug callback adds a second thread and is therefore + UNSAFE unless the application adds synchronisation itself. The + recommended pattern is to copy the needed fields of @p device out + of the callback and handle open/close on your own thread. + + Calling hid_exit() from within the callback has undefined behavior: + hid_exit() joins the hotplug thread, which would be joining itself. + + ## device parameter + + The @p device pointer is owned by HIDAPI and is valid only for the + duration of the call. To keep any of its fields (path, + serial_number, manufacturer_string, etc.) beyond the callback, + copy them out; pointer fields must be deep-copied, as all strings + are owned by HIDAPI. + + The @p device->next pointer is always NULL. Each callback + invocation describes exactly one device; compound or composite + devices that expose multiple interfaces produce multiple callback + invocations (typically delivered in quick succession). + + ## Return value + + Return 0 to keep the callback registered. Return any non-zero + value to have HIDAPI deregister the callback; no further events + for this handle will be delivered, and the handle is freed as if + hid_hotplug_deregister_callback() had been called. This applies + to both live events and to the synthetic "arrived" events + delivered during registration when #HID_API_HOTPLUG_ENUMERATE is + set. @ingroup API - @param callback_handle The hid_hotplug_callback_handle callback handle. - @param device The hid_device_info of device this event occurred on event that occurred. - @param event Event that occurred. - @param user_data User data provided when this callback was registered. - (Optionally NULL). + @param callback_handle The handle of this callback. + @param device The hid_device_info of the device this event occurred on. + @param event Event that occurred. See \ref hid_hotplug_event. + @param user_data User data provided when this callback was registered + (may be NULL). - @returns bool - Whether this callback is finished processing events. - Returning non-zero value will cause this callback to be deregistered. + @returns + 0 to stay registered; any non-zero value to be deregistered. */ typedef int (HID_API_CALL *hid_hotplug_callback_fn)( hid_hotplug_callback_handle callback_handle, @@ -335,6 +381,20 @@ extern "C" { If @p product_id is set to 0 then any product matches. If @p vendor_id and @p product_id are both set to 0, then all HID devices will be notified. + ## Thread safety + + hid_hotplug_register_callback() and hid_hotplug_deregister_callback() + are thread-safe. They may be called from any thread, including + from within a hotplug callback. This is a deliberate exception + to HIDAPI's general "not thread-safe" rule (see the + Multi-threading Notes in the project wiki). + + The first successful call to hid_hotplug_register_callback() + starts an internal HIDAPI thread that runs until either (a) the + last callback is deregistered, or (b) hid_exit() is called. + hid_exit() must not be called from within a hotplug callback + (see #hid_hotplug_callback_fn). + @ingroup API @param vendor_id The Vendor ID (VID) of the types of device to notify about. @@ -356,7 +416,13 @@ extern "C" { /** @brief Deregister a callback from a HID hotplug. - This function is safe to call from within a hotplug callback. + Thread-safe. May be called from any thread, including from within + a hotplug callback (on its own handle or on another callback's + handle). Calling it on a handle that was already deregistered, + or on a handle that was never valid, is a safe no-op returning 0. + + See "Thread safety" on #hid_hotplug_register_callback for the + full thread-safety contract of the hotplug API. @ingroup API From 9f416d045ee960be73d8ef4e9ce71829772ff2aa Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Mon, 13 Jul 2026 18:45:05 +0300 Subject: [PATCH 2/4] Hotplug docs: align with hidapi.h conventions and verified backend behavior Add "Since version 0.16.0" annotations and missing @brief's; document the ENUMERATE synchronous-delivery semantics, DEVICE_LEFT cached device info, invocation order, handle positivity, implicit hid_init and hid_exit cleanup; fix the deregister return-value contract (-1 for unknown handles) and minor wording. Where the documented (target) semantics diverge from a backend's current behavior, the gap is tracked as a follow-up, same as the rest of this PR. Assisted-by: claude-code:claude-fable-5 --- hidapi/hidapi.h | 95 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index df7764b64..eb17e2b31 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -263,17 +263,23 @@ extern "C" { /** @brief Callback handle. - Callbacks handles are generated by hid_hotplug_register_callback() + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) + + Callback handles are generated by hid_hotplug_register_callback() and can be used to deregister callbacks. Callback handles are unique and it is safe to call hid_hotplug_deregister_callback() on an already deregistered callback. + A valid callback handle is always a positive value; + 0 is never a valid handle and may be used as a sentinel. + @ingroup API */ typedef int hid_hotplug_callback_handle; - /** - Hotplug events + /** @brief Hotplug events. + + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) @ingroup API */ @@ -282,35 +288,58 @@ extern "C" { HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0), /** A device has left and is no longer available. - It is the user's responsibility to call hid_close with a disconnected device. + If the application holds an open handle to the device, it is + still the application's responsibility to close it with + hid_close(). */ HID_API_HOTPLUG_EVENT_DEVICE_LEFT = (1 << 1) } hid_hotplug_event; - /** - Hotplug flags + /** @brief Hotplug flags. + + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) @ingroup API */ typedef enum { - /** Arm the callback and fire it for all matching currently attached devices. */ + /** Arm the callback and fire it for all matching devices that are + already connected at registration time: + hid_hotplug_register_callback() itself invokes the callback, + synchronously on the calling thread, once for each such device, + before it returns. + + These synthetic "arrived" events are delivered only when + #HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED is present in the + requested events mask. */ HID_API_HOTPLUG_ENUMERATE = (1 << 0) } hid_hotplug_flag; /** @brief Hotplug callback function type. + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) + Called by HIDAPI when a device matching the registration filter is connected or disconnected. See #hid_hotplug_register_callback. ## Execution context The callback is invoked on an internal HIDAPI thread that is - distinct from any thread the application creates. Every callback - invocation holds an internal hotplug mutex for its duration, which - means that any other thread calling hid_hotplug_register_callback() - or hid_hotplug_deregister_callback() will block until the callback + distinct from any thread the application creates. The only + exception is the synthetic enumeration pass requested with + #HID_API_HOTPLUG_ENUMERATE, which is delivered on the thread + that calls hid_hotplug_register_callback(), before that call + returns. + + Every callback invocation holds an internal hotplug mutex for + its duration, which means that any other thread calling + hid_hotplug_register_callback() or + hid_hotplug_deregister_callback() will block until the callback returns. Keep the callback short. + When multiple callbacks are registered, each event is delivered + to every matching callback sequentially, in the order the + callbacks were registered. + ## What the callback may call The hotplug API itself is thread-safe (see "Thread safety" under @@ -348,6 +377,13 @@ extern "C" { devices that expose multiple interfaces produce multiple callback invocations (typically delivered in quick succession). + For #HID_API_HOTPLUG_EVENT_DEVICE_LEFT events @p device points to + a copy captured when the device arrived (or was enumerated): all + fields, including the strings, are valid and describe the device + as it was while connected. The path field matches the one + reported by the corresponding "arrived" event and may be used to + correlate the two. + ## Return value Return 0 to keep the callback registered. Return any non-zero @@ -362,7 +398,8 @@ extern "C" { @param callback_handle The handle of this callback. @param device The hid_device_info of the device this event occurred on. - @param event Event that occurred. See \ref hid_hotplug_event. + @param event Event that occurred: exactly one value (not a mask) + of \ref hid_hotplug_event. @param user_data User data provided when this callback was registered (may be NULL). @@ -377,10 +414,21 @@ extern "C" { /** @brief Register a HID hotplug callback function. + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) + If @p vendor_id is set to 0 then any vendor matches. If @p product_id is set to 0 then any product matches. If @p vendor_id and @p product_id are both set to 0, then all HID devices will be notified. + If HIDAPI is not initialized yet, this function initializes it + implicitly (as if by hid_init()). + + When #HID_API_HOTPLUG_ENUMERATE is set, @p callback_handle + (when non-NULL) is written before any of the synthetic "arrived" + events are delivered, and the callback always receives its own + handle as a parameter, so the callback may deregister itself + even during that initial pass. + ## Thread safety hid_hotplug_register_callback() and hid_hotplug_deregister_callback() @@ -390,10 +438,12 @@ extern "C" { Multi-threading Notes in the project wiki). The first successful call to hid_hotplug_register_callback() - starts an internal HIDAPI thread that runs until either (a) the - last callback is deregistered, or (b) hid_exit() is called. - hid_exit() must not be called from within a hotplug callback - (see #hid_hotplug_callback_fn). + starts HIDAPI's internal hotplug machinery (on most platforms an + internal thread), which runs until either (a) the last callback + is deregistered, or (b) hid_exit() is called. hid_exit() + deregisters any callbacks that are still registered and + invalidates their handles. hid_exit() must not be called from + within a hotplug callback (see #hid_hotplug_callback_fn). @ingroup API @@ -411,15 +461,22 @@ extern "C" { @returns This function returns 0 on success or -1 on error. + Call hid_error(NULL) to get the failure reason. + + @note On backends without hotplug support (e.g. NetBSD) + this function always returns -1. */ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void *user_data, hid_hotplug_callback_handle *callback_handle); /** @brief Deregister a callback from a HID hotplug. + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) + Thread-safe. May be called from any thread, including from within a hotplug callback (on its own handle or on another callback's handle). Calling it on a handle that was already deregistered, - or on a handle that was never valid, is a safe no-op returning 0. + or on a handle that was never valid, is safe: it has no effect + and returns -1. See "Thread safety" on #hid_hotplug_register_callback for the full thread-safety contract of the hotplug API. @@ -429,7 +486,9 @@ extern "C" { @param callback_handle The handle of the callback to deregister. @returns - This function returns 0 on success or -1 on error. + This function returns 0 when the callback was found and + deregistered, or -1 on error (including when + @p callback_handle is not a registered handle). */ int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle); From f8362b1cfa0e751652d8f1ca39d7ae1078f2d2d6 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Mon, 13 Jul 2026 20:48:37 +0300 Subject: [PATCH 3/4] Hotplug docs: ENUMERATE initial pass is asynchronous, on the event context Adopt the async design for HID_API_HOTPLUG_ENUMERATE: the initial pass for already-connected devices is delivered on the same internal event context as live events, from a registration-time snapshot (exactly-once, before any live events for that callback), honoring the callback return value (early stop + deregister). The callback is thus never invoked on an application thread. Also tighten the contract per review: deregistration post-condition (safe to free user_data after return), handle non-reuse, argument validation, out-param on failure, DEVICE_LEFT ordering and coverage, and replace markdown headings with @par (Doxygen MARKDOWN_SUPPORT is off, so ## rendered as literal text). Assisted-by: claude-code:claude-fable-5 --- hidapi/hidapi.h | 118 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 32 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index eb17e2b31..0725663a7 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -266,9 +266,11 @@ extern "C" { Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) Callback handles are generated by hid_hotplug_register_callback() - and can be used to deregister callbacks. Callback handles are unique - and it is safe to call hid_hotplug_deregister_callback() on - an already deregistered callback. + and can be used to deregister callbacks. Callback handles are + unique and are not reused while the library remains initialized, + so it is safe to call hid_hotplug_deregister_callback() on an + already deregistered callback: it fails with -1 and never + affects another callback. A valid callback handle is always a positive value; 0 is never a valid handle and may be used as a sentinel. @@ -284,7 +286,9 @@ extern "C" { @ingroup API */ typedef enum { - /** A device has been plugged in and is ready to use */ + /** A device has been plugged in and may be opened. + Opening can still fail, e.g. due to insufficient permissions + or if the device has already disconnected again. */ HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0), /** A device has left and is no longer available. @@ -303,14 +307,33 @@ extern "C" { */ typedef enum { /** Arm the callback and fire it for all matching devices that are - already connected at registration time: - hid_hotplug_register_callback() itself invokes the callback, - synchronously on the calling thread, once for each such device, - before it returns. + already connected at registration time. + + hid_hotplug_register_callback() takes a snapshot of the + matching connected devices, and the callback is invoked + asynchronously, on the same internal event context that + delivers live events (see #hid_hotplug_callback_fn), once + for each device in that snapshot. Devices whose arrival is + detected after the snapshot is taken are reported as regular + live events: each device connection is reported to the + callback exactly once - either by the initial pass or as a + live event, never both and never neither. (A device that + disconnects and reconnects is a new connection and is + reported again.) + + The initial pass is delivered before any live events for + this callback. In particular, a callback registered with + this flag for both event types never observes a + #HID_API_HOTPLUG_EVENT_DEVICE_LEFT for a matching device + whose arrival it has not been told about first. These synthetic "arrived" events are delivered only when #HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED is present in the - requested events mask. */ + requested events mask. + + hid_hotplug_register_callback() returning does not imply + the initial pass has been delivered yet: the synthetic + events may fire before or after it returns. */ HID_API_HOTPLUG_ENUMERATE = (1 << 0) } hid_hotplug_flag; @@ -321,14 +344,18 @@ extern "C" { Called by HIDAPI when a device matching the registration filter is connected or disconnected. See #hid_hotplug_register_callback. - ## Execution context + @par Execution context - The callback is invoked on an internal HIDAPI thread that is - distinct from any thread the application creates. The only - exception is the synthetic enumeration pass requested with - #HID_API_HOTPLUG_ENUMERATE, which is delivered on the thread - that calls hid_hotplug_register_callback(), before that call - returns. + The callback is only ever invoked on HIDAPI's internal event + context, never on an application thread (including the + application's main thread). This includes the synthetic + "arrived" events requested with #HID_API_HOTPLUG_ENUMERATE: + they are delivered asynchronously on that same context and are + never delivered from within the hid_hotplug_register_callback() + call itself. (When a hotplug callback itself registers a new + callback with #HID_API_HOTPLUG_ENUMERATE, the new registration's + synthetic events are delivered later, on this same event + context.) Every callback invocation holds an internal hotplug mutex for its duration, which means that any other thread calling @@ -340,7 +367,7 @@ extern "C" { to every matching callback sequentially, in the order the callbacks were registered. - ## What the callback may call + @par What the callback may call The hotplug API itself is thread-safe (see "Thread safety" under #hid_hotplug_register_callback) and the following calls are always @@ -348,13 +375,16 @@ extern "C" { - hid_hotplug_register_callback() - hid_hotplug_deregister_callback() (including on its own handle) - - hid_error(dev) with a non-NULL device handle + - hid_error(dev) with a non-NULL device handle, provided no + other thread uses that same handle concurrently Any other HIDAPI function follows HIDAPI's general thread-safety rule (see the Multi-threading Notes in the project wiki): it is the application's responsibility to serialize hid_init / hid_exit / hid_enumerate / hid_open* / hid_close / hid_error(NULL) across all - threads, including the hotplug callback thread. If your application + threads, including the hotplug callback thread (hid_exit() + additionally must never be called from within the callback + itself - see below). If your application already calls those functions only from one thread, calling them from the hotplug callback adds a second thread and is therefore UNSAFE unless the application adds synchronisation itself. The @@ -364,7 +394,7 @@ extern "C" { Calling hid_exit() from within the callback has undefined behavior: hid_exit() joins the hotplug thread, which would be joining itself. - ## device parameter + @par The device parameter The @p device pointer is owned by HIDAPI and is valid only for the duration of the call. To keep any of its fields (path, @@ -380,19 +410,25 @@ extern "C" { For #HID_API_HOTPLUG_EVENT_DEVICE_LEFT events @p device points to a copy captured when the device arrived (or was enumerated): all fields, including the strings, are valid and describe the device - as it was while connected. The path field matches the one - reported by the corresponding "arrived" event and may be used to - correlate the two. + as it was while connected. A "left" event is delivered for any + matching device that disconnects while the callback is + registered, including devices that were already connected + before the registration (their arrival is reported to this + callback only if #HID_API_HOTPLUG_ENUMERATE was used). When the + callback has observed the device's arrival, the path field + matches the one reported then and may be used to correlate the + two events. - ## Return value + @par Return value Return 0 to keep the callback registered. Return any non-zero value to have HIDAPI deregister the callback; no further events for this handle will be delivered, and the handle is freed as if hid_hotplug_deregister_callback() had been called. This applies - to both live events and to the synthetic "arrived" events - delivered during registration when #HID_API_HOTPLUG_ENUMERATE is - set. + to both live events and to the synthetic "arrived" events of + #HID_API_HOTPLUG_ENUMERATE: a non-zero return during the + initial pass stops the remainder of that pass and deregisters + the callback. @ingroup API @@ -423,13 +459,17 @@ extern "C" { If HIDAPI is not initialized yet, this function initializes it implicitly (as if by hid_init()). - When #HID_API_HOTPLUG_ENUMERATE is set, @p callback_handle - (when non-NULL) is written before any of the synthetic "arrived" - events are delivered, and the callback always receives its own + When #HID_API_HOTPLUG_ENUMERATE is set, the synthetic "arrived" + events are delivered asynchronously on HIDAPI's internal event + context (see #hid_hotplug_flag): they may fire before or after + this function returns, but never from within this call itself, + and never on an application thread. + @p callback_handle (when non-NULL) is written before any events + can be delivered, and the callback always receives its own handle as a parameter, so the callback may deregister itself even during that initial pass. - ## Thread safety + @par Thread safety hid_hotplug_register_callback() and hid_hotplug_deregister_callback() are thread-safe. They may be called from any thread, including @@ -457,11 +497,15 @@ extern "C" { See \ref hid_hotplug_callback_fn. @param user_data The user data you wanted to provide to your callback function. @param callback_handle Pointer to store the handle of the allocated callback - (Optionally NULL). + (Optionally NULL). On failure, *callback_handle (when + non-NULL) is set to 0. @returns This function returns 0 on success or -1 on error. Call hid_error(NULL) to get the failure reason. + Registration fails if @p callback is NULL, if @p events + contains no valid #hid_hotplug_event bit, or if @p events + or @p flags contain unknown bits. @note On backends without hotplug support (e.g. NetBSD) this function always returns -1. @@ -478,6 +522,16 @@ extern "C" { or on a handle that was never valid, is safe: it has no effect and returns -1. + When called from any thread other than HIDAPI's internal event + context, this function does not return until an in-progress + invocation of the callback (if any) has completed; once it + returns, the callback will not be invoked again - including any + undelivered synthetic events of #HID_API_HOTPLUG_ENUMERATE - + and it is safe to release any resources the callback uses + (e.g. whatever @p user_data points to). When called from within + a hotplug callback, the deregistered callback will not be + invoked once the currently executing invocation returns. + See "Thread safety" on #hid_hotplug_register_callback for the full thread-safety contract of the hotplug API. From 11ed3273f60274da1cfdf94cd8a1669a3c26b44e Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Tue, 14 Jul 2026 00:07:59 +0300 Subject: [PATCH 4/4] Hotplug docs: clarify the hotplug mutex is re-entrant Registering/deregistering from within a callback re-acquires the mutex on the same (event context) thread, so it cannot deadlock; only calls from other threads block until the callback returns. State this explicitly instead of leaving it implied by the word "other". Assisted-by: claude-code:claude-fable-5 --- hidapi/hidapi.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 0725663a7..4c50d1503 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -358,10 +358,14 @@ extern "C" { context.) Every callback invocation holds an internal hotplug mutex for - its duration, which means that any other thread calling + its duration: any other thread calling hid_hotplug_register_callback() or hid_hotplug_deregister_callback() will block until the callback - returns. Keep the callback short. + returns. The mutex is re-entrant: calling those functions from + within the callback itself (i.e. on the event context, which + already holds the mutex) does not block and cannot deadlock - + that is what makes them safe to call from inside the callback + (see below). Keep the callback short. When multiple callbacks are registered, each event is delivered to every matching callback sequentially, in the order the