Add support for persistent device memory.

Add a function to allocate memory belonging to a specific device,
so that the operating system can DMA straight into it for zerocopy,
and also avoid some clearing. Also, this allows up-front memory
allocation in the kernel at program startup; memory allocation is
otherwise done per-transfer, which can fail in a system where memory has become
fragmented over time).

This mirrors new functionality going into Linux' USB stack (recently
reviewed and acked upstream); only Linux is supported as a backend
currently.

[Chris Dickens] Modified to fix doxygen documentation, correct parameter
naming, reposition function declarations, and address a missing request
during the patch review process.

Signed-off-by: Chris Dickens <christopher.a.dickens@gmail.com>
This commit is contained in:
Steinar H. Gunderson 2016-02-20 12:26:12 +01:00 committed by Chris Dickens
parent 0b947e5f9b
commit a283c3b5a3
7 changed files with 110 additions and 2 deletions

0
.amend Normal file
View file

View file

@ -357,6 +357,8 @@ if (cfg != desired)
* - libusb_control_transfer_get_setup()
* - libusb_cpu_to_le16()
* - libusb_detach_kernel_driver()
* - libusb_dev_mem_alloc()
* - libusb_dev_mem_free()
* - libusb_error_name()
* - libusb_event_handler_active()
* - libusb_event_handling_ok()
@ -1812,6 +1814,60 @@ int API_EXPORTED libusb_free_streams(libusb_device_handle *dev_handle,
return LIBUSB_ERROR_NOT_SUPPORTED;
}
/** \ingroup libusb_asyncio
* Attempts to allocate a block of persistent DMA memory suitable for transfers
* against the given device. If successful, will return a block of memory
* that is suitable for use as "buffer" in \ref libusb_transfer against this
* device. Using this memory instead of regular memory means that the host
* controller can use DMA directly into the buffer to increase performance, and
* also that transfers can no longer fail due to kernel memory fragmentation.
*
* Note that this means you should not modify this memory (or even data on
* the same cache lines) when a transfer is in progress, although it is legal
* to have several transfers going on within the same memory block.
*
* Will return NULL on failure. Many systems do not support such zerocopy
* and will always return NULL. Memory allocated with this function must be
* freed with \ref libusb_dev_mem_free. Specifically, this means that the
* flag \ref LIBUSB_TRANSFER_FREE_BUFFER cannot be used to free memory allocated
* with this function.
*
* Since version 1.0.21, \ref LIBUSB_API_VERSION >= 0x01000105
*
* \param dev_handle a device handle
* \param length size of desired data buffer
* \returns a pointer to the newly allocated memory, or NULL on failure
*/
DEFAULT_VISIBILITY
unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle,
size_t length)
{
if (!dev_handle->dev->attached)
return NULL;
if (usbi_backend->dev_mem_alloc)
return usbi_backend->dev_mem_alloc(dev_handle, length);
else
return NULL;
}
/** \ingroup libusb_asyncio
* Free device memory allocated with libusb_dev_mem_alloc().
*
* \param dev_handle a device handle
* \param buffer pointer to the previously allocated memory
* \param length size of previously allocated memory
* \returns LIBUSB_SUCCESS, or a LIBUSB_ERROR code on failure
*/
int API_EXPORTED libusb_dev_mem_free(libusb_device_handle *dev_handle,
unsigned char *buffer, size_t length)
{
if (usbi_backend->dev_mem_free)
return usbi_backend->dev_mem_free(dev_handle, buffer, length);
else
return LIBUSB_ERROR_NOT_SUPPORTED;
}
/** \ingroup libusb_dev
* Determine if a kernel driver is active on an interface. If a kernel driver
* is active, you cannot claim the interface, and libusb will be unable to

View file

@ -20,6 +20,10 @@ EXPORTS
libusb_control_transfer@32 = libusb_control_transfer
libusb_detach_kernel_driver
libusb_detach_kernel_driver@8 = libusb_detach_kernel_driver
libusb_dev_mem_alloc
libusb_dev_mem_alloc@8 = libusb_dev_mem_alloc
libusb_dev_mem_free
libusb_dev_mem_free@12 = libusb_dev_mem_free
libusb_error_name
libusb_error_name@4 = libusb_error_name
libusb_event_handler_active

View file

@ -1137,7 +1137,10 @@ enum libusb_transfer_flags {
/** Report short frames as errors */
LIBUSB_TRANSFER_SHORT_NOT_OK = 1<<0,
/** Automatically free() transfer buffer during libusb_free_transfer() */
/** Automatically free() transfer buffer during libusb_free_transfer().
* Note that buffers allocated with libusb_dev_mem_alloc() should not
* be attempted freed in this way, since free() is not an appropriate
* way to release such memory. */
LIBUSB_TRANSFER_FREE_BUFFER = 1<<1,
/** Automatically call libusb_free_transfer() after callback returns.
@ -1392,6 +1395,11 @@ int LIBUSB_CALL libusb_alloc_streams(libusb_device_handle *dev_handle,
int LIBUSB_CALL libusb_free_streams(libusb_device_handle *dev_handle,
unsigned char *endpoints, int num_endpoints);
unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle,
size_t length);
int LIBUSB_CALL libusb_dev_mem_free(libusb_device_handle *dev_handle,
unsigned char *buffer, size_t length);
int LIBUSB_CALL libusb_kernel_driver_active(libusb_device_handle *dev_handle,
int interface_number);
int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev_handle,

View file

@ -933,6 +933,16 @@ struct usbi_os_backend {
int (*free_streams)(struct libusb_device_handle *dev_handle,
unsigned char *endpoints, int num_endpoints);
/* Allocate persistent DMA memory for the given device, suitable for
* zerocopy. May return NULL on failure. Optional to implement.
*/
unsigned char *(*dev_mem_alloc)(struct libusb_device_handle *handle,
size_t len);
/* Free memory allocated by dev_mem_alloc. */
int (*dev_mem_free)(struct libusb_device_handle *handle,
unsigned char *buffer, size_t len);
/* Determine if a kernel driver is active on an interface. Optional.
*
* The presence of a kernel driver on an interface indicates that any

View file

@ -33,6 +33,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
@ -1557,6 +1558,32 @@ static int op_free_streams(struct libusb_device_handle *handle,
endpoints, num_endpoints);
}
static unsigned char *op_dev_mem_alloc(struct libusb_device_handle *handle,
size_t len)
{
struct linux_device_handle_priv *hpriv = _device_handle_priv(handle);
unsigned char *buffer = (unsigned char *)mmap(NULL, len,
PROT_READ | PROT_WRITE, MAP_SHARED, hpriv->fd, 0);
if (buffer == MAP_FAILED) {
usbi_err(HANDLE_CTX(handle), "alloc dev mem failed errno %d",
errno);
return NULL;
}
return buffer;
}
static int op_dev_mem_free(struct libusb_device_handle *handle,
unsigned char *buffer, size_t len)
{
if (munmap(buffer, len) != 0) {
usbi_err(HANDLE_CTX(handle), "free dev mem failed errno %d",
errno);
return LIBUSB_ERROR_OTHER;
} else {
return LIBUSB_SUCCESS;
}
}
static int op_kernel_driver_active(struct libusb_device_handle *handle,
int interface)
{
@ -2678,6 +2705,9 @@ const struct usbi_os_backend linux_usbfs_backend = {
.alloc_streams = op_alloc_streams,
.free_streams = op_free_streams,
.dev_mem_alloc = op_dev_mem_alloc,
.dev_mem_free = op_dev_mem_free,
.kernel_driver_active = op_kernel_driver_active,
.detach_kernel_driver = op_detach_kernel_driver,
.attach_kernel_driver = op_attach_kernel_driver,

View file

@ -1 +1 @@
#define LIBUSB_NANO 11109
#define LIBUSB_NANO 11110