Commit message: usb:xhci:fix memory leak in usb_xhci_exit

If the xhci uses msix, it doesn't free the corresponding
memory, thus leading a memory leak. This patch avoid this.

Signed-off-by: Li Qiang <liqiang6-s@360.cn>
Message-id: 57d7d2e0.d4301c0a.d13e9.9a55@mx.google.com
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
CVE: CVE-2016-7466
CWE: CWE-399
Vulnerability description: Memory leak in the usb_xhci_exit function in hw/usb/hcd-xhci.c in QEMU (aka Quick Emulator), when the xhci uses msix, allows local guest OS administrators to cause a denial of service (memory consumption and possibly QEMU process crash) by repeatedly unplugging a USB device.

Project: qemu
File: hcd-xhci.c

static void usb_xhci_realize(struct PCIDevice *dev, Error **errp)
{
    int i, ret;
    Error *err = NULL;

    XHCIState *xhci = XHCI(dev);

    dev->config[PCI_CLASS_PROG] = 0x30;    /* xHCI */
    dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin 1 */
    dev->config[PCI_CACHE_LINE_SIZE] = 0x10;
    dev->config[0x60] = 0x30; /* release number */

    usb_xhci_init(xhci);

    if (xhci->msi != ON_OFF_AUTO_OFF) {
        ret = msi_init(dev, 0x70, xhci->numintrs, true, false, &err);
        /* Any error other than -ENOTSUP(board's MSI support is broken)
         * is a programming error */
        assert(!ret || ret == -ENOTSUP);
        if (ret && xhci->msi == ON_OFF_AUTO_ON) {
            /* Can't satisfy user's explicit msi=on request, fail */
            error_append_hint(&err, "You have to use msi=auto (default) or "
                    "msi=off with this machine type.\n");
            error_propagate(errp, err);
            return;
        }
        assert(!err || xhci->msi == ON_OFF_AUTO_AUTO);
        /* With msi=auto, we fall back to MSI off silently */
        error_free(err);
    }

    if (xhci->numintrs > MAXINTRS) {
        xhci->numintrs = MAXINTRS;
    }
    while (xhci->numintrs & (xhci->numintrs - 1)) {   /* ! power of 2 */
        xhci->numintrs++;
    }
    if (xhci->numintrs < 1) {
        xhci->numintrs = 1;
    }
    if (xhci->numslots > MAXSLOTS) {
        xhci->numslots = MAXSLOTS;
    }
    if (xhci->numslots < 1) {
        xhci->numslots = 1;
    }
    if (xhci_get_flag(xhci, XHCI_FLAG_ENABLE_STREAMS)) {
        xhci->max_pstreams_mask = 7; /* == 256 primary streams */
    } else {
        xhci->max_pstreams_mask = 0;
    }

    xhci->mfwrap_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_mfwrap_timer, xhci);

    memory_region_init(&xhci->mem, OBJECT(xhci), "xhci", LEN_REGS);
    memory_region_init_io(&xhci->mem_cap, OBJECT(xhci), &xhci_cap_ops, xhci,
                          "capabilities", LEN_CAP);
    memory_region_init_io(&xhci->mem_oper, OBJECT(xhci), &xhci_oper_ops, xhci,
                          "operational", 0x400);
    memory_region_init_io(&xhci->mem_runtime, OBJECT(xhci), &xhci_runtime_ops, xhci,
                          "runtime", LEN_RUNTIME);
    memory_region_init_io(&xhci->mem_doorbell, OBJECT(xhci), &xhci_doorbell_ops, xhci,
                          "doorbell", LEN_DOORBELL);

    memory_region_add_subregion(&xhci->mem, 0,            &xhci->mem_cap);
    memory_region_add_subregion(&xhci->mem, OFF_OPER,     &xhci->mem_oper);
    memory_region_add_subregion(&xhci->mem, OFF_RUNTIME,  &xhci->mem_runtime);
    memory_region_add_subregion(&xhci->mem, OFF_DOORBELL, &xhci->mem_doorbell);

    for (i = 0; i < xhci->numports; i++) {
        XHCIPort *port = &xhci->ports[i];
        uint32_t offset = OFF_OPER + 0x400 + 0x10 * i;
        port->xhci = xhci;
        memory_region_init_io(&port->mem, OBJECT(xhci), &xhci_port_ops, port,
                              port->name, 0x10);
        memory_region_add_subregion(&xhci->mem, offset, &port->mem);
    }

    pci_register_bar(dev, 0,
                     PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64,
                     &xhci->mem);

    if (pci_bus_is_express(dev->bus) ||
        xhci_get_flag(xhci, XHCI_FLAG_FORCE_PCIE_ENDCAP)) {
        ret = pcie_endpoint_cap_init(dev, 0xa0);
        assert(ret >= 0);
    }

    if (xhci->msix != ON_OFF_AUTO_OFF) {
        /* TODO check for errors */
        msix_init(dev, xhci->numintrs,
                  &xhci->mem, 0, OFF_MSIX_TABLE,
                  &xhci->mem, 0, OFF_MSIX_PBA,
                  0x90);
    }
}

