Add a new USB HID communication backend.

This commit is contained in:
Jef Driesen 2016-07-18 08:47:57 +02:00
parent f30e048afc
commit bae6cb856e
3 changed files with 471 additions and 0 deletions

View File

@ -83,6 +83,8 @@ else
libdivecomputer_la_SOURCES += irda.h irda_dummy.c
endif
libdivecomputer_la_SOURCES += usbhid.h usbhid.c
if OS_WIN32
libdivecomputer_la_SOURCES += libdivecomputer.rc
endif

347
src/usbhid.c Normal file
View File

@ -0,0 +1,347 @@
/*
* libdivecomputer
*
* Copyright (C) 2016 Jef Driesen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#if defined(HAVE_LIBUSB)
#ifdef _WIN32
#define NOGDI
#endif
#include <libusb-1.0/libusb.h>
#endif
#include "usbhid.h"
#include "common-private.h"
#include "context-private.h"
struct dc_usbhid_t {
/* Library context. */
dc_context_t *context;
/* Internal state. */
#if defined(HAVE_LIBUSB)
libusb_context *ctx;
libusb_device_handle *handle;
int interface;
unsigned char endpoint_in;
unsigned char endpoint_out;
unsigned int timeout;
#endif
};
#if defined(HAVE_LIBUSB)
static dc_status_t
syserror(int errcode)
{
switch (errcode) {
case LIBUSB_ERROR_INVALID_PARAM:
return DC_STATUS_INVALIDARGS;
case LIBUSB_ERROR_NO_MEM:
return DC_STATUS_NOMEMORY;
case LIBUSB_ERROR_NO_DEVICE:
case LIBUSB_ERROR_NOT_FOUND:
return DC_STATUS_NODEVICE;
case LIBUSB_ERROR_ACCESS:
case LIBUSB_ERROR_BUSY:
return DC_STATUS_NOACCESS;
case LIBUSB_ERROR_TIMEOUT:
return DC_STATUS_TIMEOUT;
default:
return DC_STATUS_IO;
}
}
#endif
dc_status_t
dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsigned int pid)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_usbhid_t *usbhid = NULL;
int rc = 0;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
INFO (context, "Open: vid=%04x, pid=%04x", vid, pid);
// Allocate memory.
usbhid = (dc_usbhid_t *) malloc (sizeof (dc_usbhid_t));
if (usbhid == NULL) {
ERROR (context, "Out of memory.");
return DC_STATUS_NOMEMORY;
}
// Library context.
usbhid->context = context;
#if defined(HAVE_LIBUSB)
struct libusb_device **devices = NULL;
struct libusb_config_descriptor *config = NULL;
// Initialize the libusb library.
rc = libusb_init (&usbhid->ctx);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to initialize usb support (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_free;
}
// Enumerate the USB devices.
ssize_t ndevices = libusb_get_device_list (usbhid->ctx, &devices);
if (ndevices < 0) {
ERROR (context, "Failed to enumerate the usb devices (%s).",
libusb_error_name (ndevices));
status = syserror (ndevices);
goto error_usb_exit;
}
// Find the first device matching the VID/PID.
struct libusb_device *device = NULL;
for (size_t i = 0; i < ndevices; i++) {
struct libusb_device_descriptor desc;
rc = libusb_get_device_descriptor (devices[i], &desc);
if (rc < 0) {
ERROR (context, "Failed to get the device descriptor (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_usb_free_list;
}
if (desc.idVendor == vid && desc.idProduct == pid) {
device = devices[i];
break;
}
}
if (device == NULL) {
ERROR (context, "No matching USB device found.");
status = DC_STATUS_NODEVICE;
goto error_usb_free_list;
}
// Get the active configuration descriptor.
rc = libusb_get_active_config_descriptor (device, &config);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to get the configuration descriptor (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_usb_free_list;
}
// Find the first HID interface.
const struct libusb_interface_descriptor *interface = NULL;
for (unsigned int i = 0; i < config->bNumInterfaces; i++) {
const struct libusb_interface *iface = &config->interface[i];
for (unsigned int j = 0; j < iface->num_altsetting; j++) {
const struct libusb_interface_descriptor *desc = &iface->altsetting[j];
if (desc->bInterfaceClass == LIBUSB_CLASS_HID && interface == NULL) {
interface = desc;
}
}
}
if (interface == NULL) {
ERROR (context, "No HID interface found.");
status = DC_STATUS_IO;
goto error_usb_free_config;
}
// Find the first input and output interrupt endpoints.
const struct libusb_endpoint_descriptor *ep_in = NULL, *ep_out = NULL;
for (unsigned int i = 0; i < interface->bNumEndpoints; i++) {
const struct libusb_endpoint_descriptor *desc = &interface->endpoint[i];
unsigned int type = desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK;
unsigned int direction = desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK;
if (type != LIBUSB_TRANSFER_TYPE_INTERRUPT)
continue;
if (direction == LIBUSB_ENDPOINT_IN && ep_in == NULL) {
ep_in = desc;
}
if (direction == LIBUSB_ENDPOINT_OUT && ep_out == NULL) {
ep_out = desc;
}
}
if (ep_in == NULL || ep_out == NULL) {
ERROR (context, "No interrupt endpoints found.");
status = DC_STATUS_IO;
goto error_usb_free_config;
}
usbhid->interface = interface->bInterfaceNumber;
usbhid->endpoint_in = ep_in->bEndpointAddress;
usbhid->endpoint_out = ep_out->bEndpointAddress;
usbhid->timeout = 0;
INFO (context, "Open: interface=%u, endpoints=%02x,%02x",
usbhid->interface, usbhid->endpoint_in, usbhid->endpoint_out);
// Open the USB device.
rc = libusb_open (device, &usbhid->handle);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to open the usb device (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_usb_free_config;
}
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
libusb_set_auto_detach_kernel_driver (usbhid->handle, 1);
#endif
// Claim the HID interface.
rc = libusb_claim_interface (usbhid->handle, usbhid->interface);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to claim the usb interface (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_usb_close;
}
libusb_free_config_descriptor (config);
libusb_free_device_list (devices, 1);
#endif
*out = usbhid;
return DC_STATUS_SUCCESS;
#if defined(HAVE_LIBUSB)
error_usb_close:
libusb_close (usbhid->handle);
error_usb_free_config:
libusb_free_config_descriptor (config);
error_usb_free_list:
libusb_free_device_list (devices, 1);
error_usb_exit:
libusb_exit (usbhid->ctx);
#endif
error_free:
free (usbhid);
return status;
}
dc_status_t
dc_usbhid_close (dc_usbhid_t *usbhid)
{
dc_status_t status = DC_STATUS_SUCCESS;
if (usbhid == NULL)
return DC_STATUS_SUCCESS;
#if defined(HAVE_LIBUSB)
libusb_release_interface (usbhid->handle, usbhid->interface);
libusb_close (usbhid->handle);
libusb_exit (usbhid->ctx);
#endif
free (usbhid);
return status;
}
dc_status_t
dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout)
{
if (usbhid == NULL)
return DC_STATUS_INVALIDARGS;
INFO (usbhid->context, "Timeout: value=%i", timeout);
#if defined(HAVE_LIBUSB)
if (timeout < 0) {
usbhid->timeout = 0;
} else if (timeout == 0) {
return DC_STATUS_UNSUPPORTED;
} else {
usbhid->timeout = timeout;
}
#endif
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
int nbytes = 0;
if (usbhid == NULL) {
status = DC_STATUS_INVALIDARGS;
goto out;
}
#if defined(HAVE_LIBUSB)
int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout);
if (rc != LIBUSB_SUCCESS) {
ERROR (usbhid->context, "Usb read interrupt transfer failed (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto out;
}
#endif
out:
HEXDUMP (usbhid->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes);
if (actual)
*actual = nbytes;
return status;
}
dc_status_t
dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
int nbytes = 0;
if (usbhid == NULL) {
status = DC_STATUS_INVALIDARGS;
goto out;
}
#if defined(HAVE_LIBUSB)
int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_out, (void *) data, size, &nbytes, 0);
if (rc != LIBUSB_SUCCESS) {
ERROR (usbhid->context, "Usb write interrupt transfer failed (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto out;
}
#endif
out:
HEXDUMP (usbhid->context, DC_LOGLEVEL_INFO, "Write", (unsigned char *) data, nbytes);
if (actual)
*actual = nbytes;
return status;
}

