Add an I/O implementation for USB communication

The USB communication is now also implemented as an I/O stream
transport. Unlike most I/O devices, USB communication supports multiple
interfaces and endpoints. This requires some some special care:

In the general case, autodetection isn't really possible without
additional knowledge. Hence the need for the filter parameters to pass
this kind of information.

The implementation assumes two bulk endpoints for the standard
read/write interface. Communication with the control endpoint is
supported through the new DC_IOCTL_USB_CONTROL_{READ,WRITE} ioctl's.
This commit is contained in:
Jef Driesen 2020-04-30 19:10:34 +02:00
parent 57f0ce6d79
commit c84bbd93a3
7 changed files with 752 additions and 0 deletions

View File

@ -12,6 +12,7 @@ libdivecomputer_HEADERS = \
bluetooth.h \
ble.h \
irda.h \
usb.h \
usbhid.h \
custom.h \
device.h \

View File

@ -0,0 +1,145 @@
/*
* libdivecomputer
*
* Copyright (C) 2020 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_USB_H
#define DC_USB_H
#include "common.h"
#include "context.h"
#include "iostream.h"
#include "iterator.h"
#include "descriptor.h"
#include "ioctl.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* Perform a USB control transfer.
*
* The parameters for the control transfer are specified in the
* #dc_usb_control_t data structure. If the control transfer requires
* additional data as in- or output, the buffer must be located
* immediately after the #dc_usb_control_t data structure, and the
* length of the buffer must be indicated in the #wLength field. The
* size of the ioctl request is the total size, including the size of
* the #dc_usb_control_t structure.
*/
#define DC_IOCTL_USB_CONTROL_READ DC_IOCTL_IOR('u', 0, DC_IOCTL_SIZE_VARIABLE)
#define DC_IOCTL_USB_CONTROL_WRITE DC_IOCTL_IOW('u', 0, DC_IOCTL_SIZE_VARIABLE)
/**
* USB control transfer.
*/
typedef struct dc_usb_control_t {
unsigned char bmRequestType;
unsigned char bRequest;
unsigned short wValue;
unsigned short wIndex;
unsigned short wLength;
} dc_usb_control_t;
/**
* Endpoint direction bits of the USB control transfer.
*/
typedef enum dc_usb_endpoint_t {
DC_USB_ENDPOINT_OUT = 0x00,
DC_USB_ENDPOINT_IN = 0x80
} dc_usb_endpoint_t;
/**
* Request type bits of the USB control transfer.
*/
typedef enum dc_usb_request_t {
DC_USB_REQUEST_STANDARD = 0x00,
DC_USB_REQUEST_CLASS = 0x20,
DC_USB_REQUEST_VENDOR = 0x40,
DC_USB_REQUEST_RESERVED = 0x60
} dc_usb_request_t;
/**
* Recipient bits of the USB control transfer.
*/
typedef enum dc_usb_recipient_t {
DC_USB_RECIPIENT_DEVICE = 0x00,
DC_USB_RECIPIENT_INTERFACE = 0x01,
DC_USB_RECIPIENT_ENDPOINT = 0x02,
DC_USB_RECIPIENT_OTHER = 0x03,
} dc_usb_recipient_t;
/**
* Opaque object representing a USB device.
*/
typedef struct dc_usb_device_t dc_usb_device_t;
/**
* Get the vendor id (VID) of the USB device.
*
* @param[in] device A valid USB device.
*/
unsigned int
dc_usb_device_get_vid (dc_usb_device_t *device);
/**
* Get the product id (PID) of the USB device.
*
* @param[in] device A valid USB device.
*/
unsigned int
dc_usb_device_get_pid (dc_usb_device_t *device);
/**
* Destroy the USB device and free all resources.
*
* @param[in] device A valid USB device.
*/
void
dc_usb_device_free(dc_usb_device_t *device);
/**
* Create an iterator to enumerate the USB devices.
*
* @param[out] iterator A location to store the iterator.
* @param[in] context A valid context object.
* @param[in] descriptor A valid device descriptor or NULL.
* @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code
* on failure.
*/
dc_status_t
dc_usb_iterator_new (dc_iterator_t **iterator, dc_context_t *context, dc_descriptor_t *descriptor);
/**
* Open a USB connection.
*
* @param[out] iostream A location to store the USB connection.
* @param[in] context A valid context object.
* @param[in] device A valid USB device.
* @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code
* on failure.
*/
dc_status_t
dc_usb_open (dc_iostream_t **iostream, dc_context_t *context, dc_usb_device_t *device);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* DC_USB_H */