Project: qemu
File: hcd-xhci.c

static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci,
                                       unsigned int slotid,
                                       unsigned int epid)
{
    XHCIEPContext *epctx;
    int i;

    epctx = g_new0(XHCIEPContext, 1);
    epctx->xhci = xhci;
    epctx->slotid = slotid;
    epctx->epid = epid;

    for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) {
        epctx->transfers[i].xhci = xhci;
        epctx->transfers[i].slotid = slotid;
        epctx->transfers[i].epid = epid;
        usb_packet_init(&epctx->transfers[i].packet);
    }
    epctx->kick_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_ep_kick_timer, epctx);

    return epctx;
}

Project: qemu
File: hcd-xhci.c

static void xhci_complete(USBPort *port, USBPacket *packet)
{
    XHCITransfer *xfer = container_of(packet, XHCITransfer, packet);

    if (packet->status == USB_RET_REMOVE_FROM_QUEUE) {
        xhci_ep_nuke_one_xfer(xfer, 0);
        return;
    }
    xhci_complete_packet(xfer);
    xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid, xfer->streamid);
}

Project: qemu
File: hcd-xhci.c

static int xhci_complete_packet(XHCITransfer *xfer)
{
    if (xfer->packet.status == USB_RET_ASYNC) {
        trace_usb_xhci_xfer_async(xfer);
        xfer->running_async = 1;
        xfer->running_retry = 0;
        xfer->complete = 0;
        return 0;
    } else if (xfer->packet.status == USB_RET_NAK) {
        trace_usb_xhci_xfer_nak(xfer);
        xfer->running_async = 0;
        xfer->running_retry = 1;
        xfer->complete = 0;
        return 0;
    } else {
        xfer->running_async = 0;
        xfer->running_retry = 0;
        xfer->complete = 1;
        xhci_xfer_unmap(xfer);
    }

    if (xfer->packet.status == USB_RET_SUCCESS) {
        trace_usb_xhci_xfer_success(xfer, xfer->packet.actual_length);
        xfer->status = CC_SUCCESS;
        xhci_xfer_report(xfer);
        return 0;
    }

    /* error */
    trace_usb_xhci_xfer_error(xfer, xfer->packet.status);
    switch (xfer->packet.status) {
    case USB_RET_NODEV:
    case USB_RET_IOERROR:
        xfer->status = CC_USB_TRANSACTION_ERROR;
        xhci_xfer_report(xfer);
        xhci_stall_ep(xfer);
        break;
    case USB_RET_STALL:
        xfer->status = CC_STALL_ERROR;
        xhci_xfer_report(xfer);
        xhci_stall_ep(xfer);
        break;
    case USB_RET_BABBLE:
        xfer->status = CC_BABBLE_DETECTED;
        xhci_xfer_report(xfer);
        xhci_stall_ep(xfer);
        break;
    default:
        DPRINTF("%s: FIXME: status = %d\n", __func__,
                xfer->packet.status);
        FIXME("unhandled USB_RET_*");
    }
    return 0;
}

