aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Touhey <thomas@touhey.fr>2019-04-30 13:50:29 +0200
committerThomas Touhey <thomas@touhey.fr>2019-04-30 13:50:29 +0200
commit4bced2ce87c9cb6cdb37bf0d39d47b35afba0741 (patch)
treea8798d99a8d9ba4b20fee5398a5b0b14be496c20
parentc5ecf356812ce0cb588a38b34591e144587cee7c (diff)
Added libusb support (untested).
-rw-r--r--include/libtio/io/usb.h32
-rw-r--r--include/libtio/native.h8
-rw-r--r--include/libtio/stream.h4
-rw-r--r--lib/init.c16
-rw-r--r--lib/internals.h12
-rw-r--r--lib/misc/alloc.c (renamed from lib/alloc.c)2
-rw-r--r--lib/misc/libusb.c40
-rw-r--r--lib/stream/builtin/libusb.c236
-rw-r--r--lib/stream/builtin/libusb.c.draft115
-rw-r--r--lib/stream/usb.c13
-rw-r--r--lib/usb/builtin/libusb.c119
-rw-r--r--lib/usb/list.c46
12 files changed, 517 insertions, 126 deletions
diff --git a/include/libtio/io/usb.h b/include/libtio/io/usb.h
index b761bd1..aed26a9 100644
--- a/include/libtio/io/usb.h
+++ b/include/libtio/io/usb.h
@@ -8,9 +8,28 @@ TIO_BEGIN_NAMESPACE
* Using such a stream, you can send USB requests and receive the answer
* (act as the master).
*
- * USB packets are described in the callbacks section. */
+ * USB packets are described in the callbacks section.
+ *
+ * Descriptors (for finding and giving information about connected USB
+ * devices) have the following structure: */
+
+TIO_STRUCT(tio_usb_device, tio_usb_device_t)
+
+struct tio_usb_device {
+ /* Localisation on the USB buses. */
+
+ int tio_usb_device_bus;
+ int tio_usb_device_address;
-/* TODO: USB descriptors. */
+ /* Data. */
+
+ int tio_usb_device_class; /* bDeviceClass */
+ int tio_usb_device_subclass; /* bDeviceSubClass */
+ int tio_usb_device_protocol; /* bDeviceProtocol */
+
+ unsigned int tio_usb_device_vendor_id; /* idVendor */
+ unsigned int tio_usb_device_product_id; /* idProduct */
+};
/* ---
* Utilities.
@@ -18,7 +37,14 @@ TIO_BEGIN_NAMESPACE
TIO_BEGIN_DECLS
-/* TODO: list USB devices. */
+/* List the available USB devices.
+ * The iterator yields strings (`char const *`) representing the path. */
+
+TIO_EXTERN(int) tio_list_usb_devices
+ OF((tio_iter_t **tio__iterp));
+
+# define tio_next_usb_device(ITER, PTRP) \
+ (tio_next((ITER), (void **)(tio_usb_device_t **)(PTRP)))
TIO_END_DECLS
TIO_END_NAMESPACE
diff --git a/include/libtio/native.h b/include/libtio/native.h
index 1f556fb..4a005ad 100644
--- a/include/libtio/native.h
+++ b/include/libtio/native.h
@@ -109,9 +109,13 @@ TIO_EXTERN(int) tio_list_windows_serial_ports
# include <libusb.h>
TIO_EXTERN(int) tio_open_libusb
- OF((tio_stream_t **streamp, int bus, int addr));
+ OF((tio_stream_t **tio__streamp, int tio__bus, int tio__addr));
TIO_EXTERN(int) tio_open_libusb_device
- OF((tio_stream_t **streamp, libusb_device_handle *handle));
+ OF((tio_stream_t **tio__streamp, libusb_device *tio__device,
+ int tio__deref));
+
+TIO_EXTERN(int) tio_list_libusb_devices
+ OF((tio_iter_t **tio__iterp));
# endif
diff --git a/include/libtio/stream.h b/include/libtio/stream.h
index 0ab20d3..e4dac10 100644
--- a/include/libtio/stream.h
+++ b/include/libtio/stream.h
@@ -80,7 +80,7 @@ typedef TIO_HOOK_TYPE(int) tio_usb_send_bulk_t
OF((void *, unsigned char const *, size_t,
unsigned int /* total timeout in ms */));
typedef TIO_HOOK_TYPE(int) tio_usb_recv_bulk_t
- OF((void *, unsigned char *, size_t,
+ OF((void *, unsigned char *, size_t *,
unsigned int /* total timeout in ms */));
/* Generic stream definitions. */
@@ -274,7 +274,7 @@ TIO_EXTERN(int) tio_usb_send_bulk
size_t tio__size, unsigned int tio__timeout));
TIO_EXTERN(int) tio_usb_recv_bulk
OF((tio_stream_t *tio__stream, unsigned char *tio__buf,
- size_t tio__size, unsigned int tio__timeout));
+ size_t *tio__sizep, unsigned int tio__timeout));
/* Make an SCSI request. */
diff --git a/lib/init.c b/lib/init.c
index 3806079..7696ac7 100644
--- a/lib/init.c
+++ b/lib/init.c
@@ -14,6 +14,7 @@ struct exit_queue_chunk {
};
TIO_LOCAL_DATA(int) tio_initialized = 0;
+TIO_LOCAL_DATA(int) tio_exited;
TIO_LOCAL_DATA(struct exit_queue_chunk) tio_exit_queue_first_node;
TIO_LOCAL_DATA(struct exit_queue_chunk *) tio_exit_queue;
@@ -23,7 +24,9 @@ TIO_EXTERN(int) tio_init(void)
{
if (tio_initialized)
return (tio_error_arg);
+
tio_initialized = 1;
+ tio_exited = 0;
/* Initialize the exit queue. */
@@ -37,6 +40,10 @@ TIO_EXTERN(int) tio_init(void)
TIO_EXTERN(void) tio_exit(void)
{
+ if (!tio_initialized || tio_exited)
+ return ;
+ tio_exited = 1;
+
/* Nothing for now. */
while (1) {
@@ -61,6 +68,15 @@ TIO_EXTERN(void) tio_exit(void)
TIO_EXTERN(int) tio_add_exit(void (*func)(void *), void *cookie)
{
+ int err;
+
+ if ((err = tio_init()))
+ return (err);
+ if (tio_exited) {
+ (*func)(cookie);
+ return (tio_ok);
+ }
+
if (tio_exit_queue->count == EX_COUNT) {
struct exit_queue_chunk *chunk;
diff --git a/lib/internals.h b/lib/internals.h
index 8cc7991..3b5ba7f 100644
--- a/lib/internals.h
+++ b/lib/internals.h
@@ -8,12 +8,24 @@
((TIO__A) > (TIO__B) ? (TIO__B) : (TIO__A))
# endif
+/* Exit function. */
+
typedef TIO_HOOK_TYPE(void) tio_exit_t
OF((void *));
TIO_EXTERN(int) tio_add_exit
OF((tio_exit_t func, void *cookie));
+/* libusb utilities. */
+
+# ifndef LIBTIO_DISABLED_LIBUSB
+# include <libusb.h>
+
+TIO_EXTERN(int) tio_get_libusb_context
+ OF((libusb_context **tio__ctx));
+
+# endif
+
/* ---
* Logging system.
* --- */
diff --git a/lib/alloc.c b/lib/misc/alloc.c
index ec8b6a4..52ce1d9 100644
--- a/lib/alloc.c
+++ b/lib/misc/alloc.c
@@ -1,4 +1,4 @@
-#include "internals.h"
+#include "../internals.h"
#include <stdlib.h>
/* `tio_alloc()`: allocate memory dynamically. */
diff --git a/lib/misc/libusb.c b/lib/misc/libusb.c
new file mode 100644
index 0000000..16a08ea
--- /dev/null
+++ b/lib/misc/libusb.c
@@ -0,0 +1,40 @@
+#include "../internals.h"
+#ifndef LIBTIO_DISABLED_LIBUSB
+# include <libusb.h>
+
+TIO_HOOK(void) free_context(void *vcontext)
+{
+ libusb_exit((libusb_context *)vcontext);
+}
+
+/* `get_context()`: get the default libusb context. */
+
+TIO_LOCAL_DATA(libusb_context *) tio_libusb_context = NULL;
+
+TIO_EXTERN(int) tio_get_libusb_context(libusb_context **context)
+{
+ libusb_context *ctx;
+ int lerr, err;
+
+ if (tio_libusb_context) {
+ *context = tio_libusb_context;
+ return (tio_ok);
+ }
+
+ if ((lerr = libusb_init(&ctx))) {
+ msg((ll_fatal, "libusb_init failed with error %s: %s.",
+ libusb_error_name(lerr), libusb_strerror(lerr)));
+ return (tio_error_unknown);
+ }
+
+ if ((err = tio_add_exit(free_context, ctx))) {
+ libusb_exit(ctx);
+ return (err);
+ }
+
+ tio_libusb_context = ctx;
+ *context = ctx;
+ return (tio_ok);
+}
+
+#endif
diff --git a/lib/stream/builtin/libusb.c b/lib/stream/builtin/libusb.c
new file mode 100644
index 0000000..d57edd1
--- /dev/null
+++ b/lib/stream/builtin/libusb.c
@@ -0,0 +1,236 @@
+#include "../internals.h"
+#ifndef LIBTIO_DISABLED_LIBUSB
+# include <libusb.h>
+
+# define BULK_ENDPOINT_IN \
+ (LIBUSB_ENDPOINT_IN | LIBUSB_TRANSFER_TYPE_BULK)
+# define BULK_ENDPOINT_OUT \
+ (LIBUSB_ENDPOINT_OUT | LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+
+/* `find_device()`: find a descriptor out of bus/host number. */
+
+TIO_LOCAL(int) find_device(int bus, int addr, libusb_device **devicep)
+{
+ libusb_context *ctx = NULL;
+ libusb_device **device_list;
+ int device_count, err, id;
+
+ if ((err = tio_get_libusb_context(&ctx)))
+ return (err);
+
+ /* Get the device list. */
+
+ device_count = libusb_get_device_list(ctx, &device_list);
+ if (device_count < 0) {
+ msg((ll_fatal, "libusb_get_device_list returned error %s: %s.",
+ libusb_error_name(device_count), libusb_strerror(device_count)));
+ err = tio_error_unknown;
+ goto fail;
+ }
+
+ /* Look for the device. */
+
+ for (id = 0; id < device_count; id++) {
+ /* Check the position on the bus. */
+
+ if (bus >= 0) {
+ if (libusb_get_bus_number(device_list[id]) != bus)
+ continue;
+
+ if (addr >= 0
+ && libusb_get_device_address(device_list[id]) != addr)
+ continue;
+ }
+
+ *devicep = device_list[id];
+ libusb_ref_device(*devicep);
+ goto fail;
+ }
+
+ /* If we're here, no device has been found. */
+
+ err = tio_error_notfound;
+fail:
+ libusb_free_device_list(device_list, 1);
+ return (err);
+}
+
+/* `claim_interface()`: claim an interface using a libusb device handle. */
+
+TIO_LOCAL(int) claim_interface(libusb_device_handle *handle, int endpoint)
+{
+ int uerr;
+
+ /* Disconnect any kernel driver. */
+
+ switch ((uerr = libusb_detach_kernel_driver(handle, endpoint))) {
+ case 0:
+ if ((uerr = libusb_kernel_driver_active(handle, endpoint))) {
+ msg((ll_error, "libusb_kernel_driver_active: interface %d is "
+ "still active…", endpoint));
+ return (tio_error_unknown);
+ }
+
+ break;
+
+ case LIBUSB_ERROR_NOT_SUPPORTED:
+ case LIBUSB_ERROR_NOT_FOUND:
+ /* Not on a platform on which this is supported.
+ * This is fine. */
+
+ break;
+
+ default:
+ msg((ll_error, "libusb_detach_kernel_driver returned error %s for "
+ "interface %d: %s", libusb_error_name(uerr), endpoint,
+ libusb_strerror(uerr)));
+ return (tio_error_unknown);
+ }
+
+ /* Claim the interface. */
+
+ switch ((uerr = libusb_claim_interface(handle, endpoint))) {
+ case 0:
+ break;
+
+ default:
+ msg((ll_error, "libusb_claim_interface returned error %s for "
+ "interface %d: %s", libusb_error_name(uerr), endpoint,
+ libusb_strerror(uerr)));
+ return (tio_error_unknown);
+ }
+
+ return (tio_ok);
+}
+
+/* ---
+ * Cookie and callbacks.
+ * --- */
+
+/* The cookie structure.
+ * Contains the libusb_device cookie and whether to close it or not. */
+
+TIO_STRUCT(cookie, cookie_t)
+
+struct cookie {
+ libusb_device_handle *handle;
+
+ libusb_device *device;
+ int deref;
+};
+
+/* `send_bulk()`: send bulk data. */
+
+TIO_HOOK(int) send_bulk(cookie_t *cookie, unsigned char const *data,
+ size_t size, unsigned int timeout /* in ms */)
+{
+ /* TODO */
+
+ return (tio_error_op);
+}
+
+/* `recv_bulk()`: receive bulk data. */
+
+TIO_HOOK(int) recv_bulk(cookie_t *cookie, unsigned char *buf,
+ size_t *sizep, unsigned int timeout /* in ms */)
+{
+ /* TODO */
+
+ return (tio_error_op);
+}
+
+/* `close_stream()`: close the stream. */
+
+TIO_HOOK(void) close_stream(cookie_t *cookie)
+{
+ libusb_close(cookie->handle);
+ if (cookie->deref)
+ libusb_unref_device(cookie->device);
+
+ tio_free(cookie);
+}
+
+/* `usb_functions`: functions for a libusb device to be understood
+ * by libtio. */
+
+TIO_LOCAL_DATA(tio_usb_functions_t const) usb_functions = {
+ (tio_close_t *)&close_stream,
+
+ (tio_usb_send_bulk_t *)&send_bulk,
+ (tio_usb_recv_bulk_t *)&recv_bulk
+};
+
+/* ---
+ * Open the stream.
+ * --- */
+
+/* `tio_open_libusb_device()`: find a USB device. */
+
+TIO_EXTERN(int) tio_open_libusb_device(tio_stream_t **streamp,
+ libusb_device *device, int deref)
+{
+ int err = tio_ok, uerr;
+ libusb_device_handle *handle = NULL;
+ cookie_t *cookie;
+
+ /* Open the calculator handle. */
+
+ switch ((uerr = libusb_open(device, &handle))) {
+ case 0:
+ break;
+
+ case LIBUSB_ERROR_ACCESS:
+ err = tio_error_access;
+ goto fail;
+
+ default:
+ msg((ll_error, "libusb_open returned error %s: %s",
+ libusb_error_name(uerr), libusb_strerror(uerr)));
+ err = tio_error_unknown;
+ goto fail;
+ }
+
+ /* Claim the interfaces we ought to use. */
+
+ if ((err = claim_interface(handle, BULK_ENDPOINT_IN))
+ || (err = claim_interface(handle, BULK_ENDPOINT_OUT)))
+ goto fail;
+
+ /* Allocate the cookie. */
+
+ if (!(cookie = tio_alloc(1, sizeof(*cookie)))) {
+ err = tio_error_alloc;
+ goto fail;
+ }
+
+ cookie->device = device;
+ cookie->handle = handle;
+ cookie->deref = deref;
+
+ /* Prepare the stream. */
+
+ return (tio_open_usb(streamp, NULL, cookie,
+ TIO_OPENFLAG_USB_SEND_BULK | TIO_OPENFLAG_USB_RECV_BULK,
+ &usb_functions));
+fail:
+ if (handle)
+ libusb_close(handle);
+ if (deref)
+ libusb_unref_device(device);
+
+ return (err);
+}
+
+/* `tio_open_libusb()`: open a USB device. */
+
+TIO_EXTERN(int) tio_open_libusb(tio_stream_t **streamp, int bus, int addr)
+{
+ int err = 0;
+ libusb_device *device = NULL;
+
+ if ((err = find_device(bus, addr, &device)))
+ return (err);
+ return (tio_open_libusb_device(streamp, device, 1));
+}
+
+#endif
diff --git a/lib/stream/builtin/libusb.c.draft b/lib/stream/builtin/libusb.c.draft
deleted file mode 100644
index 122859d..0000000
--- a/lib/stream/builtin/libusb.c.draft
+++ /dev/null
@@ -1,115 +0,0 @@
-#include "../internals.h"
-#ifndef LIBTIO_DISABLED_LIBUSB
-# include <libusb.h>
-
-/* `free_context()`: free the libusb context passed as a `void *`. */
-
-TIO_HOOK(void) free_context(void *vcontext)
-{
- libusb_exit((libusb_context *)context);
-}
-
-/* `get_context()`: get the default libusb context. */
-
-TIO_LOCAL libusb_context *tio_libusb_context = NULL;
-
-TIO_LOCAL int get_context(libusb_context **context)
-{
- libusb_context *ctx;
- int lerr, err;
-
- if (tio_libusb_context) {
- *context = tio_libusb_context;
- return (tio_ok);
- }
-
- if ((lerr = libusb_init(&ctx)))
- return (tio_error_unknown);
- if ((err = tio_add_exit(free_context, ctx))) {
- libusb_exit(&ctx);
- return (err);
- }
-
- tio_libusb_context = ctx;
- *context = ctx;
- return (tio_ok);
-}
-
-/* `find_descriptor()`: find a descriptor out of bus/host number. */
-
-TIO_LOCAL int find_descriptor(int bus, int addr,
- libusb_device *device, struct libusb_device_descriptor *descp)
-{
- libusb_context *ctx = NULL;
- int device_count, err;
-
- /* Open up the context. */
-
- if ((err = get_context(&ctx)
-
- if (libusb_init(&context)) {
- msg((ll_fatal, "Couldn't create a libusb context."));
- err = tio_error_alloc;
- goto fail;
- }
-
- /* Get the device list. */
-
- device_count = libusb_get_device_list(context, &device_list);
- if (device_count < 0) {
- msg((ll_fatal, "couldn't get device list."));
- err = tio_error_nodevice; /* FIXME */
- goto fail;
- }
-
- /* Look for the device. */
-
- for (id = 0; id < device_count; id++) {
- /* Check the position on the bus. */
-
- if (bus >= 0) {
- if (libusb_get_bus_number(device_list[id]) != bus)
- continue;
-
- if (addr >= 0
- && libusb_get_device_address(device_list[id]) != addr)
- continue;
- }
-
- /* Get the device descriptor. */
-
- if (libusb_get_device_descriptor(device_list[id], descp))
- continue ;
-
- device = device_list[id];
- break;
- }
-
- err = tio_no_error;
-fail:
- libusb_free_device_list(device_list, 1);
- return (err);
-}
-
-/* `tio_open_libusb()`: open a USB device. */
-
-int TIO_EXPORT tio_open_libusb(tio_stream_t **streamp, int bus, int addr)
-{
- int err = 0;
- libusb_context *context = NULL;
- libusb_device *device = NULL;
-
- /* Get the device list. */
-
- find_descriptor(bus, addr, &device, &descp
-}
-
-/* `tio_open_libusb_device()`: find a USB device. */
-
-int TIO_EXPORT tio_open_libusb_device(tio_stream_t **streamp,
- libusb_device *device)
-{
- int err = 0;
-}
-
-#endif
diff --git a/lib/stream/usb.c b/lib/stream/usb.c
index 84238ad..9179519 100644
--- a/lib/stream/usb.c
+++ b/lib/stream/usb.c
@@ -23,6 +23,8 @@ TIO_EXTERN(int) tio_usb_send_bulk(tio_stream_t *stream,
/* Execute the request. */
+ if (!size)
+ return (tio_ok);
if ((err = (*func)(stream->tio_stream_cookie, data, size, timeout)))
goto fail;
@@ -35,7 +37,7 @@ fail:
/* `tio_usb_recv_bulk()`: receive an USB bulk packet. */
TIO_EXTERN(int) tio_usb_recv_bulk(tio_stream_t *stream, unsigned char *buf,
- size_t size, unsigned int timeout /* in ms */)
+ size_t *sizep, unsigned int timeout /* in ms */)
{
int err;
tio_usb_recv_bulk_t *func;
@@ -47,14 +49,19 @@ TIO_EXTERN(int) tio_usb_recv_bulk(tio_stream_t *stream, unsigned char *buf,
if (!(func = stream->tio_stream_usb_recv_bulk)) {
err = stream->tio_stream_parent
- ? tio_usb_recv_bulk(stream->tio_stream_parent, buf, size, timeout)
+ ? tio_usb_recv_bulk(stream->tio_stream_parent, buf, sizep, timeout)
: tio_error_op;
goto fail;
}
/* Execute the request. */
- if ((err = (*func)(stream->tio_stream_cookie, buf, size, timeout)))
+ if (!sizep)
+ return (tio_error_arg);
+ if (!*sizep)
+ return (tio_ok);
+
+ if ((err = (*func)(stream->tio_stream_cookie, buf, sizep, timeout)))
goto fail;
err = tio_ok;
diff --git a/lib/usb/builtin/libusb.c b/lib/usb/builtin/libusb.c
new file mode 100644
index 0000000..c91cb20
--- /dev/null
+++ b/lib/usb/builtin/libusb.c
@@ -0,0 +1,119 @@
+#include "../../internals.h"
+#ifndef LIBTIO_DISABLED_LIBUSB
+# include <libusb.h>
+
+/* ---
+ * Cookie and callbacks.
+ * --- */
+
+/* The iterator cookie.
+ * Contains the device list. */
+
+TIO_STRUCT(cookie, cookie_t)
+
+struct cookie {
+ libusb_device **list;
+ int count, last_id;
+
+ tio_usb_device_t device_info;
+};
+
+/* `next_device()`: get the next device data. */
+
+TIO_HOOK(int) next_device(cookie_t *cookie, tio_usb_device_t **infop)
+{
+ tio_usb_device_t *info = &cookie->device_info;
+
+ while (++cookie->last_id < cookie->count) {
+ int id = cookie->last_id;
+ libusb_device *device = cookie->list[id];
+ struct libusb_device_descriptor desc;
+ int bus, addr, err;
+
+ if ((err = (bus = libusb_get_bus_number(device))) < 0
+ || (err = (addr = libusb_get_device_address(device))) < 0
+ || (err = libusb_get_device_descriptor(device, &desc)) < 0) {
+ msg((ll_error, "getting device #%d info returned error %s: %s",
+ id, libusb_error_name(err), libusb_strerror(err)));
+ continue ;
+ }
+
+ /* Initialize the information structure with the data
+ * the libusb calls supplied. */
+
+ info->tio_usb_device_bus = bus;
+ info->tio_usb_device_address = addr;
+
+ info->tio_usb_device_class = desc.bDeviceClass;
+ info->tio_usb_device_subclass = desc.bDeviceSubClass;
+ info->tio_usb_device_protocol = desc.bDeviceProtocol;
+
+ info->tio_usb_device_vendor_id = desc.idVendor;
+ info->tio_usb_device_product_id = desc.idProduct;
+
+ *infop = info;
+ return (tio_ok);
+ }
+
+ return (tio_error_iter);
+}
+
+/* `end_iter()`: end the iterator. */
+
+TIO_HOOK(void) end_iter(cookie_t *cookie)
+{
+ libusb_free_device_list(cookie->list, 1);
+ tio_free(cookie);
+}
+
+TIO_LOCAL_DATA(tio_iter_functions_t const) libusb_dl_functions = {
+ (tio_next_t *)&next_device,
+ NULL,
+ (tio_end_t *)&end_iter
+};
+
+/* ---
+ * Open the iterator.
+ * --- */
+
+/* `tio_list_libusb_devices()`: list USB devices using libusb. */
+
+TIO_EXTERN(int) tio_list_libusb_devices(tio_iter_t **iterp)
+{
+ cookie_t *cookie;
+ libusb_context *ctx;
+ libusb_device **device_list;
+ int device_count;
+ int err;
+
+ /* Open up the context. */
+
+ if ((err = tio_get_libusb_context(&ctx)))
+ return (err);
+
+ /* Get the device list. */
+
+ device_count = libusb_get_device_list(ctx, &device_list);
+ if (device_count < 0) {
+ msg((ll_fatal, "libusb_get_device_list returned %s: %s.",
+ libusb_error_name(device_count), libusb_strerror(device_count)));
+ return (tio_error_unknown);
+ }
+
+ /* Allocate the cookie. */
+
+ if (!(cookie = tio_alloc(1, sizeof(*cookie)))) {
+ libusb_free_device_list(device_list, 1);
+ return (tio_error_alloc);
+ }
+
+ cookie->list = device_list;
+ cookie->count = device_count;
+ cookie->last_id = -1;
+
+ /* Create the iterator. */
+
+ return (tio_iter(iterp, cookie, &libusb_dl_functions));
+}
+
+#endif
diff --git a/lib/usb/list.c b/lib/usb/list.c
new file mode 100644
index 0000000..c2b1b4a
--- /dev/null
+++ b/lib/usb/list.c
@@ -0,0 +1,46 @@
+#include "../internals.h"
+
+/* `udl_candidates`: candidates to USB devices listing.
+ * `udl_last_successful_candidate`: last candidate which returned
+ * a valid iterator. */
+
+typedef TIO_EXTERN_TYPE(int) udl_func
+ OF((tio_iter_t **));
+
+TIO_LOCAL_DATA(udl_func *) udl_candidates[] = {
+#ifndef LIBTIO_DISABLED_LIBUSB
+ &tio_list_libusb_devices,
+#endif
+
+ NULL
+};
+
+TIO_LOCAL_DATA(udl_func *) udl_last_successful_candidate = 0;
+
+/* `tio_list_serial_ports()`: list the serial ports, in an agnostic way. */
+
+TIO_EXTERN(int) tio_list_usb_devices(tio_iter_t **iterp)
+{
+ int err;
+
+ if (udl_last_successful_candidate) {
+ err = (*udl_last_successful_candidate)(iterp);
+ if (err != tio_error_op)
+ return (err);
+ }
+
+ {
+ udl_func **func;
+
+ for (func = udl_candidates; func; func++) {
+ err = (**func)(iterp);
+ if (err == tio_error_op)
+ continue ;
+
+ udl_last_successful_candidate = *func;
+ return (err);
+ }
+ }
+
+ return (tio_error_op);
+}