View File

@ -518,6 +518,10 @@
RelativePath="..\src\timer.c"
>
</File>
<File
RelativePath="..\src\usb.c"
>
</File>
<File
RelativePath="..\src\usbhid.c"
>
@ -872,6 +876,10 @@
RelativePath="..\include\libdivecomputer\units.h"
>
</File>
<File
RelativePath="..\include\libdivecomputer\usb.h"
>
</File>
<File
RelativePath="..\include\libdivecomputer\usbhid.h"
>

View File

@ -76,6 +76,7 @@ libdivecomputer_la_SOURCES = \
liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \
socket.h socket.c \
irda.c \
usb.c \
usbhid.c \
bluetooth.c \
custom.c

View File

@ -33,6 +33,12 @@ typedef struct dc_usb_desc_t {
unsigned short pid;
} dc_usb_desc_t;
typedef struct dc_usb_params_t {
unsigned int interface;
unsigned char endpoint_in;
unsigned char endpoint_out;
} dc_usb_params_t;
int
dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params);

View File

@ -70,6 +70,12 @@ dc_irda_device_free
dc_irda_iterator_new
dc_irda_open
dc_usb_device_get_vid
dc_usb_device_get_pid
dc_usb_device_free
dc_usb_iterator_new
dc_usb_open
dc_usbhid_device_get_vid
dc_usbhid_device_get_pid
dc_usbhid_device_free

585
src/usb.c Normal file
View File