Project: qemu
File: hcd-xhci.c

static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid,
                               unsigned int epid)
{
    XHCISlot *slot;
    XHCIEPContext *epctx;
    int i;

    trace_usb_xhci_ep_disable(slotid, epid);
    assert(slotid >= 1 && slotid <= xhci->numslots);
    assert(epid >= 1 && epid <= 31);

    slot = &xhci->slots[slotid-1];

    if (!slot->eps[epid-1]) {
        DPRINTF("xhci: slot %d ep %d already disabled\n", slotid, epid);
        return CC_SUCCESS;
    }

    xhci_ep_nuke_xfers(xhci, slotid, epid, 0);

    epctx = slot->eps[epid-1];

    if (epctx->nr_pstreams) {
        xhci_free_streams(epctx);
    }

    for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) {
        usb_packet_cleanup(&epctx->transfers[i].packet);
    }

    /* only touch guest RAM if we're not resetting the HC */
    if (xhci->dcbaap_low || xhci->dcbaap_high) {
        xhci_set_ep_state(xhci, epctx, NULL, EP_DISABLED);
    }

    timer_free(epctx->kick_timer);
    g_free(epctx);
    slot->eps[epid-1] = NULL;

    return CC_SUCCESS;
}

Project: qemu
File: hcd-xhci.c

static TRBCCode xhci_enable_slot(XHCIState *xhci, unsigned int slotid)
{
    trace_usb_xhci_slot_enable(slotid);
    assert(slotid >= 1 && slotid <= xhci->numslots);
    xhci->slots[slotid-1].enabled = 1;
    xhci->slots[slotid-1].uport = NULL;
    memset(xhci->slots[slotid-1].eps, 0, sizeof(XHCIEPContext*)*31);

    return CC_SUCCESS;
}

Project: qemu
File: hcd-xhci.c

static void xhci_ep_kick_timer(void *opaque)
{
    XHCIEPContext *epctx = opaque;
    xhci_kick_ep(epctx->xhci, epctx->slotid, epctx->epid, 0);
}

Project: qemu
File: hcd-xhci.c

static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report)
{
    int killed = 0;

    if (report && (t->running_async || t->running_retry)) {
        t->status = report;
        xhci_xfer_report(t);
    }

    if (t->running_async) {
        usb_cancel_packet(&t->packet);
        t->running_async = 0;
        killed = 1;
    }
    if (t->running_retry) {
        XHCIEPContext *epctx = t->xhci->slots[t->slotid-1].eps[t->epid-1];
        if (epctx) {
            epctx->retry = NULL;
            timer_del(epctx->kick_timer);
        }
        t->running_retry = 0;
        killed = 1;
    }
    g_free(t->trbs);

    t->trbs = NULL;
    t->trb_count = t->trb_alloced = 0;

    return killed;
}

Project: qemu
File: hcd-xhci.c

static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid,
                               unsigned int epid, TRBCCode report)
{
    XHCISlot *slot;
    XHCIEPContext *epctx;
    int i, xferi, killed = 0;
    USBEndpoint *ep = NULL;
    assert(slotid >= 1 && slotid <= xhci->numslots);
    assert(epid >= 1 && epid <= 31);

    DPRINTF("xhci_ep_nuke_xfers(%d, %d)\n", slotid, epid);

    slot = &xhci->slots[slotid-1];

    if (!slot->eps[epid-1]) {
        return 0;
    }

    epctx = slot->eps[epid-1];

    xferi = epctx->next_xfer;
    for (i = 0; i < TD_QUEUE; i++) {
        killed += xhci_ep_nuke_one_xfer(&epctx->transfers[xferi], report);
        if (killed) {
            report = 0; /* Only report once */
        }
        epctx->transfers[xferi].packet.ep = NULL;
        xferi = (xferi + 1) % TD_QUEUE;
    }

    ep = xhci_epid_to_usbep(xhci, slotid, epid);
    if (ep) {
        usb_device_ep_stopped(ep->dev, ep);
    }
    return killed;
}