122
src/usbhid.h Normal file
View File

@ -0,0 +1,122 @@
/*
* libdivecomputer
*
* Copyright (C) 2016 Jef Driesen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifndef DC_USBHID_H
#define DC_USBHID_H
#include <libdivecomputer/common.h>
#include <libdivecomputer/context.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* Opaque object representing a USB HID connection.
*/
typedef struct dc_usbhid_t dc_usbhid_t;
/**
* Open a USB HID connection.
*
* @param[out] usbhid A location to store the USB HID connection.
* @param[in] context A valid context object.
* @param[in] vid The USB Vendor ID of the device.
* @param[in] pid The USB Product ID of the device.
* @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code
* on failure.
*/
dc_status_t
dc_usbhid_open (dc_usbhid_t **usbhid, dc_context_t *context, unsigned int vid, unsigned int pid);
/**
* Close the connection and free all resources.
*
* @param[in] usbhid A valid USB HID connection.
* @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code
* on failure.
*/
dc_status_t
dc_usbhid_close (dc_usbhid_t *usbhid);
/**
* Set the read timeout.
*
* There are three distinct modes available:
*
* 1. Blocking (timeout < 0):
*
* The read operation is blocked until all the requested bytes have
* been received. If the requested number of bytes does not arrive,
* the operation will block forever.
*
* 2. Non-blocking (timeout == 0):
*
* The read operation returns immediately with the bytes that have
* already been received, even if no bytes have been received.
*
* 3. Timeout (timeout > 0):
*
* The read operation is blocked until all the requested bytes have
* been received. If the requested number of bytes does not arrive
* within the specified amount of time, the operation will return
* with the bytes that have already been received.
*
* @param[in] usbhid A valid USB HID connection.
* @param[in] timeout The timeout in milliseconds.
* @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code
* on failure.
*/
dc_status_t
dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout);
/**
* Read data from the USB HID connection.
*
* @param[in] usbhid A valid USB HID connection.
* @param[out] data The memory buffer to read the data into.
* @param[in] size The number of bytes to read.
* @param[out] actual An (optional) location to store the actual
* number of bytes transferred.
* @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code
* on failure.
*/
dc_status_t
dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual);
/**
* Write data to the USB HID connection.
*
* @param[in] usbhid A valid USB HID connection.
* @param[in] data The memory buffer to write the data from.
* @param[in] size The number of bytes to write.
* @param[out] actual An (optional) location to store the actual
* number of bytes transferred.
* @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code
* on failure.
*/
dc_status_t
dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *actual);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* DC_USBHID_H */