@ -0,0 +1,585 @@
/*
* libdivecomputer
*
* Copyright (C) 2020 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>
#include <string.h>
#ifdef HAVE_LIBUSB
#ifdef _WIN32
#define NOGDI
#endif
#include <libusb.h>
#endif
#include <libdivecomputer/usb.h>
#include "common-private.h"
#include "context-private.h"
#include "iostream-private.h"
#include "descriptor-private.h"
#include "iterator-private.h"
#include "platform.h"
#define ISINSTANCE(device) dc_iostream_isinstance((device), &dc_usb_vtable)
typedef struct dc_usb_session_t {
size_t refcount;
#ifdef HAVE_LIBUSB
libusb_context *handle;
#endif
} dc_usb_session_t;
struct dc_usb_device_t {
unsigned short vid, pid;
dc_usb_session_t *session;
#ifdef HAVE_LIBUSB
struct libusb_device *handle;
int interface;
unsigned char endpoint_in;
unsigned char endpoint_out;
#endif
};
#ifdef HAVE_LIBUSB
static dc_status_t dc_usb_iterator_next (dc_iterator_t *iterator, void *item);
static dc_status_t dc_usb_iterator_free (dc_iterator_t *iterator);
static dc_status_t dc_usb_set_timeout (dc_iostream_t *iostream, int timeout);
static dc_status_t dc_usb_poll (dc_iostream_t *iostream, int timeout);
static dc_status_t dc_usb_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual);
static dc_status_t dc_usb_write (dc_iostream_t *iostream, const void *data, size_t size, size_t *actual);
static dc_status_t dc_usb_ioctl (dc_iostream_t *iostream, unsigned int request, void *data, size_t size);
static dc_status_t dc_usb_close (dc_iostream_t *iostream);
typedef struct dc_usb_iterator_t {
dc_iterator_t base;
dc_descriptor_t *descriptor;
dc_usb_session_t *session;
struct libusb_device **devices;
size_t count;
size_t current;
} dc_usb_iterator_t;
typedef struct dc_usb_t {
/* Base class. */
dc_iostream_t base;
/* Internal state. */
dc_usb_session_t *session;
libusb_device_handle *handle;
int interface;
unsigned char endpoint_in;
unsigned char endpoint_out;
unsigned int timeout;
} dc_usb_t;
static const dc_iterator_vtable_t dc_usb_iterator_vtable = {
sizeof(dc_usb_iterator_t),
dc_usb_iterator_next,
dc_usb_iterator_free,
};
static const dc_iostream_vtable_t dc_usb_vtable = {
sizeof(dc_usb_t),
dc_usb_set_timeout, /* set_timeout */
NULL, /* set_break */
NULL, /* set_dtr */
NULL, /* set_rts */
NULL, /* get_lines */
NULL, /* get_available */
NULL, /* configure */
dc_usb_poll, /* poll */
dc_usb_read, /* read */
dc_usb_write, /* write */
dc_usb_ioctl, /* ioctl */
NULL, /* flush */
NULL, /* purge */
NULL, /* sleep */
dc_usb_close, /* close */
};
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;
}
}
static dc_status_t
dc_usb_session_new (dc_usb_session_t **out, dc_context_t *context)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_usb_session_t *session = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
session = (dc_usb_session_t *) malloc (sizeof(dc_usb_session_t));
if (session == NULL) {
ERROR (context, "Failed to allocate memory.");
status = DC_STATUS_NOMEMORY;
goto error_unlock;
}
session->refcount = 1;
int rc = libusb_init (&session->handle);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to initialize usb support (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_free;
}
*out = session;
return status;
error_free:
free (session);
error_unlock:
return status;
}
static dc_usb_session_t *
dc_usb_session_ref (dc_usb_session_t *session)
{
if (session == NULL)
return NULL;
session->refcount++;
return session;
}
static dc_status_t
dc_usb_session_unref (dc_usb_session_t *session)
{
if (session == NULL)
return DC_STATUS_SUCCESS;
if (--session->refcount == 0) {
libusb_exit (session->handle);
free (session);
}
return DC_STATUS_SUCCESS;
}
#endif
unsigned int
dc_usb_device_get_vid (dc_usb_device_t *device)
{
if (device == NULL)
return 0;
return device->vid;
}
unsigned int
dc_usb_device_get_pid (dc_usb_device_t *device)
{
if (device == NULL)
return 0;
return device->pid;
}
void
dc_usb_device_free(dc_usb_device_t *device)
{
if (device == NULL)
return;
#ifdef HAVE_LIBUSB
libusb_unref_device (device->handle);
dc_usb_session_unref (device->session);
#endif
free (device);
}
dc_status_t
dc_usb_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_t *descriptor)
{
#ifdef HAVE_LIBUSB
dc_status_t status = DC_STATUS_SUCCESS;
dc_usb_iterator_t *iterator = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
iterator = (dc_usb_iterator_t *) dc_iterator_allocate (context, &dc_usb_iterator_vtable);
if (iterator == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Initialize the usb library.
status = dc_usb_session_new (&iterator->session, context);
if (status != DC_STATUS_SUCCESS) {
goto error_free;
}
// Enumerate the USB devices.
struct libusb_device **devices = NULL;
ssize_t ndevices = libusb_get_device_list (iterator->session->handle, &devices);
if (ndevices < 0) {
ERROR (context, "Failed to enumerate the usb devices (%s).",
libusb_error_name (ndevices));
status = syserror (ndevices);
goto error_session_unref;
}
iterator->devices = devices;
iterator->count = ndevices;
iterator->current = 0;
iterator->descriptor = descriptor;
*out = (dc_iterator_t *) iterator;
return DC_STATUS_SUCCESS;
error_session_unref:
dc_usb_session_unref (iterator->session);
error_free:
dc_iterator_deallocate ((dc_iterator_t *) iterator);
return status;
#else
return DC_STATUS_UNSUPPORTED;
#endif
}
#ifdef HAVE_LIBUSB
static dc_status_t
dc_usb_iterator_next (dc_iterator_t *abstract, void *out)
{
dc_usb_iterator_t *iterator = (dc_usb_iterator_t *) abstract;
dc_usb_device_t *device = NULL;
while (iterator->current < iterator->count) {
struct libusb_device *current = iterator->devices[iterator->current++];
// Get the device descriptor.
struct libusb_device_descriptor dev;
int rc = libusb_get_device_descriptor (current, &dev);
if (rc < 0) {
ERROR (abstract->context, "Failed to get the device descriptor (%s).",
libusb_error_name (rc));
return syserror (rc);
}
dc_usb_desc_t usb = {dev.idVendor, dev.idProduct};
dc_usb_params_t params = {0, 0, 0};
if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USB, &usb, &params)) {
continue;
}
// Get the active configuration descriptor.
struct libusb_config_descriptor *config = NULL;
rc = libusb_get_active_config_descriptor (current, &config);
if (rc != LIBUSB_SUCCESS) {
ERROR (abstract->context, "Failed to get the configuration descriptor (%s).",
libusb_error_name (rc));
return syserror (rc);
}
// Find the first matching 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 (int j = 0; j < iface->num_altsetting; j++) {
const struct libusb_interface_descriptor *desc = &iface->altsetting[j];
if (interface == NULL && desc->bInterfaceNumber == params.interface) {
interface = desc;
}
}
}
if (interface == NULL) {
libusb_free_config_descriptor (config);
continue;
}
// Find the first matching input and output bulk 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_BULK) {
continue;
}
if (ep_in == NULL && direction == LIBUSB_ENDPOINT_IN &&
(params.endpoint_in == 0 || params.endpoint_in == desc->bEndpointAddress)) {
ep_in = desc;
}
if (ep_out == NULL && direction == LIBUSB_ENDPOINT_OUT &&
(params.endpoint_out == 0 || params.endpoint_out == desc->bEndpointAddress)) {
ep_out = desc;
}
}
if (ep_in == NULL || ep_out == NULL) {
libusb_free_config_descriptor (config);
continue;
}
device = (dc_usb_device_t *) malloc (sizeof(dc_usb_device_t));
if (device == NULL) {
ERROR (abstract->context, "Failed to allocate memory.");
libusb_free_config_descriptor (config);
return DC_STATUS_NOMEMORY;
}
device->session = dc_usb_session_ref (iterator->session);
device->vid = dev.idVendor;
device->pid = dev.idProduct;
device->handle = libusb_ref_device (current);
device->interface = interface->bInterfaceNumber;
device->endpoint_in = ep_in->bEndpointAddress;
device->endpoint_out = ep_out->bEndpointAddress;
*(dc_usb_device_t **) out = device;
libusb_free_config_descriptor (config);
return DC_STATUS_SUCCESS;
}
return DC_STATUS_DONE;
}
static dc_status_t
dc_usb_iterator_free (dc_iterator_t *abstract)
{
dc_usb_iterator_t *iterator = (dc_usb_iterator_t *) abstract;
libusb_free_device_list (iterator->devices, 1);
dc_usb_session_unref (iterator->session);
return DC_STATUS_SUCCESS;
}
#endif
dc_status_t
dc_usb_open (dc_iostream_t **out, dc_context_t *context, dc_usb_device_t *device)
{
#ifdef HAVE_LIBUSB
dc_status_t status = DC_STATUS_SUCCESS;
dc_usb_t *usb = NULL;
if (out == NULL || device == NULL)
return DC_STATUS_INVALIDARGS;
INFO (context, "Open: vid=%04x, pid=%04x, interface=%u, endpoints=%02x,%02x",
device->vid, device->pid, device->interface, device->endpoint_in, device->endpoint_out);
// Allocate memory.
usb = (dc_usb_t *) dc_iostream_allocate (context, &dc_usb_vtable, DC_TRANSPORT_USB);
if (usb == NULL) {
ERROR (context, "Out of memory.");
return DC_STATUS_NOMEMORY;
}
// Initialize the usb library.
usb->session = dc_usb_session_ref (device->session);
if (usb->session == NULL) {
goto error_free;
}
// Open the USB device.
int rc = libusb_open (device->handle, &usb->handle);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to open the usb device (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_session_unref;
}
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
libusb_set_auto_detach_kernel_driver (usb->handle, 1);
#endif
// Claim the interface.
rc = libusb_claim_interface (usb->handle, device->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;
}
usb->interface = device->interface;
usb->endpoint_in = device->endpoint_in;
usb->endpoint_out = device->endpoint_out;
usb->timeout = 0;
*out = (dc_iostream_t *) usb;
return DC_STATUS_SUCCESS;
error_usb_close:
libusb_close (usb->handle);
error_session_unref:
dc_usb_session_unref (usb->session);
error_free:
dc_iostream_deallocate ((dc_iostream_t *) usb);
return status;
#else
return DC_STATUS_UNSUPPORTED;
#endif
}
#ifdef HAVE_LIBUSB
static dc_status_t
dc_usb_close (dc_iostream_t *abstract)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_usb_t *usb = (dc_usb_t *) abstract;
libusb_release_interface (usb->handle, usb->interface);
libusb_close (usb->handle);
dc_usb_session_unref (usb->session);
return status;
}
static dc_status_t
dc_usb_set_timeout (dc_iostream_t *abstract, int timeout)
{
dc_usb_t *usb = (dc_usb_t *) abstract;
if (timeout < 0) {
usb->timeout = 0;
} else if (timeout == 0) {
return DC_STATUS_UNSUPPORTED;
} else {
usb->timeout = timeout;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_usb_poll (dc_iostream_t *abstract, int timeout)
{
return DC_STATUS_UNSUPPORTED;
}
static dc_status_t
dc_usb_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_usb_t *usb = (dc_usb_t *) abstract;
int nbytes = 0;
int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_in, data, size, &nbytes, usb->timeout);
if (rc != LIBUSB_SUCCESS) {
ERROR (abstract->context, "Usb read bulk transfer failed (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto out;
}
out:
if (actual)
*actual = nbytes;
return status;
}
static dc_status_t
dc_usb_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_usb_t *usb = (dc_usb_t *) abstract;
int nbytes = 0;
int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_out, (void *) data, size, &nbytes, 0);
if (rc != LIBUSB_SUCCESS) {
ERROR (abstract->context, "Usb write bulk transfer failed (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto out;
}
out:
if (actual)
*actual = nbytes;
return status;
}
static dc_status_t
dc_usb_ioctl_control (dc_iostream_t *abstract, void *data, size_t size)
{
dc_usb_t *usb = (dc_usb_t *) abstract;
const dc_usb_control_t *control = (const dc_usb_control_t *) data;
if (size < sizeof(control) || control->wLength > size - sizeof(control)) {
return DC_STATUS_INVALIDARGS;
}
int rc = libusb_control_transfer (usb->handle,
control->bmRequestType, control->bRequest, control->wValue, control->wIndex,
(unsigned char *) data + sizeof(control), control->wLength, usb->timeout);
if (rc != LIBUSB_SUCCESS) {
ERROR (abstract->context, "Usb control transfer failed (%s).",
libusb_error_name (rc));
return syserror (rc);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_usb_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size_t size)
{
switch (request) {
case DC_IOCTL_USB_CONTROL_READ:
case DC_IOCTL_USB_CONTROL_WRITE:
return dc_usb_ioctl_control (abstract, data, size);
default:
return DC_STATUS_UNSUPPORTED;
}
}
#endif