Project: qemu
File: hcd-xhci.c

static USBEndpoint *xhci_epid_to_usbep(XHCIState *xhci,
                                       unsigned int slotid, unsigned int epid)
{
    assert(slotid >= 1 && slotid <= xhci->numslots);

    if (!xhci->slots[slotid - 1].uport) {
        return NULL;
    }

    return usb_ep_get(xhci->slots[slotid - 1].uport->dev,
                      (epid & 1) ? USB_TOKEN_IN : USB_TOKEN_OUT, epid >> 1);
}

Project: qemu
File: hcd-xhci.c

static int xhci_epmask_to_eps_with_streams(XHCIState *xhci,
                                           unsigned int slotid,
                                           uint32_t epmask,
                                           XHCIEPContext **epctxs,
                                           USBEndpoint **eps)
{
    XHCISlot *slot;
    XHCIEPContext *epctx;
    USBEndpoint *ep;
    int i, j;

    assert(slotid >= 1 && slotid <= xhci->numslots);

    slot = &xhci->slots[slotid - 1];

    for (i = 2, j = 0; i <= 31; i++) {
        if (!(epmask & (1u << i))) {
            continue;
        }

        epctx = slot->eps[i - 1];
        ep = xhci_epid_to_usbep(xhci, slotid, i);
        if (!epctx || !epctx->nr_pstreams || !ep) {
            continue;
        }

        if (epctxs) {
            epctxs[j] = epctx;
        }
        eps[j++] = ep;
    }
    return j;
}

Project: qemu
File: hcd-xhci.c

static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer)
{
    XHCITRB *trb_setup, *trb_status;
    uint8_t bmRequestType;

    trb_setup = &xfer->trbs[0];
    trb_status = &xfer->trbs[xfer->trb_count-1];

    trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->epid, xfer->streamid);

    /* at most one Event Data TRB allowed after STATUS */
    if (TRB_TYPE(*trb_status) == TR_EVDATA && xfer->trb_count > 2) {
        trb_status--;
    }

    /* do some sanity checks */
    if (TRB_TYPE(*trb_setup) != TR_SETUP) {
        DPRINTF("xhci: ep0 first TD not SETUP: %d\n",
                TRB_TYPE(*trb_setup));
        return -1;
    }
    if (TRB_TYPE(*trb_status) != TR_STATUS) {
        DPRINTF("xhci: ep0 last TD not STATUS: %d\n",
                TRB_TYPE(*trb_status));
        return -1;
    }
    if (!(trb_setup->control & TRB_TR_IDT)) {
        DPRINTF("xhci: Setup TRB doesn't have IDT set\n");
        return -1;
    }
    if ((trb_setup->status & 0x1ffff) != 8) {
        DPRINTF("xhci: Setup TRB has bad length (%d)\n",
                (trb_setup->status & 0x1ffff));
        return -1;
    }

    bmRequestType = trb_setup->parameter;

    xfer->in_xfer = bmRequestType & USB_DIR_IN;
    xfer->iso_xfer = false;
    xfer->timed_xfer = false;

    if (xhci_setup_packet(xfer) < 0) {
        return -1;
    }
    xfer->packet.parameter = trb_setup->parameter;

    usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);

    xhci_complete_packet(xfer);
    if (!xfer->running_async && !xfer->running_retry) {
        xhci_kick_ep(xhci, xfer->slotid, xfer->epid, 0);
    }
    return 0;
}

