combined-packet.c 6.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
 * QEMU USB packet combining code (for input pipelining)
 *
 * Copyright(c) 2012 Red Hat, Inc.
 *
 * Red Hat Authors:
 * Hans de Goede <hdegoede@redhat.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or(at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */
P
Peter Maydell 已提交
22
#include "qemu/osdep.h"
23
#include "qemu/units.h"
24 25
#include "qemu-common.h"
#include "hw/usb.h"
26
#include "qemu/iov.h"
27 28 29 30 31 32 33 34 35
#include "trace.h"

static void usb_combined_packet_add(USBCombinedPacket *combined, USBPacket *p)
{
    qemu_iovec_concat(&combined->iov, &p->iov, 0, p->iov.size);
    QTAILQ_INSERT_TAIL(&combined->packets, p, combined_entry);
    p->combined = combined;
}

36
/* Note will free combined when the last packet gets removed */
37 38 39 40 41 42
static void usb_combined_packet_remove(USBCombinedPacket *combined,
                                       USBPacket *p)
{
    assert(p->combined == combined);
    p->combined = NULL;
    QTAILQ_REMOVE(&combined->packets, p, combined_entry);
43
    if (QTAILQ_EMPTY(&combined->packets)) {
44
        qemu_iovec_destroy(&combined->iov);
45 46
        g_free(combined);
    }
47 48 49 50 51 52 53 54
}

/* Also handles completion of non combined packets for pipelined input eps */
void usb_combined_input_packet_complete(USBDevice *dev, USBPacket *p)
{
    USBCombinedPacket *combined = p->combined;
    USBEndpoint *ep = p->ep;
    USBPacket *next;
55 56
    int status, actual_length;
    bool short_not_ok, done = false;
57 58 59 60 61 62 63 64

    if (combined == NULL) {
        usb_packet_complete_one(dev, p);
        goto leave;
    }

    assert(combined->first == p && p == QTAILQ_FIRST(&combined->packets));

65 66
    status = combined->first->status;
    actual_length = combined->first->actual_length;
67
    short_not_ok = QTAILQ_LAST(&combined->packets)->short_not_ok;
68 69

    QTAILQ_FOREACH_SAFE(p, &combined->packets, combined_entry, next) {
70
        if (!done) {
71
            /* Distribute data over uncombined packets */
72 73
            if (actual_length >= p->iov.size) {
                p->actual_length = p->iov.size;
74 75
            } else {
                /* Send short or error packet to complete the transfer */
76
                p->actual_length = actual_length;
77
                done = true;
78
            }
79
            /* Report status on the last packet */
80
            if (done || next == NULL) {
81 82 83 84
                p->status = status;
            } else {
                p->status = USB_RET_SUCCESS;
            }
85
            p->short_not_ok = short_not_ok;
86
            /* Note will free combined when the last packet gets removed! */
87 88
            usb_combined_packet_remove(combined, p);
            usb_packet_complete_one(dev, p);
89
            actual_length -= p->actual_length;
90 91
        } else {
            /* Remove any leftover packets from the queue */
92
            p->status = USB_RET_REMOVE_FROM_QUEUE;
93
            /* Note will free combined on the last packet! */
94 95 96
            dev->port->ops->complete(dev->port, p);
        }
    }
97
    /* Do not use combined here, it has been freed! */
98 99 100 101 102 103 104 105 106 107
leave:
    /* Check if there are packets in the queue waiting for our completion */
    usb_ep_combine_input_packets(ep);
}

/* May only be called for combined packets! */
void usb_combined_packet_cancel(USBDevice *dev, USBPacket *p)
{
    USBCombinedPacket *combined = p->combined;
    assert(combined != NULL);
108
    USBPacket *first = p->combined->first;
109

110
    /* Note will free combined on the last packet! */
111
    usb_combined_packet_remove(combined, p);
112
    if (p == first) {
113 114 115 116 117 118 119 120 121 122 123 124 125 126
        usb_device_cancel_packet(dev, p);
    }
}

/*
 * Large input transfers can get split into multiple input packets, this
 * function recombines them, removing the short_not_ok checks which all but
 * the last packet of such splits transfers have, thereby allowing input
 * transfer pipelining (which we cannot do on short_not_ok transfers)
 */
void usb_ep_combine_input_packets(USBEndpoint *ep)
{
    USBPacket *p, *u, *next, *prev = NULL, *first = NULL;
    USBPort *port = ep->dev->port;
127
    int totalsize;
128 129 130 131 132 133 134

    assert(ep->pipeline);
    assert(ep->pid == USB_TOKEN_IN);

    QTAILQ_FOREACH_SAFE(p, &ep->queue, queue, next) {
        /* Empty the queue on a halt */
        if (ep->halted) {
135
            p->status = USB_RET_REMOVE_FROM_QUEUE;
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
            port->ops->complete(port, p);
            continue;
        }

        /* Skip packets already submitted to the device */
        if (p->state == USB_PACKET_ASYNC) {
            prev = p;
            continue;
        }
        usb_packet_check_state(p, USB_PACKET_QUEUED);

        /*
         * If the previous (combined) packet has the short_not_ok flag set
         * stop, as we must not submit packets to the device after a transfer
         * ending with short_not_ok packet.
         */
        if (prev && prev->short_not_ok) {
            break;
        }

        if (first) {
            if (first->combined == NULL) {
                USBCombinedPacket *combined = g_new0(USBCombinedPacket, 1);

                combined->first = first;
                QTAILQ_INIT(&combined->packets);
                qemu_iovec_init(&combined->iov, 2);
                usb_combined_packet_add(combined, first);
            }
            usb_combined_packet_add(first->combined, p);
        } else {
            first = p;
        }

        /* Is this packet the last one of a (combined) transfer? */
171
        totalsize = (p->combined) ? p->combined->iov.size : p->iov.size;
172
        if ((p->iov.size % ep->max_packet_size) != 0 || !p->short_not_ok ||
173 174
                next == NULL ||
                /* Work around for Linux usbfs bulk splitting + migration */
175
                (totalsize == (16 * KiB - 36) && p->int_req)) {
176 177
            usb_device_handle_data(ep->dev, first);
            assert(first->status == USB_RET_ASYNC);
178 179 180 181 182 183 184 185 186 187 188 189
            if (first->combined) {
                QTAILQ_FOREACH(u, &first->combined->packets, combined_entry) {
                    usb_packet_set_state(u, USB_PACKET_ASYNC);
                }
            } else {
                usb_packet_set_state(first, USB_PACKET_ASYNC);
            }
            first = NULL;
            prev = p;
        }
    }
}