* Niels de Vos. David S. Ahern continued working on it. Kevin Wolf,
* Jan Kiszka and Vincent Palatin contributed bugfixes.
*
- *
* 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.
+ * version 2.1 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
+ * You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
#include "hw/usb/ehci-regs.h"
#include "hw/usb/hcd-ehci.h"
+#include "migration/vmstate.h"
#include "trace.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
#define FRAME_TIMER_FREQ 1000
#define FRAME_TIMER_NS (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ)
static void ehci_trace_guest_bug(EHCIState *s, const char *message)
{
trace_usb_ehci_guest_bug(message);
- fprintf(stderr, "ehci warning: %s\n", message);
+ warn_report("%s", message);
}
static inline bool ehci_enabled(EHCIState *s)
return s->caps[addr];
}
+static void ehci_caps_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+}
+
static uint64_t ehci_opreg_read(void *ptr, hwaddr addr,
unsigned size)
{
while (bytes > 0) {
if (cpage > 4) {
fprintf(stderr, "cpage out of range (%d)\n", cpage);
+ qemu_sglist_destroy(&p->sgl);
return -1;
}
{
USBDevice *dev;
USBEndpoint *ep;
- uint32_t i, len, pid, dir, devaddr, endp, xfers = 0;
+ uint32_t i, len, pid, dir, devaddr, endp;
uint32_t pg, off, ptr1, ptr2, max, mult;
ehci->periodic_sched_active = PERIODIC_ACTIVE;
if (off + len > 4096) {
/* transfer crosses page border */
if (pg == 6) {
+ qemu_sglist_destroy(&ehci->isgl);
return -1; /* avoid page pg + 1 */
}
ptr2 = (itd->bufptr[pg + 1] & ITD_BUFPTR_MASK);
qemu_sglist_add(&ehci->isgl, ptr1 + off, len);
}
- pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
-
dev = ehci_find_device(ehci, devaddr);
+ if (dev == NULL) {
+ ehci_trace_guest_bug(ehci, "no device found");
+ return -1;
+ }
+ pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
ep = usb_ep_get(dev, pid, endp);
if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) {
usb_packet_setup(&ehci->ipacket, pid, ep, 0, addr, false,
ehci_raise_irq(ehci, USBSTS_INT);
}
itd->transact[i] &= ~ITD_XACT_ACTIVE;
- xfers++;
}
}
- return xfers ? 0 : -1;
+ return 0;
}
ehci_set_state(ehci, async, EST_HORIZONTALQH);
} else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
- (NLPTR_TBIT(q->qh.current_qtd) == 0)) {
+ (NLPTR_TBIT(q->qh.current_qtd) == 0) &&
+ (q->qh.current_qtd != 0)) {
q->qtdaddr = q->qh.current_qtd;
ehci_set_state(ehci, async, EST_FETCHQTD);
/* siTD is not active, nothing to do */;
} else {
/* TODO: split transfers are not implemented */
- fprintf(stderr, "WARNING: Skipping active siTD\n");
+ warn_report("Skipping active siTD");
}
ehci_set_fetch_addr(ehci, async, sitd.next);
EHCIqtd qtd;
EHCIPacket *p;
int again = 1;
+ uint32_t addr;
- if (get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd,
- sizeof(EHCIqtd) >> 2) < 0) {
+ addr = NLPTR_GET(q->qtdaddr);
+ if (get_dwords(q->ehci, addr + 8, &qtd.token, 1) < 0) {
+ return 0;
+ }
+ barrier();
+ if (get_dwords(q->ehci, addr + 0, &qtd.next, 1) < 0 ||
+ get_dwords(q->ehci, addr + 4, &qtd.altnext, 1) < 0 ||
+ get_dwords(q->ehci, addr + 12, qtd.bufptr,
+ ARRAY_SIZE(qtd.bufptr)) < 0) {
return 0;
}
ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
break;
case EHCI_ASYNC_INFLIGHT:
/* Check if the guest has added new tds to the queue */
- again = ehci_fill_queue(QTAILQ_LAST(&q->packets, pkts_head));
+ again = ehci_fill_queue(QTAILQ_LAST(&q->packets));
/* Unfinished async handled packet, go horizontal */
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
break;
static void ehci_advance_state(EHCIState *ehci, int async)
{
EHCIQueue *q = NULL;
+ int itd_count = 0;
int again;
do {
case EST_FETCHITD:
again = ehci_state_fetchitd(ehci, async);
+ itd_count++;
break;
case EST_FETCHSITD:
again = ehci_state_fetchsitd(ehci, async);
+ itd_count++;
break;
case EST_ADVANCEQUEUE:
break;
}
- if (again < 0) {
+ if (again < 0 || itd_count > 16) {
+ /* TODO: notify guest (raise HSE irq?) */
fprintf(stderr, "processing error - resetting ehci HC\n");
ehci_reset(ehci);
again = 0;
static void ehci_update_frindex(EHCIState *ehci, int uframes)
{
- int i;
-
if (!ehci_enabled(ehci) && ehci->pstate == EST_INACTIVE) {
return;
}
- for (i = 0; i < uframes; i++) {
- ehci->frindex++;
-
- if (ehci->frindex == 0x00002000) {
- ehci_raise_irq(ehci, USBSTS_FLR);
- }
+ /* Generate FLR interrupt if frame index rolls over 0x2000 */
+ if ((ehci->frindex % 0x2000) + uframes >= 0x2000) {
+ ehci_raise_irq(ehci, USBSTS_FLR);
+ }
- if (ehci->frindex == 0x00004000) {
- ehci_raise_irq(ehci, USBSTS_FLR);
- ehci->frindex = 0;
- if (ehci->usbsts_frindex >= 0x00004000) {
- ehci->usbsts_frindex -= 0x00004000;
- } else {
- ehci->usbsts_frindex = 0;
- }
+ /* How many times will frindex roll over 0x4000 with this frame count?
+ * usbsts_frindex is decremented by 0x4000 on rollover until it reaches 0
+ */
+ int rollovers = (ehci->frindex + uframes) / 0x4000;
+ if (rollovers > 0) {
+ if (ehci->usbsts_frindex >= (rollovers * 0x4000)) {
+ ehci->usbsts_frindex -= 0x4000 * rollovers;
+ } else {
+ ehci->usbsts_frindex = 0;
}
}
+
+ ehci->frindex = (ehci->frindex + uframes) % 0x4000;
}
-static void ehci_frame_timer(void *opaque)
+static void ehci_work_bh(void *opaque)
{
EHCIState *ehci = opaque;
int need_timer = 0;
int64_t expire_time, t_now;
uint64_t ns_elapsed;
- int uframes, skipped_uframes;
+ uint64_t uframes, skipped_uframes;
int i;
+ if (ehci->working) {
+ return;
+ }
+ ehci->working = true;
+
t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
ns_elapsed = t_now - ehci->last_run_ns;
uframes = ns_elapsed / UFRAME_TIMER_NS;
/* If we've raised int, we speed up the timer, so that we quickly
* notice any new packets queued up in response */
if (ehci->int_req_by_async && (ehci->usbsts & USBSTS_INT)) {
- expire_time = t_now + get_ticks_per_sec() / (FRAME_TIMER_FREQ * 4);
+ expire_time = t_now +
+ NANOSECONDS_PER_SECOND / (FRAME_TIMER_FREQ * 4);
ehci->int_req_by_async = false;
} else {
- expire_time = t_now + (get_ticks_per_sec()
+ expire_time = t_now + (NANOSECONDS_PER_SECOND
* (ehci->async_stepdown+1) / FRAME_TIMER_FREQ);
}
timer_mod(ehci->frame_timer, expire_time);
}
+
+ ehci->working = false;
+}
+
+static void ehci_work_timer(void *opaque)
+{
+ EHCIState *ehci = opaque;
+
+ qemu_bh_schedule(ehci->async_bh);
}
static const MemoryRegionOps ehci_mmio_caps_ops = {
.read = ehci_caps_read,
+ .write = ehci_caps_write,
.valid.min_access_size = 1,
.valid.max_access_size = 4,
.impl.min_access_size = 1,
.wakeup_endpoint = ehci_wakeup_endpoint,
};
-static void usb_ehci_pre_save(void *opaque)
+static int usb_ehci_pre_save(void *opaque)
{
EHCIState *ehci = opaque;
uint32_t new_frindex;
new_frindex = ehci->frindex & ~7;
ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS;
ehci->frindex = new_frindex;
+
+ return 0;
}
static int usb_ehci_post_load(void *opaque, int version_id)
NB_PORTS);
return;
}
+ if (s->maxframes < 8 || s->maxframes > 512) {
+ error_setg(errp, "maxframes %d out if range (8 .. 512)",
+ s->maxframes);
+ return;
+ }
usb_bus_new(&s->bus, sizeof(s->bus), s->companion_enable ?
&ehci_bus_ops_companion : &ehci_bus_ops_standalone, dev);
s->ports[i].dev = 0;
}
- s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_frame_timer, s);
- s->async_bh = qemu_bh_new(ehci_frame_timer, s);
+ s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_work_timer, s);
+ s->async_bh = qemu_bh_new(ehci_work_bh, s);
s->device = dev;
s->vmstate = qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s);
&s->mem_ports);
}
+void usb_ehci_finalize(EHCIState *s)
+{
+ usb_packet_cleanup(&s->ipacket);
+}
+
/*
* vim: expandtab ts=4
*/