Add hotplug support to libx52

Prior to this change, libx52 would require that the X52/Pro be plugged
in before initialization. This change allows the application to
initialize libx52 even before the device is plugged in.
debian-packaging
nirenjan 2020-04-14 14:19:16 -07:00
parent 152a3e7932
commit 946916f456
5 changed files with 366 additions and 8 deletions

View File

@ -0,0 +1,63 @@
libx52 Hotplug Support
======================
This document describes a design for USB hotplug support in libx52. The idea is
to have a daemon process running at all times, which will update the hardware
when the X52/X52Pro is plugged in.
# Assumptions
The core assumption is that a maximum of 1 device is present at any time. There
is no goal to support more than 1 X52/X52Pro at the same time.
# LibUSB Hotplug Support
libusb-1.0 version 1.0.16 and newer support hotplug notifications when a device
is inserted or removed. These call a registered callback function which can read
the device descriptor, and take action accordingly.
# libx52 Hotplug design
The hotplug mechanism consists of the following functions:
```
int libx52_hotplug_init(libx52_hotplug_service **svc);
void libx52_hotplug_exit(libx52_hotplug_service *svc);
int libx52_hotplug_register_callback(libx52_hotplug_service *svc,
libx52_hotplug_fn *callback_fn,
void *user_data,
libx52_hotplug_callback_handle **cb_handle);
int libx52_hotplug_deregister_callback(libx52_hotplug_callback_handle *cb_handle);
typedef void (*libx52_hotplug_fn)(bool inserted, void *user_data, libx52_device *dev);
```
The init function will take care of initializing libx52, so the user should
not call `libx52_init`. The exit function will also take care of deinitializing
libx52, so the user should not call `libx52_exit`.
The user may register any number of callbacks, and they are called in an
undefined order on device insertion and removal. Each register callback also
returns a callback handle in an output parameter. This callback handle can be
used to deregister the callback in the future, if necessary.
`libx52_hotplug_exit` will take care of deregistering all registered callbacks,
and will deinitialize libx52.
# Interaction with libusbx52
As of this writing, libusbx52 reads from a regular file, however, in order to
support hotplug, the read should read from a fifo. Under Linux, a program can
open a fifo for reading and writing, and this should allow the program to write
a small amount of data into the fifo and exit, and the program using libusbx52
can read from that file.
In this case, the data format should change to include an additional character
prior to the idVendor and idProduct fields. This additional character will
indicate whether the device is being inserted or removed.
libusbx52 must now spawn a separate thread to deal with the fifo. This thread
will read from the fifo, and callback the registered handler when an event
occurs.

View File

@ -20,4 +20,4 @@ x52includedir = $(includedir)/x52pro
x52include_HEADERS = libx52.h
# Extra files that need to be in the distribution
EXTRA_DIST = libx52.h x52_commands.h x52_common.h README.md
EXTRA_DIST = libx52.h x52_commands.h x52_common.h x52_hotplug.h README.md

View File

