for 1.0
=======
cancellation race concerns - better tracking of kernel feedback?
-isochronous endpoint I/O
thread safety
error codes
fixme review
make libusb_get_pollfds return const?
doxygen warnings
make descriptor things const?
+isochronous sync I/O?
1.0 API style/naming points to reconsider
=========================================
if (!buf)
return -ENOMEM;
- transfer = libusb_alloc_transfer();
+ transfer = libusb_alloc_transfer(0);
if (!transfer) {
free(buf);
return -ENOMEM;
static int alloc_transfers(void)
{
- img_transfer = libusb_alloc_transfer();
+ img_transfer = libusb_alloc_transfer(0);
if (!img_transfer)
return -ENOMEM;
- irq_transfer = libusb_alloc_transfer();
+ irq_transfer = libusb_alloc_transfer(0);
if (!irq_transfer)
return -ENOMEM;
#include "libusbi.h"
-#define TRANSFER_TO_PRIV(trf) (container_of((trf), struct usbi_transfer, pub))
-
/* this is a list of in-flight rb_handles, sorted by timeout expiration.
* URBs to timeout the soonest are placed at the beginning of the list, URBs
* that will time out later are placed after, and urbs with infinite timeout
* generic transfer object mentioned above. At this stage, the transfer
* is "blank" with no details about what type of I/O it will be used for.
*
- * Allocation is done with the libusb_alloc_transfer() function. It is also
- * possible to allocate your own libusb_transfer structure, but only if you
- * take the following into account:
- * -# For implementation reasons, the memory allocated for the transfer must
- * actually be larger than sizeof(struct libusb_transfer). If you wish to
- * allocate your own transfers, you must allocate them with the size
- * determined by libusb_get_transfer_alloc_size().
- * -# After allocating space for a transfer, you must initialize it with
- * libusb_init_transfer().
+ * Allocation is done with the libusb_alloc_transfer() function. You must use
+ * this function rather than allocating your own transfers.
*
* \subsection asyncfill Filling
*
*
* When a transfer has completed (i.e. the callback function has been invoked),
* you are advised to free the transfer (unless you wish to resubmit it, see
- * below).
- *
- * If you allocated the transfer with libusb_alloc_transfer(), deallocate it
- * with libusb_free_transfer(). If you're using your own memory management
- * scheme, free it through your scheme.
+ * below). Transfers are deallocated with libusb_free_transfer().
*
* It is undefined behaviour to free a transfer which has not completed.
*
* and libusb_control_transfer_get_setup() functions within your transfer
* callback.
*
+ * \section asynciso Considerations for isochronous transfers
+ *
+ * As isochronous transfers are more complicated than transfers to
+ * non-isochronous endpoints.
+ *
+ * To perform I/O to an isochronous endpoint, allocate the transfer by calling
+ * libusb_alloc_transfer() with an appropriate number of isochronous packets.
+ *
+ * During filling, set \ref libusb_transfer::endpoint_type "endpoint_type" to
+ * \ref libusb_endpoint_type::LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS
+ * "LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS", and set
+ * \ref libusb_transfer::num_iso_packets "num_iso_packets" to a value less than
+ * or equal to the number of packets you requested during allocation.
+ * libusb_alloc_transfer() does not set either of these fields for you, given
+ * that you might not even use the transfer on an isochronous endpoint.
+ *
+ * Next, populate the length field for the first num_iso_packets entries in
+ * the \ref libusb_transfer::iso_packet_desc "iso_packet_desc" array. Section
+ * 5.6.3 of the USB2 specifications describe how the maximum isochronous
+ * packet length is determined by the endpoint descriptor. FIXME need a helper
+ * function to find this.
+ * FIXME, write a helper function to set the length for all iso packets in an
+ * array
+ *
+ * For outgoing transfers, you'll obviously fill the buffer and populate the
+ * packet descriptors in hope that all the data gets transferred. For incoming
+ * transfers, you must ensure the buffer has sufficient capacity for
+ * the situation where all packets transfer the full amount of requested data.
+ *
+ * Completion handling requires some extra consideration. The
+ * \ref libusb_transfer::actual_length "actual_length" field of the transfer
+ * is meaningless and should not be examined; instead you must refer to the
+ * \ref libusb_iso_packet_descriptor::actual_length "actual_length" field of
+ * each individual packet.
+ *
+ * The \ref libusb_transfer::status "status" field of the transfer is also a
+ * little misleading:
+ * - If the packets were submitted and the isochronous data microframes
+ * completed normally, status will have value
+ * \ref libusb_transfer_status::LIBUSB_TRANSFER_COMPLETED
+ * "LIBUSB_TRANSFER_COMPLETED". Note that bus errors and software-incurred
+ * delays are not counted as transfer errors; the transfer.status field may
+ * indicate COMPLETED even if some or all of the packets failed. Refer to
+ * the \ref libusb_iso_packet_descriptor::status "status" field of each
+ * individual packet to determine packet failures.
+ * - The status field will have value
+ * \ref libusb_transfer_status::LIBUSB_TRANSFER_ERROR
+ * "LIBUSB_TRANSFER_ERROR" only when serious errors were encountered.
+ * - Other transfer status codes occur with normal behaviour.
+ *
+ * The data for each packet will be found at an offset into the buffer that
+ * can be calculated as if each prior packet completed in full. FIXME write
+ * a helper function to determine this, and flesh this description out a bit
+ * more.
+ *
* \section asyncmem Memory caveats
*
* In most circumstances, it is not safe to use stack memory for transfer
{
int r;
struct timespec current_time;
- unsigned int timeout = transfer->pub.timeout;
+ unsigned int timeout =
+ __USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer)->timeout;
if (!timeout)
return 0;
}
/** \ingroup asyncio
- * Obtain the memory-resident size of a transfer structure. Normally, you do
- * not care about this as you will use libusb_alloc_transfer() and
- * libusb_free_transfer() which hide this implementation detail from you.
- *
- * This function is useful when you wish to allocate transfers using your
- * own custom memory allocator. libusb's transfer structure is bigger than
- * it appears, so you must use this function to find out how much space is
- * required for each transfer allocation.
- *
- * If you are allocating your own transfers, remember to initialize them
- * with libusb_init_transfer().
- *
- * \returns the true size of a libusb_transfer structure
- */
-API_EXPORTED size_t libusb_get_transfer_alloc_size(void)
-{
- return sizeof(struct usbi_transfer) + usbi_backend->transfer_priv_size;
-}
-
-void __init_transfer(struct usbi_transfer *transfer)
-{
- memset(transfer, 0, sizeof(*transfer));
-}
-
-/** \ingroup asyncio
- * Initialize a libusb transfer. This function is only for users who allocate
- * their transfers using their own memory allocator. The more standard
- * libusb_alloc_transfer() returns pre-initialized transfers.
- * \param transfer a transfer to initialize
- */
-API_EXPORTED void libusb_init_transfer(struct libusb_transfer *transfer)
-{
- __init_transfer(TRANSFER_TO_PRIV(transfer));
-}
-
-/** \ingroup asyncio
- * Allocate a libusb transfer using the standard system memory allocator. The
- * returned transfer is pre-initialized for you. When the new transfer is no
- * longer needed, it should be freed with libusb_free_transfer().
- *
- * \note
- * Instead of using this function, it is legal for you to allocate transfers
- * using a memory allocator of your choosing, but only if you consider the
- * hidden size requirement (see libusb_get_transfer_alloc_size()) and
- * initialize them before use (see libusb_init_transfer()).
- *
+ * Allocate a libusb transfer with a specified number of isochronous packet
+ * descriptors. The returned transfer is pre-initialized for you. When the new
+ * transfer is no longer needed, it should be freed with
+ * libusb_free_transfer().
+ *
+ * Transfers intended for non-isochronous endpoints (e.g. control, bulk,
+ * interrupt) should specify an iso_packets count of zero.
+ *
+ * For transfers intended for isochronous endpoints, specify an appropriate
+ * number of packet descriptors to be allocated as part of the transfer.
+ * The returned transfer is not specially initialized for isochronous I/O;
+ * you are still required to set the
+ * \ref libusb_transfer::num_iso_packets "num_iso_packets" and
+ * \ref libusb_transfer::endpoint_type "endpoint_type" fields accordingly.
+ *
+ * It is safe to allocate a transfer with some isochronous packets and then
+ * use it on a non-isochronous endpoint. If you do this, ensure that at time
+ * of submission, num_iso_packets is 0 and that endpoint_type is set
+ * appropriately.
+ *
+ * \param iso_packets number of isochronous packet descriptors to allocate
* \returns a newly allocated transfer, or NULL on error
*/
-API_EXPORTED struct libusb_transfer *libusb_alloc_transfer(void)
+API_EXPORTED struct libusb_transfer *libusb_alloc_transfer(int iso_packets)
{
- struct usbi_transfer *transfer =
- malloc(sizeof(*transfer) + usbi_backend->transfer_priv_size);
- if (!transfer)
+ size_t os_alloc_size = usbi_backend->transfer_priv_size
+ + (usbi_backend->add_iso_packet_size * iso_packets);
+ int alloc_size = sizeof(struct usbi_transfer)
+ + sizeof(struct libusb_transfer)
+ + (sizeof(struct libusb_iso_packet_descriptor) * iso_packets)
+ + os_alloc_size;
+ struct usbi_transfer *itransfer = malloc(alloc_size);
+ if (!itransfer)
return NULL;
- __init_transfer(transfer);
- return &transfer->pub;
+ memset(itransfer, 0, alloc_size);
+ itransfer->num_iso_packets = iso_packets;
+ return __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
}
/** \ingroup asyncio
if (transfer->flags & LIBUSB_TRANSFER_FREE_BUFFER && transfer->buffer)
free(transfer->buffer);
- itransfer = TRANSFER_TO_PRIV(transfer);
+ itransfer = __LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
free(itransfer);
}
*/
API_EXPORTED int libusb_submit_transfer(struct libusb_transfer *transfer)
{
- struct usbi_transfer *itransfer = TRANSFER_TO_PRIV(transfer);
+ struct usbi_transfer *itransfer =
+ __LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
int r;
itransfer->transferred = 0;
*/
API_EXPORTED int libusb_cancel_transfer(struct libusb_transfer *transfer)
{
- struct usbi_transfer *itransfer = TRANSFER_TO_PRIV(transfer);
+ struct usbi_transfer *itransfer =
+ __LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
int r;
usbi_dbg("");
*/
API_EXPORTED int libusb_cancel_transfer_sync(struct libusb_transfer *transfer)
{
- struct usbi_transfer *itransfer = TRANSFER_TO_PRIV(transfer);
+ struct usbi_transfer *itransfer =
+ __LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
int r;
usbi_dbg("");
void usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
enum libusb_transfer_status status)
{
- struct libusb_transfer *transfer = &itransfer->pub;
+ struct libusb_transfer *transfer =
+ __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
uint8_t flags;
if (status == LIBUSB_TRANSFER_SILENT_COMPLETION)
* completed. we asynchronously cancel the URB and report timeout
* to the user when the URB cancellation completes (or not at all if the
* URB actually gets delivered as per this race) */
- struct libusb_transfer *transfer = &itransfer->pub;
+ struct libusb_transfer *transfer =
+ __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
int r;
itransfer->flags |= USBI_TRANSFER_TIMED_OUT;
LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2,
};
+/** \ingroup asyncio
+ * Isochronous packet descriptor. */
+struct libusb_iso_packet_descriptor {
+ /** Length of data to request in this packet */
+ unsigned int length;
+
+ /** Amount of data that was actually transferred */
+ unsigned int actual_length;
+
+ /** Status code for this packet */
+ enum libusb_transfer_status status;
+};
+
struct libusb_transfer;
typedef void (*libusb_transfer_cb_fn)(struct libusb_transfer *transfer);
/* FIXME: make const? */
/** The status of the transfer. Read-only, and only for use within
- * transfer callback function. */
+ * transfer callback function.
+ *
+ * If this is an isochronous transfer, this field may read COMPLETED even
+ * if there were errors in the frames. Use the
+ * \ref libusb_iso_packet_descriptor::status "status" field in each packet
+ * to determine if errors occurred. */
enum libusb_transfer_status status;
/** Length of the data buffer */
int length;
/** Actual length of data that was transferred. Read-only, and only for
- * use within transfer callback function. */
+ * use within transfer callback function. Not valid for isochronous
+ * endpoint transfers. */
int actual_length;
/** Callback function. This will be invoked when the transfer completes,
/** Data buffer */
unsigned char *buffer;
+
+ /** Number of isochronous packets. Only used for I/O with isochronous
+ * endpoints. */
+ int num_iso_packets;
+
+ /** Isochronous packet descriptors, for isochronous transfers only. */
+ struct libusb_iso_packet_descriptor iso_packet_desc[0];
};
int libusb_init(void);
size_t libusb_get_transfer_alloc_size(void);
void libusb_init_transfer(struct libusb_transfer *transfer);
-struct libusb_transfer *libusb_alloc_transfer(void);
+struct libusb_transfer *libusb_alloc_transfer(int iso_packets);
int libusb_submit_transfer(struct libusb_transfer *transfer);
int libusb_cancel_transfer(struct libusb_transfer *transfer);
int libusb_cancel_transfer_sync(struct libusb_transfer *transfer);
transfer->callback = callback;
}
+/** \ingroup asyncio
+ * Helper function to populate the required \ref libusb_transfer fields
+ * for an isochronous transfer.
+ *
+ * \param transfer the transfer to populate
+ * \param dev_handle handle of the device that will handle the transfer
+ * \param endpoint address of the endpoint where this transfer will be sent
+ * \param buffer data buffer
+ * \param length length of data buffer
+ * \param num_iso_packets the number of isochronous packets
+ * \param callback callback function to be invoked on transfer completion
+ * \param user_data user data to pass to callback function
+ * \param timeout timeout for the transfer in milliseconds
+ */
+static inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer,
+ libusb_device_handle *dev_handle, unsigned char endpoint,
+ unsigned char *buffer, int length, int num_iso_packets,
+ libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout)
+{
+ transfer->dev_handle = dev_handle;
+ transfer->endpoint = endpoint;
+ transfer->endpoint_type = LIBUSB_ENDPOINT_TYPE_BULK;
+ transfer->timeout = timeout;
+ transfer->buffer = buffer;
+ transfer->length = length;
+ transfer->num_iso_packets = num_iso_packets;
+ transfer->user_data = user_data;
+ transfer->callback = callback;
+}
+
/* sync I/O */
int libusb_control_transfer(libusb_device_handle *dev_handle,
#define USBI_TRANSFER_SYNC_CANCELLED (1<<0)
#define USBI_TRANSFER_TIMED_OUT (1<<1)
-struct usbi_transfer {
- /* must come first */
- struct libusb_transfer pub;
+/* in-memory transfer layout:
+ *
+ * 1. struct usbi_transfer
+ * 2. struct libusb_transfer (which includes iso packets) [variable size]
+ * 3. os private data [variable size]
+ *
+ * from a libusb_transfer, you can get the usbi_transfer by rewinding the
+ * appropriate number of bytes.
+ * the usbi_transfer includes the number of allocated packets, so you can
+ * determine the size of the transfer and hence the start and length of the
+ * OS-private data.
+ */
+struct usbi_transfer {
+ int num_iso_packets;
struct list_head list;
struct timeval timeout;
int transferred;
uint8_t flags;
-
- unsigned char os_priv[0];
};
+#define __USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer) \
+ ((struct libusb_transfer *)(((void *)(transfer)) \
+ + sizeof(struct usbi_transfer)))
+#define __LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer) \
+ ((struct usbi_transfer *)(((void *)(transfer)) \
+ - sizeof(struct usbi_transfer)))
+
+static inline void *usbi_transfer_get_os_priv(struct usbi_transfer *transfer)
+{
+ return ((void *)transfer) + sizeof(struct usbi_transfer)
+ + sizeof(struct libusb_transfer)
+ + (transfer->num_iso_packets
+ * sizeof(struct libusb_iso_packet_descriptor));
+}
+
/* bus structures */
/* All standard descriptors have these 2 fields in common */
/* number of bytes to reserve for usbi_transfer.os_priv */
size_t transfer_priv_size;
+
+ /* number of additional bytes for os_priv for each iso packet */
+ size_t add_iso_packet_size;
};
extern const struct usbi_os_backend * const usbi_backend;
int fd;
};
-struct linux_transfer_priv {
- struct usbfs_urb urb;
-};
-
static struct linux_device_priv *__device_priv(struct libusb_device *dev)
{
return (struct linux_device_priv *) dev->os_priv;
return (struct linux_device_handle_priv *) handle->os_priv;
}
-static struct linux_transfer_priv *__transfer_priv(
- struct usbi_transfer *transfer)
-{
- return (struct linux_transfer_priv *) transfer->os_priv;
-}
-
-#define TRANSFER_PRIV_GET_ITRANSFER(tpriv) \
- ((struct usbi_transfer *) \
- container_of((tpriv), struct usbi_transfer, os_priv))
-
static int check_usb_vfs(const char *dirname)
{
DIR *dir;
static int submit_transfer(struct usbi_transfer *itransfer)
{
- struct libusb_transfer *transfer = &itransfer->pub;
- struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb;
+ struct libusb_transfer *transfer =
+ __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+ struct usbfs_urb *urb = usbi_transfer_get_os_priv(itransfer);
struct linux_device_handle_priv *dpriv =
__device_handle_priv(transfer->dev_handle);
int to_be_transferred = transfer->length - itransfer->transferred;
int r;
urb->buffer = transfer->buffer + itransfer->transferred;
- urb->buffer_length = MIN(to_be_transferred, MAX_URB_BUFFER_LENGTH);
+ if (urb->type == USBFS_URB_TYPE_ISO) {
+ /* FIXME: iso stuff needs reworking. if a big transfer is submitted,
+ * split it up into multiple URBs. */
+ urb->buffer_length = to_be_transferred;
+ } else {
+ urb->buffer_length = MIN(to_be_transferred, MAX_URB_BUFFER_LENGTH);
+ }
/* FIXME: for requests that we have to split into multiple URBs, we should
* submit all the URBs instantly: submit, submit, submit, reap, reap, reap
return r;
}
+static void fill_iso_packet_descriptors(struct usbfs_urb *urb,
+ struct usbi_transfer *itransfer)
+{
+ struct libusb_transfer *transfer =
+ __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+ int i;
+
+ for (i = 0; i < transfer->num_iso_packets; i++) {
+ struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i];
+ struct libusb_iso_packet_descriptor *lib_desc =
+ &transfer->iso_packet_desc[i];
+ urb_desc->length = lib_desc->length;
+ }
+}
+
static int op_submit_transfer(struct usbi_transfer *itransfer)
{
- struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb;
- struct libusb_transfer *transfer = &itransfer->pub;
+ struct usbfs_urb *urb = usbi_transfer_get_os_priv(itransfer);
+ struct libusb_transfer *transfer =
+ __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
memset(urb, 0, sizeof(*urb));
+ urb->usercontext = itransfer;
switch (transfer->endpoint_type) {
case LIBUSB_ENDPOINT_TYPE_CONTROL:
urb->type = USBFS_URB_TYPE_CONTROL;
case LIBUSB_ENDPOINT_TYPE_INTERRUPT:
urb->type = USBFS_URB_TYPE_INTERRUPT;
break;
+ case LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS:
+ urb->type = USBFS_URB_TYPE_ISO;
+ /* FIXME: interface for non-ASAP data? */
+ urb->flags = USBFS_URB_ISO_ASAP;
+ fill_iso_packet_descriptors(urb, itransfer);
+ urb->number_of_packets = transfer->num_iso_packets;
+ break;
default:
usbi_err("unknown endpoint type %d", transfer->endpoint_type);
return -EINVAL;
static int op_cancel_transfer(struct usbi_transfer *itransfer)
{
- struct usbfs_urb *urb = &__transfer_priv(itransfer)->urb;
- struct libusb_transfer *transfer = &itransfer->pub;
+ struct usbfs_urb *urb = usbi_transfer_get_os_priv(itransfer);
+ struct libusb_transfer *transfer =
+ __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
struct linux_device_handle_priv *dpriv =
__device_handle_priv(transfer->dev_handle);
struct linux_device_handle_priv *hpriv = __device_handle_priv(handle);
int r;
struct usbfs_urb *urb;
- struct linux_transfer_priv *tpriv;
struct usbi_transfer *itransfer;
struct libusb_transfer *transfer;
int trf_requested;
return r;
}
- tpriv = container_of(urb, struct linux_transfer_priv, urb);
- itransfer = TRANSFER_PRIV_GET_ITRANSFER(tpriv);
- transfer = &itransfer->pub;
+ itransfer = urb->usercontext;
+ transfer = __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
usbi_dbg("urb type=%d status=%d transferred=%d", urb->type, urb->status,
urb->actual_length);
if (urb->status != 0)
usbi_warn("unrecognised urb status %d", urb->status);
+ /* copy isochronous packet results back */
+ if (transfer->endpoint_type == LIBUSB_ENDPOINT_TYPE_ISOCHRONOUS) {
+ int i;
+ for (i = 0; i < urb->number_of_packets; i++) {
+ struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i];
+ struct libusb_iso_packet_descriptor *lib_desc =
+ &transfer->iso_packet_desc[i];
+ lib_desc->status = urb_desc->status;
+ lib_desc->actual_length = urb_desc->actual_length;
+ }
+ }
+
/* determine how much data was asked for */
length = transfer->length;
if (transfer->endpoint_type == LIBUSB_ENDPOINT_TYPE_CONTROL)
.device_priv_size = sizeof(struct linux_device_priv),
.device_handle_priv_size = sizeof(struct linux_device_handle_priv),
- .transfer_priv_size = sizeof(struct linux_transfer_priv),
+ .transfer_priv_size = sizeof(struct usbfs_urb),
+ .add_iso_packet_size = sizeof(struct usbfs_iso_packet_desc),
};
uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
unsigned char *data, uint16_t wLength, unsigned int timeout)
{
- struct libusb_transfer *transfer = libusb_alloc_transfer();
+ struct libusb_transfer *transfer = libusb_alloc_transfer(0);
unsigned char *buffer;
int completed = 0;
int r;
unsigned char endpoint, unsigned char *buffer, int length,
int *transferred, unsigned int timeout, unsigned char endpoint_type)
{
- struct libusb_transfer *transfer = libusb_alloc_transfer();
+ struct libusb_transfer *transfer = libusb_alloc_transfer(0);
int completed = 0;
int r;