Project: qemu
File: hcd-xhci.c

static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx)
{
    trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->epid, xfer->streamid);
    return xhci_submit(xhci, xfer, epctx);
}

Project: qemu
File: hcd-xhci.c

static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
                         unsigned int epid, unsigned int streamid)
{
    XHCIStreamContext *stctx;
    XHCIEPContext *epctx;
    XHCIRing *ring;
    USBEndpoint *ep = NULL;
    uint64_t mfindex;
    int length;
    int i;

    trace_usb_xhci_ep_kick(slotid, epid, streamid);
    assert(slotid >= 1 && slotid <= xhci->numslots);
    assert(epid >= 1 && epid <= 31);

    if (!xhci->slots[slotid-1].enabled) {
        DPRINTF("xhci: xhci_kick_ep for disabled slot %d\n", slotid);
        return;
    }
    epctx = xhci->slots[slotid-1].eps[epid-1];
    if (!epctx) {
        DPRINTF("xhci: xhci_kick_ep for disabled endpoint %d,%d\n",
                epid, slotid);
        return;
    }

    /* If the device has been detached, but the guest has not noticed this
       yet the 2 above checks will succeed, but we must NOT continue */
    if (!xhci->slots[slotid - 1].uport ||
        !xhci->slots[slotid - 1].uport->dev ||
        !xhci->slots[slotid - 1].uport->dev->attached) {
        return;
    }

    if (epctx->retry) {
        XHCITransfer *xfer = epctx->retry;

        trace_usb_xhci_xfer_retry(xfer);
        assert(xfer->running_retry);
        if (xfer->timed_xfer) {
            /* time to kick the transfer? */
            mfindex = xhci_mfindex_get(xhci);
            xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
            if (xfer->running_retry) {
                return;
            }
            xfer->timed_xfer = 0;
            xfer->running_retry = 1;
        }
        if (xfer->iso_xfer) {
            /* retry iso transfer */
            if (xhci_setup_packet(xfer) < 0) {
                return;
            }
            usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);
            assert(xfer->packet.status != USB_RET_NAK);
            xhci_complete_packet(xfer);
        } else {
            /* retry nak'ed transfer */
            if (xhci_setup_packet(xfer) < 0) {
                return;
            }
            usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);
            if (xfer->packet.status == USB_RET_NAK) {
                return;
            }
            xhci_complete_packet(xfer);
        }
        assert(!xfer->running_retry);
        epctx->retry = NULL;
    }

    if (epctx->state == EP_HALTED) {
        DPRINTF("xhci: ep halted, not running schedule\n");
        return;
    }


    if (epctx->nr_pstreams) {
        uint32_t err;
        stctx = xhci_find_stream(epctx, streamid, &err);
        if (stctx == NULL) {
            return;
        }
        ring = &stctx->ring;
        xhci_set_ep_state(xhci, epctx, stctx, EP_RUNNING);
    } else {
        ring = &epctx->ring;
        streamid = 0;
        xhci_set_ep_state(xhci, epctx, NULL, EP_RUNNING);
    }
    assert(ring->dequeue != 0);

    while (1) {
        XHCITransfer *xfer = &epctx->transfers[epctx->next_xfer];
        if (xfer->running_async || xfer->running_retry) {
            break;
        }
        length = xhci_ring_chain_length(xhci, ring);
        if (length < 0) {
            break;
        } else if (length == 0) {
            break;
        }
        if (xfer->trbs && xfer->trb_alloced < length) {
            xfer->trb_count = 0;
            xfer->trb_alloced = 0;
            g_free(xfer->trbs);
            xfer->trbs = NULL;
        }
        if (!xfer->trbs) {
            xfer->trbs = g_new(XHCITRB, length);
            xfer->trb_alloced = length;
        }
        xfer->trb_count = length;

        for (i = 0; i < length; i++) {
            TRBType type;
            type = xhci_ring_fetch(xhci, ring, &xfer->trbs[i], NULL);
            assert(type);
        }
        xfer->streamid = streamid;

        if (epid == 1) {
            if (xhci_fire_ctl_transfer(xhci, xfer) >= 0) {
                epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE;
            } else {
                DPRINTF("xhci: error firing CTL transfer\n");
            }
        } else {
            if (xhci_fire_transfer(xhci, xfer, epctx) >= 0) {
                epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE;
            } else {
                if (!xfer->timed_xfer) {
                    DPRINTF("xhci: error firing data transfer\n");
                }
            }
        }

        if (epctx->state == EP_HALTED) {
            break;
        }
        if (xfer->running_retry) {
            DPRINTF("xhci: xfer nacked, stopping schedule\n");
            epctx->retry = xfer;
            break;
        }
    }

    ep = xhci_epid_to_usbep(xhci, slotid, epid);
    if (ep) {
        usb_device_flush_ep_queue(ep->dev, ep);
    }
}