@ -26,6 +26,26 @@ struct libx52_device;
*/
typedef struct libx52_device libx52_device;
/**
* @brief Opaque structure used by libx52 hotplug API
*/
struct libx52_hotplug_callback_handle;
/**
* @brief Opaque structure used by libx52 hotplug API
*/
typedef struct libx52_hotplug_callback_handle libx52_hotplug_callback_handle;
/**
* @brief Opaque structure used by libx52 hotplug API
*/
struct libx52_hotplug_service;
/**
* @brief Opaque structure used by libx52 hotplug API
*/
typedef struct libx52_hotplug_service libx52_hotplug_service;
/**
* @brief List of supported clocks on the MFD
*/
@ -194,7 +214,7 @@ typedef enum {
* structures to access the joystick, and returns a \ref libx52_device pointer.
* All calls to libx52 use the returned pointer to control the device.
*
* If no joystick is found `libx52_init()` returns _NULL_.
* If no joystick is found `libx52_init()` returns \ref LIBX52_ERROR_NO_DEVICE.
*
* @par Limitations
* This function does not support hotplugging. The joystick must be plugged in

View File

@ -15,6 +15,7 @@
#include "libx52.h"
#include "x52_commands.h"
#include "x52_common.h"
#include "x52_hotplug.h"
#define VENDOR_SAITEK 0x06a3
#define X52_PROD_X52PRO 0x0762
@ -36,10 +37,12 @@ static int libx52_check_product(uint16_t idVendor, uint16_t idProduct)
return 0;
}
/* Check if the attached device is an X52 Pro */
static int libx52_device_is_x52pro(uint16_t idProduct)
/* Set flags according to the device model */
static void libx52_set_dev_flags(libx52_device *dev, uint16_t idProduct)
{
return (idProduct == X52_PROD_X52PRO);
if (idProduct == X52_PROD_X52PRO) {
set_bit(&(dev->flags), X52_FLAG_IS_PRO);
}
}
int libx52_init(libx52_device **dev)
@ -93,9 +96,7 @@ int libx52_init(libx52_device **dev)
x52_dev->hdl = hdl;
if (libx52_device_is_x52pro(desc.idProduct)) {
set_bit(&(x52_dev->flags), X52_FLAG_IS_PRO);
}
libx52_set_dev_flags(x52_dev, desc.idProduct);
break;
}
}
@ -123,3 +124,233 @@ void libx52_exit(libx52_device *dev)
free(dev);
}
static int _hotplug_handler(
libusb_context *ctx,
libusb_device *dev,
libusb_hotplug_event event,
void *user_data
) {
libx52_hotplug_service *svc = user_data;
libx52_hotplug_callback_handle *cb;
struct libusb_device_descriptor desc;
size_t i;
int rc;
rc = libusb_get_device_descriptor(dev, &desc);
if (rc != LIBUSB_SUCCESS) {
return rc;
}
if (libx52_check_product(desc.idVendor, desc.idProduct)) {
return LIBX52_ERROR_NOT_SUPPORTED;
}
if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) {
rc = libusb_open(dev, &(svc->dev->hdl));
if (rc != LIBUSB_SUCCESS) {
return libx52internal_translate_libusb_error(rc);
}
libx52_set_dev_flags(svc->dev, desc.idProduct);
} else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) {
if (svc->dev->hdl != NULL) {
libusb_close(svc->dev->hdl);
svc->dev->hdl = NULL;
}
} else {
return LIBX52_ERROR_INVALID_PARAM;
}
/* Iterate through registered callbacks */
for (i = 0; i < svc->num_callbacks; i++) {
cb = svc->callbacks[i];
if (cb->callback != NULL) {
(cb->callback)(event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
cb->user_data, svc->dev);
}
}
return 0;
}
int libx52_hotplug_init(libx52_hotplug_service **service)
{
libx52_hotplug_service *svc;
int rc = LIBX52_SUCCESS;
/* Make sure that we have a valid return pointer */
if (service == NULL) {
return LIBX52_ERROR_INVALID_PARAM;
}
/* Allocate memory for the library's data structures */
svc = calloc(1, sizeof(*svc));
if (svc == NULL) {
rc = LIBX52_ERROR_OUT_OF_MEMORY;
goto err_recovery;
}
/* Allocate initial buffer for callbacks list */
svc->callbacks = calloc(DEFAULT_NUM_CALLBACKS, sizeof(*(svc->callbacks)));
if (svc->callbacks == NULL) {
rc = LIBX52_ERROR_OUT_OF_MEMORY;
goto err_recovery;
}
svc->num_callbacks = DEFAULT_NUM_CALLBACKS;
svc->dev = calloc(1, sizeof(*(svc->dev)));
if (svc->dev == NULL) {
rc = LIBX52_ERROR_OUT_OF_MEMORY;
goto err_recovery;
}
/* Initialize libusb */
rc = libusb_init(&(svc->dev->ctx));
if (rc != LIBUSB_SUCCESS) {
rc = LIBX52_ERROR_INIT_FAILURE;
goto err_recovery;
}
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000106)
/*
* Use the libusb_set_option flag instead of libusb_set_debug. This
* was introduced in libusb 1.0.22
*/
libusb_set_option(svc->dev->ctx, LIBUSB_OPTION_LOG_LEVEL,
LIBUSB_LOG_LEVEL_WARNING);
#else
libusb_set_debug(svc->dev->ctx, LIBUSB_LOG_LEVEL_WARNING);
#endif
/* Register with the libusb hotplug API */
rc = libusb_hotplug_register_callback(svc->dev->ctx,
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
LIBUSB_HOTPLUG_ENUMERATE, VENDOR_SAITEK,
LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
_hotplug_handler, svc, &(svc->cb_handle));
if (rc != LIBUSB_SUCCESS) {
rc = LIBX52_ERROR_INIT_FAILURE;
libusb_exit(svc->dev->ctx);
goto err_recovery;
}
*service = svc;
return rc;
err_recovery:
if (svc->dev != NULL) {
free(svc->dev);
}
if (svc->callbacks != NULL) {
free(svc->callbacks);
}
free(svc);
return rc;
}
int libx52_hotplug_exit(libx52_hotplug_service *svc) {
if (svc == NULL || svc->dev == NULL || svc->callbacks == NULL) {
return LIBX52_ERROR_INVALID_PARAM;
}
/* Deregister the callback handle */
libusb_hotplug_deregister_callback(svc->dev->ctx, svc->cb_handle);
/* Clean up registered callback handlers */
for (size_t i = 0; i < svc->num_callbacks; i++) {
free(svc->callbacks[i]);
}
free(svc->callbacks);
if (svc->dev->hdl != NULL) {
libusb_close(svc->dev->hdl);
}
libusb_exit(svc->dev->ctx);
/* Clear the device area and free it */
memset(svc->dev, 0, sizeof(*svc->dev));
free(svc->dev);
/* Clear the service pointer before freeing it */
memset(svc, 0, sizeof(*svc));
free(svc);
return LIBX52_SUCCESS;
}
int libx52_hotplug_register_callback(libx52_hotplug_service *svc,
libx52_hotplug_fn callback_fn,
void *user_data,
libx52_hotplug_callback_handle **cb_handle)
{
libx52_hotplug_callback_handle *hdl;
libx52_hotplug_callback_handle **callbacks;
size_t i;
if (svc == NULL || callback_fn == NULL) {
return LIBX52_ERROR_INVALID_PARAM;
}
/* Find an empty slot */
for (i = 0; i < svc->num_callbacks; i++) {
if (svc->callbacks[i] == NULL) {
break;
}
}
if (i == svc->num_callbacks) {
/* Existing callbacks array is full, create a new one */
callbacks = calloc(svc->num_callbacks + DEFAULT_NUM_CALLBACKS, sizeof(*callbacks));
if (callbacks == NULL) {
return LIBX52_ERROR_OUT_OF_MEMORY;
}
/* Copy the callbacks array to the new one */
for (i = 0; i < svc->num_callbacks; i++) {
callbacks[i] = svc->callbacks[i];
}
/* Free the old array, and adjust the link to the new larger array */
svc->num_callbacks += DEFAULT_NUM_CALLBACKS;
free(svc->callbacks);
svc->callbacks = callbacks;
}
hdl = calloc(1, sizeof(*hdl));
if (hdl == NULL) {
return LIBX52_ERROR_OUT_OF_MEMORY;
}
hdl->svc = svc;
hdl->id = i;
hdl->callback = callback_fn;
hdl->user_data = user_data;
/* Insert the node into the callbacks list */
svc->callbacks[i] = hdl;
*cb_handle = hdl;
return LIBX52_SUCCESS;
}
int libx52_hotplug_deregister_callback(libx52_hotplug_callback_handle *hdl)
{
libx52_hotplug_service *svc;
if (hdl == NULL || hdl->svc == NULL) {
return LIBX52_ERROR_INVALID_PARAM;
}
svc = hdl->svc;
if (hdl->id >= svc->num_callbacks) {
return LIBX52_ERROR_INVALID_PARAM;
}
/* Free the handle */
svc->callbacks[hdl->id] = NULL;
free(hdl);
return LIBX52_SUCCESS;
}

View File

@ -0,0 +1,44 @@
/*
* Saitex X52 Pro MFD & LED driver
*
* Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52_HOTPLUG_H
#define X52_HOTPLUG_H
#include <stdint.h>
#include <stdbool.h>
#include <libusb.h>
#include "libx52.h"
/* Callback function for hotplug events */
typedef void (*libx52_hotplug_fn)(bool inserted, void *user_data, libx52_device *dev);
/*
* Structure for callback handle. This is a node in a doubly linked list,
* which is iterated over by the libusb callback handler.
*/
struct libx52_hotplug_callback_handle {
libx52_hotplug_service *svc;
libx52_hotplug_fn callback;
void *user_data;
size_t id;
};
struct libx52_hotplug_service {
libx52_device *dev;
libusb_hotplug_callback_handle cb_handle;
libx52_hotplug_callback_handle **callbacks;
size_t num_callbacks;
};
#define DEFAULT_NUM_CALLBACKS 8
#endif /* !defined X52_HOTPLUG_H */