From bae6cb856ea36d965813a9cc7d23336a6f92d087 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 18 Jul 2016 08:47:57 +0200 Subject: [PATCH 1/4] 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 */ From ed2a7c91feb7bb207db124f5b4a05917950f00c0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 18 Aug 2016 08:25:02 +0200 Subject: [PATCH 2/4] Use the hidapi library on Mac OS X. On Mac OS X, libusb doesn't work for USB HID devices. We can use the hidapi library instead. Although the hidapi library supports Linux and Windows too, we keep using libusb there to avoid the extra dependency. --- configure.ac | 19 +++++++++++++- src/Makefile.am | 4 +-- src/usbhid.c | 69 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index b8c25dc..0bb834f 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,8 @@ esac AC_MSG_RESULT([$os_win32]) AM_CONDITIONAL([OS_WIN32], [test "$os_win32" = "yes"]) +DEPENDENCIES="" + # Checks for USB support. AC_ARG_WITH([libusb], [AS_HELP_STRING([--without-libusb], @@ -82,10 +84,25 @@ AS_IF([test "x$with_libusb" != "xno"], [ PKG_CHECK_MODULES([LIBUSB], [libusb-1.0], [have_libusb=yes], [have_libusb=no]) AS_IF([test "x$have_libusb" = "xyes"], [ AC_DEFINE([HAVE_LIBUSB], [1], [libusb library]) - AC_SUBST([DEPENDENCIES], [libusb-1.0]) + DEPENDENCIES="$DEPENDENCIES libusb-1.0" ]) ]) +# Checks for HIDAPI support. +AC_ARG_WITH([hidapi], + [AS_HELP_STRING([--without-hidapi], + [Build without the hidapi library])], + [], [with_hidapi=auto]) +AS_IF([test "x$with_hidapi" != "xno"], [ + PKG_CHECK_MODULES([HIDAPI], [hidapi], [have_hidapi=yes], [have_hidapi=no]) + AS_IF([test "x$have_hidapi" = "xyes"], [ + AC_DEFINE([HAVE_HIDAPI], [1], [hidapi library]) + DEPENDENCIES="$DEPENDENCIES hidapi" + ]) +]) + +AC_SUBST([DEPENDENCIES]) + # Checks for IrDA support. AC_CHECK_HEADERS([winsock2.h af_irda.h], [irda_win32=yes], [irda_win32=no], [ #if HAVE_WINSOCK2_H diff --git a/src/Makefile.am b/src/Makefile.am index e52d1cd..fba41a8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,9 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -AM_CFLAGS = $(LIBUSB_CFLAGS) -DENABLE_DEPRECATED +AM_CFLAGS = $(LIBUSB_CFLAGS) $(HIDAPI_CFLAGS) -DENABLE_DEPRECATED lib_LTLIBRARIES = libdivecomputer.la -libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) -lm +libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(HIDAPI_LIBS) -lm libdivecomputer_la_LDFLAGS = \ -version-info $(DC_VERSION_LIBTOOL) \ -no-undefined \ diff --git a/src/usbhid.c b/src/usbhid.c index 1bdb1fd..f5b602d 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -25,11 +25,13 @@ #include -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) #ifdef _WIN32 #define NOGDI #endif #include +#elif defined(HAVE_HIDAPI) +#include #endif #include "usbhid.h" @@ -40,17 +42,20 @@ struct dc_usbhid_t { /* Library context. */ dc_context_t *context; /* Internal state. */ -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) libusb_context *ctx; libusb_device_handle *handle; int interface; unsigned char endpoint_in; unsigned char endpoint_out; unsigned int timeout; +#elif defined(HAVE_HIDAPI) + hid_device *handle; + int timeout; #endif }; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) static dc_status_t syserror(int errcode) { @@ -95,7 +100,7 @@ dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsi // Library context. usbhid->context = context; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) struct libusb_device **devices = NULL; struct libusb_config_descriptor *config = NULL; @@ -226,13 +231,33 @@ dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsi libusb_free_config_descriptor (config); libusb_free_device_list (devices, 1); + +#elif defined(HAVE_HIDAPI) + + // Initialize the hidapi library. + rc = hid_init(); + if (rc < 0) { + ERROR (context, "Failed to initialize usb support."); + status = DC_STATUS_IO; + goto error_free; + } + + // Open the USB device. + usbhid->handle = hid_open (vid, pid, NULL); + if (usbhid->handle == NULL) { + ERROR (context, "Failed to open the usb device."); + status = DC_STATUS_IO; + goto error_hid_exit; + } + + usbhid->timeout = -1; #endif *out = usbhid; return DC_STATUS_SUCCESS; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) error_usb_close: libusb_close (usbhid->handle); error_usb_free_config: @@ -241,6 +266,9 @@ error_usb_free_list: libusb_free_device_list (devices, 1); error_usb_exit: libusb_exit (usbhid->ctx); +#elif defined(HAVE_HIDAPI) +error_hid_exit: + hid_exit (); #endif error_free: free (usbhid); @@ -255,10 +283,13 @@ dc_usbhid_close (dc_usbhid_t *usbhid) if (usbhid == NULL) return DC_STATUS_SUCCESS; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) libusb_release_interface (usbhid->handle, usbhid->interface); libusb_close (usbhid->handle); libusb_exit (usbhid->ctx); +#elif defined(HAVE_HIDAPI) + hid_close(usbhid->handle); + hid_exit(); #endif free (usbhid); @@ -273,7 +304,7 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) INFO (usbhid->context, "Timeout: value=%i", timeout); -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) if (timeout < 0) { usbhid->timeout = 0; } else if (timeout == 0) { @@ -281,6 +312,12 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) } else { usbhid->timeout = timeout; } +#elif defined(HAVE_HIDAPI) + if (timeout < 0) { + usbhid->timeout = -1; + } else { + usbhid->timeout = timeout; + } #endif return DC_STATUS_SUCCESS; @@ -297,7 +334,7 @@ dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) goto out; } -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) 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).", @@ -305,6 +342,13 @@ dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) status = syserror (rc); goto out; } +#elif defined(HAVE_HIDAPI) + nbytes = hid_read_timeout(usbhid->handle, data, size, usbhid->timeout); + if (nbytes < 0) { + ERROR (usbhid->context, "Usb read interrupt transfer failed."); + status = DC_STATUS_IO; + goto out; + } #endif out: @@ -327,7 +371,7 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act goto out; } -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) 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).", @@ -335,6 +379,13 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act status = syserror (rc); goto out; } +#elif defined(HAVE_HIDAPI) + nbytes = hid_write(usbhid->handle, data, size); + if (nbytes < 0) { + ERROR (usbhid->context, "Usb write interrupt transfer failed."); + status = DC_STATUS_IO; + goto out; + } #endif out: From 6c6b144fe01a248c0bb848d0daa456ef06c23ba5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 30 Aug 2016 11:19:54 +0200 Subject: [PATCH 3/4] Add a dummy backend for systems without USB HID support. This dummy implemantion is used when building without libusb and hidapi. --- src/usbhid.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/usbhid.c b/src/usbhid.c index f5b602d..44c0886 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -26,11 +26,13 @@ #include #if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#define USBHID #ifdef _WIN32 #define NOGDI #endif #include #elif defined(HAVE_HIDAPI) +#define USBHID #include #endif @@ -81,6 +83,7 @@ syserror(int errcode) dc_status_t dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsigned int pid) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; dc_usbhid_t *usbhid = NULL; int rc = 0; @@ -273,11 +276,15 @@ error_hid_exit: error_free: free (usbhid); return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_close (dc_usbhid_t *usbhid) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; if (usbhid == NULL) @@ -294,11 +301,15 @@ dc_usbhid_close (dc_usbhid_t *usbhid) free (usbhid); return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) { +#ifdef USBHID if (usbhid == NULL) return DC_STATUS_INVALIDARGS; @@ -321,11 +332,15 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) #endif return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; int nbytes = 0; @@ -358,11 +373,15 @@ out: *actual = nbytes; return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *actual) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; int nbytes = 0; @@ -395,4 +414,7 @@ out: *actual = nbytes; return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } From 417ee6e6192ee3dbcc0c2d2bd899e41ca8e5d071 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 18 Jul 2016 08:50:46 +0200 Subject: [PATCH 4/4] Use the new USB HID backend for the Eon Steel. --- src/descriptor.c | 8 +++- src/suunto_eonsteel.c | 89 +++++++++++++------------------------------ 2 files changed, 33 insertions(+), 64 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 84f6028..3efb036 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -23,6 +23,12 @@ #include "config.h" #endif +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#define USBHID +#elif defined(HAVE_HIDAPI) +#define USBHID +#endif + #include #include @@ -80,7 +86,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Suunto", "Vyper Novo", DC_FAMILY_SUUNTO_D9, 0x1D}, {"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1E}, /* Suunto EON Steel */ -#ifdef HAVE_LIBUSB +#ifdef USBHID {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0}, #endif /* Uwatec Aladin */ diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index a84eaea..4f48bc8 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -19,10 +19,6 @@ * MA 02110-1301 USA */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - #include #include #include @@ -32,24 +28,15 @@ #include "context-private.h" #include "device-private.h" #include "array.h" +#include "usbhid.h" #ifdef _MSC_VER #define snprintf _snprintf #endif -#ifdef HAVE_LIBUSB - -#ifdef _WIN32 -#define NOGDI -#endif - -#include - typedef struct suunto_eonsteel_device_t { dc_device_t base; - - libusb_context *ctx; - libusb_device_handle *handle; + dc_usbhid_t *usbhid; unsigned int magic; unsigned short seq; unsigned char version[0x30]; @@ -143,13 +130,13 @@ static void put_le32(unsigned int val, unsigned char *p) static int receive_packet(suunto_eonsteel_device_t *eon, unsigned char *buffer, int size) { unsigned char buf[64]; - const int InEndpoint = 0x82; - int rc, transferred, len; + dc_status_t rc = DC_STATUS_SUCCESS; + size_t transferred = 0; + int len; - /* 5000 = 5s timeout */ - rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, PACKET_SIZE, &transferred, 5000); - if (rc) { - ERROR(eon->base.context, "read interrupt transfer failed (%s)", libusb_error_name(rc)); + rc = dc_usbhid_read(eon->usbhid, buf, PACKET_SIZE, &transferred); + if (rc != DC_STATUS_SUCCESS) { + ERROR(eon->base.context, "read interrupt transfer failed"); return -1; } if (transferred != PACKET_SIZE) { @@ -179,11 +166,11 @@ static int send_cmd(suunto_eonsteel_device_t *eon, unsigned int len, const unsigned char *buffer) { - const int OutEndpoint = 0x02; unsigned char buf[64]; - int transferred, rc; unsigned short seq = eon->seq; unsigned int magic = eon->magic; + dc_status_t rc = DC_STATUS_SUCCESS; + size_t transferred = 0; // Two-byte packet header, followed by 12 bytes of extended header if (len > sizeof(buf)-2-12) { @@ -213,8 +200,8 @@ static int send_cmd(suunto_eonsteel_device_t *eon, memcpy(buf+14, buffer, len); } - rc = libusb_interrupt_transfer(eon->handle, OutEndpoint, buf, sizeof(buf), &transferred, 5000); - if (rc < 0) { + rc = dc_usbhid_write(eon->usbhid, buf, sizeof(buf), &transferred); + if (rc != DC_STATUS_SUCCESS) { ERROR(eon->base.context, "write interrupt transfer failed"); return -1; } @@ -525,22 +512,25 @@ static int get_file_list(suunto_eonsteel_device_t *eon, struct directory_entry * static int initialize_eonsteel(suunto_eonsteel_device_t *eon) { - const int InEndpoint = 0x82; const unsigned char init[] = {0x02, 0x00, 0x2a, 0x00}; unsigned char buf[64]; struct eon_hdr hdr; + dc_usbhid_set_timeout(eon->usbhid, 10); + /* Get rid of any pending stale input first */ for (;;) { - int transferred; + size_t transferred = 0; - int rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 10); - if (rc < 0) + dc_status_t rc = dc_usbhid_read(eon->usbhid, buf, sizeof(buf), &transferred); + if (rc != DC_STATUS_SUCCESS) break; if (!transferred) break; } + dc_usbhid_set_timeout(eon->usbhid, 5000); + if (send_cmd(eon, INIT_CMD, sizeof(init), init)) { ERROR(eon->base.context, "Failed to send initialization command"); return -1; @@ -576,39 +566,24 @@ suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, const char memset (eon->version, 0, sizeof (eon->version)); memset (eon->fingerprint, 0, sizeof (eon->fingerprint)); - if (libusb_init(&eon->ctx)) { - ERROR(context, "libusb_init() failed"); - status = DC_STATUS_IO; + status = dc_usbhid_open(&eon->usbhid, context, 0x1493, 0x0030); + if (status != DC_STATUS_SUCCESS) { + ERROR(context, "unable to open device"); goto error_free; } - eon->handle = libusb_open_device_with_vid_pid(eon->ctx, 0x1493, 0x0030); - if (!eon->handle) { - ERROR(context, "unable to open device"); - status = DC_STATUS_IO; - goto error_usb_exit; - } - -#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) - libusb_set_auto_detach_kernel_driver(eon->handle, 1); -#endif - - libusb_claim_interface(eon->handle, 0); - if (initialize_eonsteel(eon) < 0) { ERROR(context, "unable to initialize device"); status = DC_STATUS_IO; - goto error_usb_close; + goto error_close; } *out = (dc_device_t *) eon; return DC_STATUS_SUCCESS; -error_usb_close: - libusb_close(eon->handle); -error_usb_exit: - libusb_exit(eon->ctx); +error_close: + dc_usbhid_close(eon->usbhid); error_free: free(eon); return status; @@ -732,19 +707,7 @@ suunto_eonsteel_device_close(dc_device_t *abstract) { suunto_eonsteel_device_t *eon = (suunto_eonsteel_device_t *) abstract; - libusb_close(eon->handle); - libusb_exit(eon->ctx); + dc_usbhid_close(eon->usbhid); return DC_STATUS_SUCCESS; } - -#else // no LIBUSB support - -dc_status_t -suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, const char *name, unsigned int model) -{ - ERROR(context, "The Suunto EON Steel backend needs libusb-1.0"); - return DC_STATUS_UNSUPPORTED; -} - -#endif