Project: qemu
File: hcd-xhci.c

static int xhci_ring_chain_length(XHCIState *xhci, const XHCIRing *ring)
{
    PCIDevice *pci_dev = PCI_DEVICE(xhci);
    XHCITRB trb;
    int length = 0;
    dma_addr_t dequeue = ring->dequeue;
    bool ccs = ring->ccs;
    /* hack to bundle together the two/three TDs that make a setup transfer */
    bool control_td_set = 0;

    while (1) {
        TRBType type;
        pci_dma_read(pci_dev, dequeue, &trb, TRB_SIZE);
        le64_to_cpus(&trb.parameter);
        le32_to_cpus(&trb.status);
        le32_to_cpus(&trb.control);

        if ((trb.control & TRB_C) != ccs) {
            return -length;
        }

        type = TRB_TYPE(trb);

        if (type == TR_LINK) {
            dequeue = xhci_mask64(trb.parameter);
            if (trb.control & TRB_LK_TC) {
                ccs = !ccs;
            }
            continue;
        }

        length += 1;
        dequeue += TRB_SIZE;

        if (type == TR_SETUP) {
            control_td_set = 1;
        } else if (type == TR_STATUS) {
            control_td_set = 0;
        }

        if (!control_td_set && !(trb.control & TRB_TR_CH)) {
            return length;
        }
    }
}

Project: qemu
File: hcd-xhci.c

static TRBType xhci_ring_fetch(XHCIState *xhci, XHCIRing *ring, XHCITRB *trb,
                               dma_addr_t *addr)
{
    PCIDevice *pci_dev = PCI_DEVICE(xhci);

    while (1) {
        TRBType type;
        pci_dma_read(pci_dev, ring->dequeue, trb, TRB_SIZE);
        trb->addr = ring->dequeue;
        trb->ccs = ring->ccs;
        le64_to_cpus(&trb->parameter);
        le32_to_cpus(&trb->status);
        le32_to_cpus(&trb->control);

        trace_usb_xhci_fetch_trb(ring->dequeue, trb_name(trb),
                                 trb->parameter, trb->status, trb->control);

        if ((trb->control & TRB_C) != ring->ccs) {
            return 0;
        }

        type = TRB_TYPE(*trb);

        if (type != TR_LINK) {
            if (addr) {
                *addr = ring->dequeue;
            }
            ring->dequeue += TRB_SIZE;
            return type;
        } else {
            ring->dequeue = xhci_mask64(trb->parameter);
            if (trb->control & TRB_LK_TC) {
                ring->ccs = !ring->ccs;
            }
        }
    }
}

Project: qemu
File: hcd-xhci.c

