From bae6cb856ea36d965813a9cc7d23336a6f92d087 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 18 Jul 2016 08:47:57 +0200 Subject: [PATCH] Add a new USB HID communication backend. --- src/Makefile.am | 2 + src/usbhid.c | 347 ++++++++++++++++++++++++++++++++++++++++++++++++ src/usbhid.h | 122 +++++++++++++++++ 3 files changed, 471 insertions(+) create mode 100644 src/usbhid.c create mode 100644 src/usbhid.h diff --git a/src/Makefile.am b/src/Makefile.am index ed7b6f8..e52d1cd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/usbhid.c b/src/usbhid.c new file mode 100644 index 0000000..1bdb1fd --- /dev/null +++ b/src/usbhid.c @@ -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 + +#if defined(HAVE_LIBUSB) +#ifdef _WIN32 +#define NOGDI +#endif +#include +#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; +} diff --git a/src/usbhid.h b/src/usbhid.h new file mode 100644 index 0000000..65d98c1 --- /dev/null +++ b/src/usbhid.h @@ -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 +#include + +#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 */