mirror of https://github.com/nirenjan/libx52.git
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
parent
152a3e7932
commit
946916f456
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
Loading…
Reference in New Issue