static int xhci_setup_packet(XHCITransfer *xfer)
{
    XHCIState *xhci = xfer->xhci;
    USBEndpoint *ep;
    int dir;

    dir = xfer->in_xfer ? USB_TOKEN_IN : USB_TOKEN_OUT;

    if (xfer->packet.ep) {
        ep = xfer->packet.ep;
    } else {
        ep = xhci_epid_to_usbep(xhci, xfer->slotid, xfer->epid);
        if (!ep) {
            DPRINTF("xhci: slot %d has no device\n",
                    xfer->slotid);
            return -1;
        }
    }

    xhci_xfer_create_sgl(xfer, dir == USB_TOKEN_IN); /* Also sets int_req */
    usb_packet_setup(&xfer->packet, dir, ep, xfer->streamid,
                     xfer->trbs[0].addr, false, xfer->int_req);
    usb_packet_map(&xfer->packet, &xfer->sgl);
    DPRINTF("xhci: setup packet pid 0x%x addr %d ep %d\n",
            xfer->packet.pid, ep->dev->addr, ep->nr);
    return 0;
}

Project: qemu
File: hcd-xhci.c

static void xhci_stall_ep(XHCITransfer *xfer)
{
    XHCIState *xhci = xfer->xhci;
    XHCISlot *slot = &xhci->slots[xfer->slotid-1];
    XHCIEPContext *epctx = slot->eps[xfer->epid-1];
    uint32_t err;
    XHCIStreamContext *sctx;

    if (epctx->nr_pstreams) {
        sctx = xhci_find_stream(epctx, xfer->streamid, &err);
        if (sctx == NULL) {
            return;
        }
        sctx->ring.dequeue = xfer->trbs[0].addr;
        sctx->ring.ccs = xfer->trbs[0].ccs;
        xhci_set_ep_state(xhci, epctx, sctx, EP_HALTED);
    } else {
        epctx->ring.dequeue = xfer->trbs[0].addr;
        epctx->ring.ccs = xfer->trbs[0].ccs;
        xhci_set_ep_state(xhci, epctx, NULL, EP_HALTED);
    }
}

Project: qemu
File: hcd-xhci.c

static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx)
{
    uint64_t mfindex;

    DPRINTF("xhci_submit(slotid=%d,epid=%d)\n", xfer->slotid, xfer->epid);

    xfer->in_xfer = epctx->type>>2;

    switch(epctx->type) {
    case ET_INTR_OUT:
    case ET_INTR_IN:
        xfer->pkts = 0;
        xfer->iso_xfer = false;
        xfer->timed_xfer = true;
        mfindex = xhci_mfindex_get(xhci);
        xhci_calc_intr_kick(xhci, xfer, epctx, mfindex);
        xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
        if (xfer->running_retry) {
            return -1;
        }
        break;
    case ET_BULK_OUT:
    case ET_BULK_IN:
        xfer->pkts = 0;
        xfer->iso_xfer = false;
        xfer->timed_xfer = false;
        break;
    case ET_ISO_OUT:
    case ET_ISO_IN:
        xfer->pkts = 1;
        xfer->iso_xfer = true;
        xfer->timed_xfer = true;
        mfindex = xhci_mfindex_get(xhci);
        xhci_calc_iso_kick(xhci, xfer, epctx, mfindex);
        xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
        if (xfer->running_retry) {
            return -1;
        }
        break;
    default:
        trace_usb_xhci_unimplemented("endpoint type", epctx->type);
        return -1;
    }

    if (xhci_setup_packet(xfer) < 0) {
        return -1;
    }
    usb_handle_packet(xfer->packet.ep->dev, &xfer->packet);

    xhci_complete_packet(xfer);
    if (!xfer->running_async && !xfer->running_retry) {
        xhci_kick_ep(xhci, xfer->slotid, xfer->epid, xfer->streamid);
    }
    return 0;
}

Project: qemu
File: hcd-xhci.c

