Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8635c016 authored by Peter Collingbourne's avatar Peter Collingbourne
Browse files

fastboot: Use asynchronous operations to send data

The fastboot command currently uses USBDEVFS_BULK to transfer data
(including image data) to the target. On the kernel side it looks
like this:

1. Allocate a contiguous memory region and copy the user data into
   the memory region (which may involve accessing storage).
2. Instruct the driver to start a DMA operation.
3. Wait for the DMA to finish.

This is suboptimal because it misses out on a pipelining
opportunity. We could be doing 3 for the current operation in parallel
with 1 for the next operation, so that the next DMA is ready to go
as soon as the current DMA finishes.

The kernel supports asynchronous operations on usbdevfs file
descriptors (USBDEVFS_SUBMITURB and USBDEVFS_REAPURB), so we can
implement this like so:

1. Submit URB 0
2. Submit URB 1
3. Wait for URB 0
4. Submit URB 2
5. Wait for URB 1
and so on.

That is what this CL implements. On my machine it increases transfer
speed from 125 MB/s to 160 MB/s using a USB 3.0 connection to the
target (Pixel 8).

Bug: 324107907
Change-Id: I20db7ea14af85db48f6494091c8279ef7a21033d
parent da6329bd
Loading
Loading
Loading
Loading
+65 −15
Original line number Diff line number Diff line
@@ -404,34 +404,84 @@ ssize_t LinuxUsbTransport::Write(const void* _data, size_t len)
{
    unsigned char *data = (unsigned char*) _data;
    unsigned count = 0;
    struct usbdevfs_bulktransfer bulk;
    int n;
    struct usbdevfs_urb urb[2] = {};
    bool pending[2] = {};

    if (handle_->ep_out == 0 || handle_->desc == -1) {
        return -1;
    }

    do {
        int xfer;
        xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len;
    auto submit_urb = [&](size_t i) {
        int xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len;

        bulk.ep = handle_->ep_out;
        bulk.len = xfer;
        bulk.data = data;
        bulk.timeout = ms_timeout_;
        urb[i].type = USBDEVFS_URB_TYPE_BULK;
        urb[i].endpoint = handle_->ep_out;
        urb[i].buffer_length = xfer;
        urb[i].buffer = data;
        urb[i].usercontext = (void *)i;

        n = ioctl(handle_->desc, USBDEVFS_BULK, &bulk);
        if(n != xfer) {
            DBG("ERROR: n = %d, errno = %d (%s)\n",
                n, errno, strerror(errno));
            return -1;
        int n = ioctl(handle_->desc, USBDEVFS_SUBMITURB, &urb[i]);
        if (n != 0) {
            DBG("ioctl(USBDEVFS_SUBMITURB) failed\n");
            return false;
        }

        pending[i] = true;
        count += xfer;
        len -= xfer;
        data += xfer;
    } while(len > 0);

        return true;
    };

    auto reap_urb = [&](size_t i) {
        while (pending[i]) {
            struct usbdevfs_urb *urbp;
            int res = ioctl(handle_->desc, USBDEVFS_REAPURB, &urbp);
            if (res != 0) {
                DBG("ioctl(USBDEVFS_REAPURB) failed\n");
                return false;
            }
            size_t done = (size_t)urbp->usercontext;
            if (done >= 2 || !pending[done]) {
                DBG("unexpected urb\n");
                return false;
            }
            if (urbp->status != 0 || urbp->actual_length != urbp->buffer_length) {
                DBG("urb returned error\n");
                return false;
            }
            pending[done] = false;
        }
        return true;
    };

    if (!submit_urb(0)) {
        return -1;
    }
    while (len > 0) {
        if (!submit_urb(1)) {
            return -1;
        }
        if (!reap_urb(0)) {
            return -1;
        }
        if (len <= 0) {
            if (!reap_urb(1)) {
                return -1;
            }
            return count;
        }
        if (!submit_urb(0)) {
            return -1;
        }
        if (!reap_urb(1)) {
            return -1;
        }
    }
    if (!reap_urb(0)) {
        return -1;
    }
    return count;
}