* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu/osdep.h"
#include "hw/hw.h"
#include "qemu/timer.h"
+#include "qemu/queue.h"
#include "hw/usb.h"
#include "hw/pci/pci.h"
#include "hw/pci/msi.h"
#include "hw/pci/msix.h"
#include "trace.h"
+#include "qapi/error.h"
//#define DEBUG_XHCI
//#define DEBUG_DATA
#define MAXSLOTS 64
#define MAXINTRS 16
-#define TD_QUEUE 24
-
/* Very pessimistic, let's hope it's enough for all cases */
-#define EV_QUEUE (((3*TD_QUEUE)+16)*MAXSLOTS)
+#define EV_QUEUE (((3 * 24) + 16) * MAXSLOTS)
/* Do not deliver ER Full events. NEC's driver does some things not bound
* to the specs when it gets them */
#define ER_FULL_HACK
+#define TRB_LINK_LIMIT 4
+
#define LEN_CAP 0x40
#define LEN_OPER (0x400 + 0x10 * MAXPORTS)
#define LEN_RUNTIME ((MAXINTRS + 1) * 0x20)
} XHCIPort;
typedef struct XHCITransfer {
- XHCIState *xhci;
+ XHCIEPContext *epctx;
USBPacket packet;
QEMUSGList sgl;
bool running_async;
bool complete;
bool int_req;
unsigned int iso_pkts;
- unsigned int slotid;
- unsigned int epid;
unsigned int streamid;
bool in_xfer;
bool iso_xfer;
bool timed_xfer;
unsigned int trb_count;
- unsigned int trb_alloced;
XHCITRB *trbs;
TRBCCode status;
unsigned int cur_pkt;
uint64_t mfindex_kick;
+
+ QTAILQ_ENTRY(XHCITransfer) next;
} XHCITransfer;
struct XHCIStreamContext {
unsigned int epid;
XHCIRing ring;
- unsigned int next_xfer;
- unsigned int comp_xfer;
- XHCITransfer transfers[TD_QUEUE];
+ uint32_t xfer_count;
+ QTAILQ_HEAD(, XHCITransfer) transfers;
XHCITransfer *retry;
EPType type;
dma_addr_t pctx;
uint32_t numslots;
uint32_t flags;
uint32_t max_pstreams_mask;
+ OnOffAuto msi;
+ OnOffAuto msix;
/* Operational Registers */
uint32_t usbcmd;
} XHCIEvRingSeg;
enum xhci_flags {
- XHCI_FLAG_USE_MSI = 1,
- XHCI_FLAG_USE_MSI_X,
- XHCI_FLAG_SS_FIRST,
+ XHCI_FLAG_SS_FIRST = 1,
XHCI_FLAG_FORCE_PCIE_ENDCAP,
XHCI_FLAG_ENABLE_STREAMS,
};
static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
unsigned int epid, unsigned int streamid);
+static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid);
static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid,
unsigned int epid);
static void xhci_xfer_report(XHCITransfer *xfer);
static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v);
static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v);
-static USBEndpoint *xhci_epid_to_usbep(XHCIState *xhci,
- unsigned int slotid, unsigned int epid);
+static USBEndpoint *xhci_epid_to_usbep(XHCIEPContext *epctx);
static const char *TRBType_names[] = {
[TRB_RESERVED] = "TRB_RESERVED",
uint32_t *buf, size_t len)
{
int i;
- uint32_t tmp[len / sizeof(uint32_t)];
+ uint32_t tmp[5];
+ uint32_t n = len / sizeof(uint32_t);
assert((len % sizeof(uint32_t)) == 0);
+ assert(n <= ARRAY_SIZE(tmp));
- for (i = 0; i < (len / sizeof(uint32_t)); i++) {
+ for (i = 0; i < n; i++) {
tmp[i] = cpu_to_le32(buf[i]);
}
pci_dma_write(PCI_DEVICE(xhci), addr, tmp, len);
dma_addr_t *addr)
{
PCIDevice *pci_dev = PCI_DEVICE(xhci);
+ uint32_t link_cnt = 0;
while (1) {
TRBType type;
ring->dequeue += TRB_SIZE;
return type;
} else {
+ if (++link_cnt > TRB_LINK_LIMIT) {
+ return 0;
+ }
ring->dequeue = xhci_mask64(trb->parameter);
if (trb->control & TRB_LK_TC) {
ring->ccs = !ring->ccs;
bool ccs = ring->ccs;
/* hack to bundle together the two/three TDs that make a setup transfer */
bool control_td_set = 0;
+ uint32_t link_cnt = 0;
while (1) {
TRBType type;
type = TRB_TYPE(trb);
if (type == TR_LINK) {
+ if (++link_cnt > TRB_LINK_LIMIT) {
+ return -length;
+ }
dequeue = xhci_mask64(trb.parameter);
if (trb.control & TRB_LK_TC) {
ccs = !ccs;
}
epctx = slot->eps[i - 1];
- ep = xhci_epid_to_usbep(xhci, slotid, i);
+ ep = xhci_epid_to_usbep(epctx);
if (!epctx || !epctx->nr_pstreams || !ep) {
continue;
}
static void xhci_ep_kick_timer(void *opaque)
{
XHCIEPContext *epctx = opaque;
- xhci_kick_ep(epctx->xhci, epctx->slotid, epctx->epid, 0);
+ xhci_kick_epctx(epctx, 0);
}
static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci,
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);
- }
+ QTAILQ_INIT(&epctx->transfers);
epctx->kick_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_ep_kick_timer, epctx);
return epctx;
return CC_SUCCESS;
}
+static XHCITransfer *xhci_ep_alloc_xfer(XHCIEPContext *epctx,
+ uint32_t length)
+{
+ uint32_t limit = epctx->nr_pstreams + 16;
+ XHCITransfer *xfer;
+
+ if (epctx->xfer_count >= limit) {
+ return NULL;
+ }
+
+ xfer = g_new0(XHCITransfer, 1);
+ xfer->epctx = epctx;
+ xfer->trbs = g_new(XHCITRB, length);
+ xfer->trb_count = length;
+ usb_packet_init(&xfer->packet);
+
+ QTAILQ_INSERT_TAIL(&epctx->transfers, xfer, next);
+ epctx->xfer_count++;
+
+ return xfer;
+}
+
+static void xhci_ep_free_xfer(XHCITransfer *xfer)
+{
+ QTAILQ_REMOVE(&xfer->epctx->transfers, xfer, next);
+ xfer->epctx->xfer_count--;
+
+ usb_packet_cleanup(&xfer->packet);
+ g_free(xfer->trbs);
+ g_free(xfer);
+}
+
static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report)
{
int killed = 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);
+ if (t->epctx) {
+ t->epctx->retry = NULL;
+ timer_del(t->epctx->kick_timer);
}
t->running_retry = 0;
killed = 1;
}
- if (t->trbs) {
- g_free(t->trbs);
- }
+ g_free(t->trbs);
t->trbs = NULL;
- t->trb_count = t->trb_alloced = 0;
+ t->trb_count = 0;
return killed;
}
{
XHCISlot *slot;
XHCIEPContext *epctx;
- int i, xferi, killed = 0;
+ XHCITransfer *xfer;
+ int killed = 0;
USBEndpoint *ep = NULL;
assert(slotid >= 1 && slotid <= xhci->numslots);
assert(epid >= 1 && epid <= 31);
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);
+ for (;;) {
+ xfer = QTAILQ_FIRST(&epctx->transfers);
+ if (xfer == NULL) {
+ break;
+ }
+ killed += xhci_ep_nuke_one_xfer(xfer, report);
if (killed) {
report = 0; /* Only report once */
}
- epctx->transfers[xferi].packet.ep = NULL;
- xferi = (xferi + 1) % TD_QUEUE;
+ xhci_ep_free_xfer(xfer);
}
- ep = xhci_epid_to_usbep(xhci, slotid, epid);
+ ep = xhci_epid_to_usbep(epctx);
if (ep) {
usb_device_ep_stopped(ep->dev, ep);
}
{
XHCISlot *slot;
XHCIEPContext *epctx;
- int i;
trace_usb_xhci_ep_disable(slotid, epid);
assert(slotid >= 1 && slotid <= xhci->numslots);
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);
}
- xhci_set_ep_state(xhci, epctx, NULL, EP_DISABLED);
-
timer_free(epctx->kick_timer);
g_free(epctx);
slot->eps[epid-1] = NULL;
static int xhci_xfer_create_sgl(XHCITransfer *xfer, int in_xfer)
{
- XHCIState *xhci = xfer->xhci;
+ XHCIState *xhci = xfer->epctx->xhci;
int i;
xfer->int_req = false;
bool reported = 0;
bool shortpkt = 0;
XHCIEvent event = {ER_TRANSFER, CC_SUCCESS};
- XHCIState *xhci = xfer->xhci;
+ XHCIState *xhci = xfer->epctx->xhci;
int i;
left = xfer->packet.actual_length;
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:
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.slotid = xfer->epctx->slotid;
+ event.epid = xfer->epctx->epid;
event.length = (trb->status & 0x1ffff) - chunk;
event.flags = 0;
event.ptr = trb->addr;
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];
+ XHCIEPContext *epctx = xfer->epctx;
+ XHCIState *xhci = epctx->xhci;
uint32_t err;
XHCIStreamContext *sctx;
static int xhci_setup_packet(XHCITransfer *xfer)
{
- XHCIState *xhci = xfer->xhci;
USBEndpoint *ep;
int dir;
if (xfer->packet.ep) {
ep = xfer->packet.ep;
} else {
- ep = xhci_epid_to_usbep(xhci, xfer->slotid, xfer->epid);
+ ep = xhci_epid_to_usbep(xfer->epctx);
if (!ep) {
DPRINTF("xhci: slot %d has no device\n",
xfer->slotid);
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);
+ trace_usb_xhci_xfer_start(xfer, xfer->epctx->slotid,
+ xfer->epctx->epid, xfer->streamid);
/* at most one Event Data TRB allowed after STATUS */
if (TRB_TYPE(*trb_status) == TR_EVDATA && xfer->trb_count > 2) {
xhci_complete_packet(xfer);
if (!xfer->running_async && !xfer->running_retry) {
- xhci_kick_ep(xhci, xfer->slotid, xfer->epid, 0);
+ xhci_kick_epctx(xfer->epctx, 0);
}
return 0;
}
xhci_complete_packet(xfer);
if (!xfer->running_async && !xfer->running_retry) {
- xhci_kick_ep(xhci, xfer->slotid, xfer->epid, xfer->streamid);
+ xhci_kick_epctx(xfer->epctx, xfer->streamid);
}
return 0;
}
static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx)
{
- trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->epid, xfer->streamid);
+ trace_usb_xhci_xfer_start(xfer, xfer->epctx->slotid,
+ xfer->epctx->epid, xfer->streamid);
return xhci_submit(xhci, xfer, epctx);
}
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);
return;
}
+ xhci_kick_epctx(epctx, streamid);
+}
+
+static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid)
+{
+ XHCIState *xhci = epctx->xhci;
+ XHCIStreamContext *stctx;
+ XHCITransfer *xfer;
+ XHCIRing *ring;
+ USBEndpoint *ep = NULL;
+ uint64_t mfindex;
+ int length;
+ int i;
+
+ trace_usb_xhci_ep_kick(epctx->slotid, epctx->epid, streamid);
+
/* 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) {
+ if (!xhci->slots[epctx->slotid - 1].uport ||
+ !xhci->slots[epctx->slotid - 1].uport->dev ||
+ !xhci->slots[epctx->slotid - 1].uport->dev->attached) {
return;
}
xhci_complete_packet(xfer);
}
assert(!xfer->running_retry);
+ xhci_ep_free_xfer(epctx->retry);
epctx->retry = NULL;
}
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) {
+ 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_malloc(sizeof(XHCITRB) * length);
- xfer->trb_alloced = length;
+ xfer = xhci_ep_alloc_xfer(epctx, length);
+ if (xfer == NULL) {
+ break;
}
- xfer->trb_count = length;
for (i = 0; i < length; i++) {
- assert(xhci_ring_fetch(xhci, ring, &xfer->trbs[i], NULL));
+ 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;
- ep = xfer->packet.ep;
- } else {
- DPRINTF("xhci: error firing CTL transfer\n");
- }
+ if (epctx->epid == 1) {
+ xhci_fire_ctl_transfer(xhci, xfer);
} 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");
- }
- }
+ xhci_fire_transfer(xhci, xfer, epctx);
+ }
+ if (xfer->complete) {
+ xhci_ep_free_xfer(xfer);
+ xfer = NULL;
}
if (epctx->state == EP_HALTED) {
break;
}
- if (xfer->running_retry) {
+ if (xfer != NULL && xfer->running_retry) {
DPRINTF("xhci: xfer nacked, stopping schedule\n");
epctx->retry = xfer;
- timer_mod(epctx->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
- epctx->interval * 125000);
break;
}
}
- ep = xhci_epid_to_usbep(xhci, slotid, epid);
+ ep = xhci_epid_to_usbep(epctx);
if (ep) {
usb_device_flush_ep_queue(ep->dev, ep);
}
slot->uport = uport;
slot->ctx = octx;
+ /* Make sure device is in USB_STATE_DEFAULT state */
+ usb_device_reset(dev);
if (bsr) {
slot_ctx[3] = SLOT_DEFAULT << SLOT_STATE_SHIFT;
} else {
uint8_t buf[1];
slot_ctx[3] = (SLOT_ADDRESSED << SLOT_STATE_SHIFT) | slotid;
- usb_device_reset(dev);
memset(&p, 0, sizeof(p));
usb_packet_addbuf(&p, buf, sizeof(buf));
usb_packet_setup(&p, USB_TOKEN_OUT,
return;
}
xhci_complete_packet(xfer);
- xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid, xfer->streamid);
+ xhci_kick_epctx(xfer->epctx, xfer->streamid);
+ if (xfer->complete) {
+ xhci_ep_free_xfer(xfer);
+ }
}
static void xhci_child_detach(USBPort *uport, USBDevice *child)
}
}
-static USBEndpoint *xhci_epid_to_usbep(XHCIState *xhci,
- unsigned int slotid, unsigned int epid)
+static USBEndpoint *xhci_epid_to_usbep(XHCIEPContext *epctx)
{
- assert(slotid >= 1 && slotid <= xhci->numslots);
+ USBPort *uport;
+ uint32_t token;
- if (!xhci->slots[slotid - 1].uport) {
+ if (!epctx) {
return NULL;
}
-
- return usb_ep_get(xhci->slots[slotid - 1].uport->dev,
- (epid & 1) ? USB_TOKEN_IN : USB_TOKEN_OUT, epid >> 1);
+ uport = epctx->xhci->slots[epctx->slotid - 1].uport;
+ token = (epctx->epid & 1) ? USB_TOKEN_IN : USB_TOKEN_OUT;
+ if (!uport) {
+ return NULL;
+ }
+ return usb_ep_get(uport->dev, token, epctx->epid >> 1);
}
static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep,
static void usb_xhci_realize(struct PCIDevice *dev, Error **errp)
{
int i, ret;
+ Error *err = NULL;
XHCIState *xhci = XHCI(dev);
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;
}
assert(ret >= 0);
}
- if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI)) {
- msi_init(dev, 0x70, xhci->numintrs, true, false);
- }
- if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI_X)) {
+ 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,
/* destroy msix memory region */
if (dev->msix_table && dev->msix_pba
&& dev->msix_entry_used) {
- memory_region_del_subregion(&xhci->mem, &dev->msix_table_mmio);
- memory_region_del_subregion(&xhci->mem, &dev->msix_pba_mmio);
+ msix_uninit(dev, &xhci->mem, &xhci->mem);
}
usb_bus_release(&xhci->bus);
.version_id = 1,
.post_load = usb_xhci_post_load,
.fields = (VMStateField[]) {
- VMSTATE_PCIE_DEVICE(parent_obj, XHCIState),
+ VMSTATE_PCI_DEVICE(parent_obj, XHCIState),
VMSTATE_MSIX(parent_obj, XHCIState),
VMSTATE_STRUCT_VARRAY_UINT32(ports, XHCIState, numports, 1,
};
static Property xhci_properties[] = {
- DEFINE_PROP_BIT("msi", XHCIState, flags, XHCI_FLAG_USE_MSI, true),
- DEFINE_PROP_BIT("msix", XHCIState, flags, XHCI_FLAG_USE_MSI_X, true),
+ DEFINE_PROP_ON_OFF_AUTO("msi", XHCIState, msi, ON_OFF_AUTO_AUTO),
+ DEFINE_PROP_ON_OFF_AUTO("msix", XHCIState, msix, ON_OFF_AUTO_AUTO),
DEFINE_PROP_BIT("superspeed-ports-first",
XHCIState, flags, XHCI_FLAG_SS_FIRST, true),
DEFINE_PROP_BIT("force-pcie-endcap", XHCIState, flags,