From a283c3b5a3dce8f6f33331b9aa1d95d41c8f241c Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 20 Feb 2016 12:26:12 +0100 Subject: [PATCH] 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 --- .amend | 0 libusb/core.c | 56 +++++++++++++++++++++++++++++++++++++++++ libusb/libusb-1.0.def | 4 +++ libusb/libusb.h | 10 +++++++- libusb/libusbi.h | 10 ++++++++ libusb/os/linux_usbfs.c | 30 ++++++++++++++++++++++ libusb/version_nano.h | 2 +- 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 .amend diff --git a/.amend b/.amend new file mode 100644 index 00000000..e69de29b diff --git a/libusb/core.c b/libusb/core.c index 5317d263..d57edad5 100644 --- a/libusb/core.c +++ b/libusb/core.c @@ -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 diff --git a/libusb/libusb-1.0.def b/libusb/libusb-1.0.def index 7b14e0fc..2443d9be 100644 --- a/libusb/libusb-1.0.def +++ b/libusb/libusb-1.0.def @@ -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 diff --git a/libusb/libusb.h b/libusb/libusb.h index 5b0d522b..f73e31cc 100644 --- a/libusb/libusb.h +++ b/libusb/libusb.h @@ -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, diff --git a/libusb/libusbi.h b/libusb/libusbi.h index 75a5a813..b5530d61 100644 --- a/libusb/libusbi.h +++ b/libusb/libusbi.h @@ -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 diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index d154aa9a..0689894d 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -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, diff --git a/libusb/version_nano.h b/libusb/version_nano.h index b92475f5..5921d744 100644 --- a/libusb/version_nano.h +++ b/libusb/version_nano.h @@ -1 +1 @@ -#define LIBUSB_NANO 11109 +#define LIBUSB_NANO 11110