static int xhci_xfer_create_sgl(XHCITransfer *xfer, int in_xfer)
{
    XHCIState *xhci = xfer->xhci;
    int i;

    xfer->int_req = false;
    pci_dma_sglist_init(&xfer->sgl, PCI_DEVICE(xhci), xfer->trb_count);
    for (i = 0; i < xfer->trb_count; i++) {
        XHCITRB *trb = &xfer->trbs[i];
        dma_addr_t addr;
        unsigned int chunk = 0;

        if (trb->control & TRB_TR_IOC) {
            xfer->int_req = true;
        }

        switch (TRB_TYPE(*trb)) {
        case TR_DATA:
            if ((!(trb->control & TRB_TR_DIR)) != (!in_xfer)) {
                DPRINTF("xhci: data direction mismatch for TR_DATA\n");
                goto err;
            }
            /* fallthrough */
        case TR_NORMAL:
        case TR_ISOCH:
            addr = xhci_mask64(trb->parameter);
            chunk = trb->status & 0x1ffff;
            if (trb->control & TRB_TR_IDT) {
                if (chunk > 8 || in_xfer) {
                    DPRINTF("xhci: invalid immediate data TRB\n");
                    goto err;
                }
                qemu_sglist_add(&xfer->sgl, trb->addr, chunk);
            } else {
                qemu_sglist_add(&xfer->sgl, addr, chunk);
            }
            break;
        }
    }

    return 0;

err:
    qemu_sglist_destroy(&xfer->sgl);
    xhci_die(xhci);
    return -1;
}

Project: qemu
File: hcd-xhci.c

static void xhci_xfer_report(XHCITransfer *xfer)
{
    uint32_t edtla = 0;
    unsigned int left;
    bool reported = 0;
    bool shortpkt = 0;
    XHCIEvent event = {ER_TRANSFER, CC_SUCCESS};
    XHCIState *xhci = xfer->xhci;
    int i;

    left = xfer->packet.actual_length;

    for (i = 0; i < xfer->trb_count; i++) {
        XHCITRB *trb = &xfer->trbs[i];
        unsigned int chunk = 0;

        switch (TRB_TYPE(*trb)) {
        case TR_SETUP:
            chunk = trb->status & 0x1ffff;
            if (chunk > 8) {
                chunk = 8;
            }
            break;
        case TR_DATA:
        case TR_NORMAL:
        case TR_ISOCH:
            chunk = trb->status & 0x1ffff;
            if (chunk > left) {
                chunk = left;
                if (xfer->status == CC_SUCCESS) {
                    shortpkt = 1;
                }
            }
            left -= chunk;
            edtla += chunk;
            break;
        case TR_STATUS:
            reported = 0;
            shortpkt = 0;
            break;
        }

        if (!reported && ((trb->control & TRB_TR_IOC) ||
                          (shortpkt && (trb->control & TRB_TR_ISP)) ||
                          (xfer->status != CC_SUCCESS && left == 0))) {
            event.slotid = xfer->slotid;
            event.epid = xfer->epid;
            event.length = (trb->status & 0x1ffff) - chunk;
            event.flags = 0;
            event.ptr = trb->addr;
            if (xfer->status == CC_SUCCESS) {
                event.ccode = shortpkt ? CC_SHORT_PACKET : CC_SUCCESS;
            } else {
                event.ccode = xfer->status;
            }
            if (TRB_TYPE(*trb) == TR_EVDATA) {
                event.ptr = trb->parameter;
                event.flags |= TRB_EV_ED;
                event.length = edtla & 0xffffff;
                DPRINTF("xhci_xfer_data: EDTLA=%d\n", event.length);
                edtla = 0;
            }
            xhci_event(xhci, &event, TRB_INTR(*trb));
            reported = 1;
            if (xfer->status != CC_SUCCESS) {
                return;
            }
        }

        switch (TRB_TYPE(*trb)) {
        case TR_SETUP:
            reported = 0;
            shortpkt = 0;
            break;
        }

    }
}