On Fri, Jan 9, 2015 at 9:00 AM, Martin Pieuchot <[email protected]> wrote:
> As pointed out by David Higgs, uhidev report functions do a lot of
> memory allocations and copies between buffers. This is not uncommon
> in USB land due to way DMA buffers are handled.
>
> One way to reduce the number of copies is to submit a transfer with
> the USBD_NO_COPY flag. Without it the kernel does two to three copies:
>
> (stack/driver array <-->) malloc'ed array <--> DMA reacheable array
>
> This is not limited to uhidev functions. That's also done for every
> transfer sent through an interrupt pipe or for every control request
> with data.
>
> I'd like to slowly change the stack such that USBD_NO_COPY becomes the
> default behavior. This would first reduces the number of buffer copies
> and then allow umass(4) to directly use the DMA reachable buffers
> provided by the SCSI stack, which should *at least* reduce the CPU usage
> when using USB drives.
>
> Here's a diff about uhidev(4) to illustrate the changes I'm talking
> about.
>
> Comments, ok?
>
See inline. Reviewed to the best of my ability/eyesight via
variable-width webmail.
> Index: uhidev.c
> ===================================================================
> RCS file: /cvs/src/sys/dev/usb/uhidev.c,v
> retrieving revision 1.68
> diff -u -p -r1.68 uhidev.c
> --- uhidev.c 9 Jan 2015 12:09:51 -0000 1.68
> +++ uhidev.c 9 Jan 2015 13:31:36 -0000
> @@ -671,18 +671,28 @@ int
> uhidev_set_report_async(struct uhidev_softc *sc, int type, int id, void
> *data,
> int len)
> {
> + struct usbd_xfer *xfer;
> usb_device_request_t req;
> - char *buf = data;
> int actlen = len;
> + char *buf;
> +
> + xfer = usbd_alloc_xfer(sc->sc_udev);
> + if (xfer == NULL)
> + return (-1);
> +
> + if (id > 0)
> + len++;
> +
> + buf = usbd_alloc_buffer(xfer, len);
> + if (buf == NULL)
> + return (-1);
You forgot to free xfer in this error path.
>
> /* Prepend the reportID. */
> if (id > 0) {
> - len++;
> - buf = malloc(len, M_TEMP, M_NOWAIT);
> - if (buf == NULL)
> - return (-1);
> buf[0] = id;
> memcpy(buf + 1, data, len - 1);
> + } else {
> + memcpy(buf, data, len);
> }
>
> req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
> @@ -691,17 +701,9 @@ uhidev_set_report_async(struct uhidev_so
> USETW(req.wIndex, sc->sc_ifaceno);
> USETW(req.wLength, len);
>
> - if (usbd_do_request_async(sc->sc_udev, &req, buf, NULL, NULL))
> + if (usbd_request_async(xfer, &req, NULL, NULL))
> actlen = -1;
>
> - /*
> - * Since report requests are write-only it is safe to free
> - * the buffer right after submitting the transfer because
> - * it won't be used afterward.
> - */
> - if (id > 0)
> - free(buf, M_TEMP, len);
> -
> return (actlen);
> }
>
> @@ -750,11 +752,11 @@ uhidev_get_report_async_cb(struct usbd_x
> if (info->id > 0) {
> len--;
> memcpy(info->data, xfer->buffer + 1, len);
> + } else {
> + memcpy(info->data, xfer->buffer, len);
> }
> }
> info->callback(info->priv, info->id, info->data, len);
> - if (info->id > 0)
> - free(xfer->buffer, M_TEMP, xfer->length);
> free(info, M_TEMP, sizeof(*info));
> usbd_free_xfer(xfer);
> }
> @@ -763,42 +765,48 @@ int
> uhidev_get_report_async(struct uhidev_softc *sc, int type, int id, void
> *data,
> int len, void *priv, void (*callback)(void *, int, void *, int))
> {
> + struct usbd_xfer *xfer;
> usb_device_request_t req;
> struct uhidev_async_info *info;
> - char *buf = data;
> int actlen = len;
> + void *buf;
> +
> + xfer = usbd_alloc_xfer(sc->sc_udev);
> + if (xfer == NULL)
> + return (-1);
> +
> + if (id > 0)
> + len++;
> +
> + buf = usbd_alloc_buffer(xfer, len);
> + if (buf == NULL) {
> + usbd_free_xfer(xfer);
> + return (-1);
> + }
>
> info = malloc(sizeof(*info), M_TEMP, M_NOWAIT);
> - if (info == NULL)
> + if (info == NULL) {
> + usbd_free_xfer(xfer);
> return (-1);
> + }
>
> info->callback = callback;
> info->priv = priv;
> info->data = data;
> info->id = id;
>
> - if (id > 0) {
> - len++;
> - buf = malloc(len, M_TEMP, M_NOWAIT|M_ZERO);
> - if (buf == NULL) {
> - free(info, M_TEMP, sizeof(*info));
> - return (-1);
> - }
> - }
> -
> req.bmRequestType = UT_READ_CLASS_INTERFACE;
> req.bRequest = UR_GET_REPORT;
> USETW2(req.wValue, type, id);
> USETW(req.wIndex, sc->sc_ifaceno);
> USETW(req.wLength, len);
>
> - if (usbd_do_request_async(sc->sc_udev, &req, buf, priv,
> - uhidev_get_report_async_cb)) {
> + if (usbd_request_async(xfer, &req, priv, uhidev_get_report_async_cb))
> {
> free(info, M_TEMP, sizeof(*info));
> - if (id > 0)
> - free(buf, M_TEMP, len);
> + usbd_free_xfer(xfer);
> actlen = -1;
> }
> +
> return (actlen);
> }
>
> Index: usbdi.c
> ===================================================================
> RCS file: /cvs/src/sys/dev/usb/usbdi.c,v
> retrieving revision 1.77
> diff -u -p -r1.77 usbdi.c
> --- usbdi.c 9 Jan 2015 12:15:48 -0000 1.77
> +++ usbdi.c 9 Jan 2015 13:22:02 -0000
> @@ -55,8 +55,7 @@ extern int usbdebug;
> #define DPRINTFN(n,x)
> #endif
>
> -void usbd_do_request_async_cb(struct usbd_xfer *, void *,
> - usbd_status);
> +void usbd_request_async_cb(struct usbd_xfer *, void *, usbd_status);
> void usbd_start_next(struct usbd_pipe *pipe);
> usbd_status usbd_open_pipe_ival(struct usbd_interface *, u_int8_t, u_int8_t,
> struct usbd_pipe **, int);
> @@ -586,6 +585,7 @@ usbd_status
> usbd_clear_endpoint_stall_async(struct usbd_pipe *pipe)
> {
> struct usbd_device *dev = pipe->device;
> + struct usbd_xfer *xfer;
> usb_device_request_t req;
> usbd_status err;
>
> @@ -596,7 +596,12 @@ usbd_clear_endpoint_stall_async(struct u
> USETW(req.wValue, UF_ENDPOINT_HALT);
> USETW(req.wIndex, pipe->endpoint->edesc->bEndpointAddress);
> USETW(req.wLength, 0);
> - err = usbd_do_request_async(dev, &req, 0, 0, 0);
> +
> + xfer = usbd_alloc_xfer(dev);
> + if (xfer == NULL)
> + return (USBD_NOMEM);
> +
> + err = usbd_request_async(xfer, &req, NULL, NULL);
Missing xfer cleanup here too, I think.
> return (err);
> }
>
> @@ -949,8 +954,7 @@ usbd_do_request_flags(struct usbd_device
> }
>
> void
> -usbd_do_request_async_cb(struct usbd_xfer *xfer, void *priv,
> - usbd_status status)
> +usbd_request_async_cb(struct usbd_xfer *xfer, void *priv, usbd_status status)
> {
> usbd_free_xfer(xfer);
> }
> @@ -960,19 +964,17 @@ usbd_do_request_async_cb(struct usbd_xfe
> * Can be used from interrupt context.
> */
> usbd_status
> -usbd_do_request_async(struct usbd_device *dev, usb_device_request_t *req,
> - void *data, void *priv, usbd_callback callback)
> +usbd_request_async(struct usbd_xfer *xfer, usb_device_request_t *req,
> + void *priv, usbd_callback callback)
> {
> - struct usbd_xfer *xfer;
> usbd_status err;
>
> - xfer = usbd_alloc_xfer(dev);
> - if (xfer == NULL)
> - return (USBD_NOMEM);
> if (callback == NULL)
> - callback = usbd_do_request_async_cb;
> - usbd_setup_default_xfer(xfer, dev, priv, USBD_DEFAULT_TIMEOUT, req,
> - data, UGETW(req->wLength), 0, callback);
> + callback = usbd_request_async_cb;
> +
> + usbd_setup_default_xfer(xfer, xfer->device, priv,
> + USBD_DEFAULT_TIMEOUT, req, NULL, UGETW(req->wLength),
> + USBD_NO_COPY, callback);
> err = usbd_transfer(xfer);
> if (err != USBD_IN_PROGRESS) {
> usbd_free_xfer(xfer);
> Index: usbdi.h
> ===================================================================
> RCS file: /cvs/src/sys/dev/usb/usbdi.h,v
> retrieving revision 1.64
> diff -u -p -r1.64 usbdi.h
> --- usbdi.h 9 Jan 2015 12:07:50 -0000 1.64
> +++ usbdi.h 9 Jan 2015 13:14:25 -0000
> @@ -121,8 +121,8 @@ usbd_status usbd_open_pipe_intr(struct u
> void *buffer, u_int32_t length, usbd_callback, int);
> usbd_status usbd_do_request(struct usbd_device *pipe, usb_device_request_t
> *req,
> void *data);
> -usbd_status usbd_do_request_async(struct usbd_device *pipe,
> - usb_device_request_t *req, void *data, void *priv, usbd_callback
> callback);
> +usbd_status usbd_request_async(struct usbd_xfer *xfer,
> + usb_device_request_t *req, void *priv, usbd_callback callback);
> usbd_status usbd_do_request_flags(struct usbd_device *pipe,
> usb_device_request_t *req, void *data, u_int16_t flags, int*, u_int32_t);
> usb_interface_descriptor_t *usbd_get_interface_descriptor(