2010-12-03 18:17:28 +03:00
|
|
|
/*
|
|
|
|
* QEMU USB EHCI Emulation
|
|
|
|
*
|
|
|
|
* Copyright(c) 2008 Emutex Ltd. (address@hidden)
|
2012-08-28 18:21:12 +04:00
|
|
|
* Copyright(c) 2011-2012 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* Red Hat Authors:
|
|
|
|
* Gerd Hoffmann <kraxel@redhat.com>
|
|
|
|
* Hans de Goede <hdegoede@redhat.com>
|
2010-12-03 18:17:28 +03:00
|
|
|
*
|
|
|
|
* EHCI project was started by Mark Burkley, with contributions by
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
2014-05-08 14:15:23 +04:00
|
|
|
#include "hw/usb/ehci-regs.h"
|
2012-10-30 15:20:06 +04:00
|
|
|
#include "hw/usb/hcd-ehci.h"
|
2013-11-10 17:20:17 +04:00
|
|
|
#include "trace.h"
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
#define FRAME_TIMER_FREQ 1000
|
2011-05-31 14:23:13 +04:00
|
|
|
#define FRAME_TIMER_NS (1000000000 / FRAME_TIMER_FREQ)
|
2012-12-18 17:17:02 +04:00
|
|
|
#define UFRAME_TIMER_NS (FRAME_TIMER_NS / 8)
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
#define NB_MAXINTRATE 8 // Max rate at which controller issues ints
|
|
|
|
#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
|
|
|
|
#define MAX_QH 100 // Max allowable queue heads in a chain
|
2012-12-18 17:17:02 +04:00
|
|
|
#define MIN_UFR_PER_TICK 24 /* Min frames to process when catching up */
|
|
|
|
#define PERIODIC_ACTIVE 512 /* Micro-frames */
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/* Internal periodic / asynchronous schedule state machine states
|
|
|
|
*/
|
|
|
|
typedef enum {
|
|
|
|
EST_INACTIVE = 1000,
|
|
|
|
EST_ACTIVE,
|
|
|
|
EST_EXECUTING,
|
|
|
|
EST_SLEEPING,
|
|
|
|
/* The following states are internal to the state machine function
|
|
|
|
*/
|
|
|
|
EST_WAITLISTHEAD,
|
|
|
|
EST_FETCHENTRY,
|
|
|
|
EST_FETCHQH,
|
|
|
|
EST_FETCHITD,
|
2011-08-26 16:13:48 +04:00
|
|
|
EST_FETCHSITD,
|
2010-12-03 18:17:28 +03:00
|
|
|
EST_ADVANCEQUEUE,
|
|
|
|
EST_FETCHQTD,
|
|
|
|
EST_EXECUTE,
|
|
|
|
EST_WRITEBACK,
|
|
|
|
EST_HORIZONTALQH
|
|
|
|
} EHCI_STATES;
|
|
|
|
|
|
|
|
/* macros for accessing fields within next link pointer entry */
|
|
|
|
#define NLPTR_GET(x) ((x) & 0xffffffe0)
|
|
|
|
#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
|
|
|
|
#define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid
|
|
|
|
|
|
|
|
/* link pointer types */
|
|
|
|
#define NLPTR_TYPE_ITD 0 // isoc xfer descriptor
|
|
|
|
#define NLPTR_TYPE_QH 1 // queue head
|
|
|
|
#define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor
|
|
|
|
#define NLPTR_TYPE_FSTN 3 // frame span traversal node
|
|
|
|
|
|
|
|
#define SET_LAST_RUN_CLOCK(s) \
|
2013-08-21 19:03:08 +04:00
|
|
|
(s)->last_run_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/* nifty macros from Arnon's EHCI version */
|
|
|
|
#define get_field(data, field) \
|
|
|
|
(((data) & field##_MASK) >> field##_SH)
|
|
|
|
|
|
|
|
#define set_field(data, newval, field) do { \
|
|
|
|
uint32_t val = *data; \
|
|
|
|
val &= ~ field##_MASK; \
|
|
|
|
val |= ((newval) << field##_SH) & field##_MASK; \
|
|
|
|
*data = val; \
|
|
|
|
} while(0)
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static const char *ehci_state_names[] = {
|
2011-11-18 13:48:47 +04:00
|
|
|
[EST_INACTIVE] = "INACTIVE",
|
|
|
|
[EST_ACTIVE] = "ACTIVE",
|
|
|
|
[EST_EXECUTING] = "EXECUTING",
|
|
|
|
[EST_SLEEPING] = "SLEEPING",
|
|
|
|
[EST_WAITLISTHEAD] = "WAITLISTHEAD",
|
|
|
|
[EST_FETCHENTRY] = "FETCH ENTRY",
|
|
|
|
[EST_FETCHQH] = "FETCH QH",
|
|
|
|
[EST_FETCHITD] = "FETCH ITD",
|
|
|
|
[EST_ADVANCEQUEUE] = "ADVANCEQUEUE",
|
|
|
|
[EST_FETCHQTD] = "FETCH QTD",
|
|
|
|
[EST_EXECUTE] = "EXECUTE",
|
|
|
|
[EST_WRITEBACK] = "WRITEBACK",
|
|
|
|
[EST_HORIZONTALQH] = "HORIZONTALQH",
|
2011-05-18 16:23:35 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
static const char *ehci_mmio_names[] = {
|
2011-11-18 13:48:47 +04:00
|
|
|
[USBCMD] = "USBCMD",
|
|
|
|
[USBSTS] = "USBSTS",
|
|
|
|
[USBINTR] = "USBINTR",
|
|
|
|
[FRINDEX] = "FRINDEX",
|
|
|
|
[PERIODICLISTBASE] = "P-LIST BASE",
|
|
|
|
[ASYNCLISTADDR] = "A-LIST ADDR",
|
|
|
|
[CONFIGFLAG] = "CONFIGFLAG",
|
2011-05-18 16:23:35 +04:00
|
|
|
};
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-08-30 17:18:24 +04:00
|
|
|
static int ehci_state_executing(EHCIQueue *q);
|
|
|
|
static int ehci_state_writeback(EHCIQueue *q);
|
2012-11-14 20:21:37 +04:00
|
|
|
static int ehci_state_advqueue(EHCIQueue *q);
|
2012-10-24 20:14:04 +04:00
|
|
|
static int ehci_fill_queue(EHCIPacket *p);
|
2012-12-14 17:35:26 +04:00
|
|
|
static void ehci_free_packet(EHCIPacket *p);
|
2012-08-30 17:18:24 +04:00
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static const char *nr2str(const char **n, size_t len, uint32_t nr)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-18 16:23:35 +04:00
|
|
|
if (nr < len && n[nr] != NULL) {
|
|
|
|
return n[nr];
|
2010-12-03 18:17:28 +03:00
|
|
|
} else {
|
2011-05-18 16:23:35 +04:00
|
|
|
return "unknown";
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static const char *state2str(uint32_t state)
|
|
|
|
{
|
|
|
|
return nr2str(ehci_state_names, ARRAY_SIZE(ehci_state_names), state);
|
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static const char *addr2str(hwaddr addr)
|
2011-05-18 16:23:35 +04:00
|
|
|
{
|
2012-10-29 05:34:34 +04:00
|
|
|
return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr);
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
|
2011-05-18 12:12:58 +04:00
|
|
|
static void ehci_trace_usbsts(uint32_t mask, int state)
|
|
|
|
{
|
|
|
|
/* interrupts */
|
|
|
|
if (mask & USBSTS_INT) {
|
|
|
|
trace_usb_ehci_usbsts("INT", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_ERRINT) {
|
|
|
|
trace_usb_ehci_usbsts("ERRINT", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_PCD) {
|
|
|
|
trace_usb_ehci_usbsts("PCD", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_FLR) {
|
|
|
|
trace_usb_ehci_usbsts("FLR", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_HSE) {
|
|
|
|
trace_usb_ehci_usbsts("HSE", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_IAA) {
|
|
|
|
trace_usb_ehci_usbsts("IAA", state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* status */
|
|
|
|
if (mask & USBSTS_HALT) {
|
|
|
|
trace_usb_ehci_usbsts("HALT", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_REC) {
|
|
|
|
trace_usb_ehci_usbsts("REC", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_PSS) {
|
|
|
|
trace_usb_ehci_usbsts("PSS", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_ASS) {
|
|
|
|
trace_usb_ehci_usbsts("ASS", state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void ehci_set_usbsts(EHCIState *s, int mask)
|
|
|
|
{
|
|
|
|
if ((s->usbsts & mask) == mask) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ehci_trace_usbsts(mask, 1);
|
|
|
|
s->usbsts |= mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void ehci_clear_usbsts(EHCIState *s, int mask)
|
|
|
|
{
|
|
|
|
if ((s->usbsts & mask) == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ehci_trace_usbsts(mask, 0);
|
|
|
|
s->usbsts &= ~mask;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
/* update irq line */
|
|
|
|
static inline void ehci_update_irq(EHCIState *s)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int level = 0;
|
|
|
|
|
|
|
|
if ((s->usbsts & USBINTR_MASK) & s->usbintr) {
|
|
|
|
level = 1;
|
|
|
|
}
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
trace_usb_ehci_irq(level, s->frindex, s->usbsts, s->usbintr);
|
2010-12-03 18:17:28 +03:00
|
|
|
qemu_set_irq(s->irq, level);
|
|
|
|
}
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
/* flag interrupt condition */
|
|
|
|
static inline void ehci_raise_irq(EHCIState *s, int intr)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-08-15 15:55:40 +04:00
|
|
|
if (intr & (USBSTS_PCD | USBSTS_FLR | USBSTS_HSE)) {
|
|
|
|
s->usbsts |= intr;
|
|
|
|
ehci_update_irq(s);
|
|
|
|
} else {
|
|
|
|
s->usbsts_pending |= intr;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
/*
|
|
|
|
* Commit pending interrupts (added via ehci_raise_irq),
|
|
|
|
* at the rate allowed by "Interrupt Threshold Control".
|
|
|
|
*/
|
|
|
|
static inline void ehci_commit_irq(EHCIState *s)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-07-11 13:06:05 +04:00
|
|
|
uint32_t itc;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
if (!s->usbsts_pending) {
|
|
|
|
return;
|
|
|
|
}
|
2012-07-11 13:06:05 +04:00
|
|
|
if (s->usbsts_frindex > s->frindex) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
itc = (s->usbcmd >> 16) & 0xff;
|
|
|
|
s->usbsts |= s->usbsts_pending;
|
2010-12-03 18:17:28 +03:00
|
|
|
s->usbsts_pending = 0;
|
2012-07-11 13:06:05 +04:00
|
|
|
s->usbsts_frindex = s->frindex + itc;
|
|
|
|
ehci_update_irq(s);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-24 14:53:43 +04:00
|
|
|
static void ehci_update_halt(EHCIState *s)
|
|
|
|
{
|
|
|
|
if (s->usbcmd & USBCMD_RUNSTOP) {
|
|
|
|
ehci_clear_usbsts(s, USBSTS_HALT);
|
|
|
|
} else {
|
|
|
|
if (s->astate == EST_INACTIVE && s->pstate == EST_INACTIVE) {
|
|
|
|
ehci_set_usbsts(s, USBSTS_HALT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static void ehci_set_state(EHCIState *s, int async, int state)
|
|
|
|
{
|
|
|
|
if (async) {
|
|
|
|
trace_usb_ehci_state("async", state2str(state));
|
|
|
|
s->astate = state;
|
2012-05-24 14:34:18 +04:00
|
|
|
if (s->astate == EST_INACTIVE) {
|
|
|
|
ehci_clear_usbsts(s, USBSTS_ASS);
|
2012-05-24 14:53:43 +04:00
|
|
|
ehci_update_halt(s);
|
2012-05-24 14:34:18 +04:00
|
|
|
} else {
|
|
|
|
ehci_set_usbsts(s, USBSTS_ASS);
|
|
|
|
}
|
2011-05-18 16:23:35 +04:00
|
|
|
} else {
|
|
|
|
trace_usb_ehci_state("periodic", state2str(state));
|
|
|
|
s->pstate = state;
|
2012-05-24 14:34:18 +04:00
|
|
|
if (s->pstate == EST_INACTIVE) {
|
|
|
|
ehci_clear_usbsts(s, USBSTS_PSS);
|
2012-05-24 14:53:43 +04:00
|
|
|
ehci_update_halt(s);
|
2012-05-24 14:34:18 +04:00
|
|
|
} else {
|
|
|
|
ehci_set_usbsts(s, USBSTS_PSS);
|
|
|
|
}
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_get_state(EHCIState *s, int async)
|
|
|
|
{
|
|
|
|
return async ? s->astate : s->pstate;
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
|
|
|
|
{
|
|
|
|
if (async) {
|
|
|
|
s->a_fetch_addr = addr;
|
|
|
|
} else {
|
|
|
|
s->p_fetch_addr = addr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_get_fetch_addr(EHCIState *s, int async)
|
|
|
|
{
|
|
|
|
return async ? s->a_fetch_addr : s->p_fetch_addr;
|
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static void ehci_trace_qh(EHCIQueue *q, hwaddr addr, EHCIqh *qh)
|
2011-05-18 16:23:35 +04:00
|
|
|
{
|
2011-06-06 14:31:34 +04:00
|
|
|
/* need three here due to argument count limits */
|
|
|
|
trace_usb_ehci_qh_ptrs(q, addr, qh->next,
|
|
|
|
qh->current_qtd, qh->next_qtd, qh->altnext_qtd);
|
|
|
|
trace_usb_ehci_qh_fields(addr,
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_RL),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_MPLEN),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_EPS),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_EP),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_DEVADDR));
|
|
|
|
trace_usb_ehci_qh_bits(addr,
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_C),
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_H),
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_DTC),
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_I));
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static void ehci_trace_qtd(EHCIQueue *q, hwaddr addr, EHCIqtd *qtd)
|
2011-05-18 16:23:35 +04:00
|
|
|
{
|
2011-06-06 14:31:34 +04:00
|
|
|
/* need three here due to argument count limits */
|
|
|
|
trace_usb_ehci_qtd_ptrs(q, addr, qtd->next, qtd->altnext);
|
|
|
|
trace_usb_ehci_qtd_fields(addr,
|
|
|
|
get_field(qtd->token, QTD_TOKEN_TBYTES),
|
|
|
|
get_field(qtd->token, QTD_TOKEN_CPAGE),
|
|
|
|
get_field(qtd->token, QTD_TOKEN_CERR),
|
|
|
|
get_field(qtd->token, QTD_TOKEN_PID));
|
|
|
|
trace_usb_ehci_qtd_bits(addr,
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_IOC),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_ACTIVE),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_HALT),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_BABBLE),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_XACTERR));
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static void ehci_trace_itd(EHCIState *s, hwaddr addr, EHCIitd *itd)
|
2011-05-18 16:23:35 +04:00
|
|
|
{
|
2011-05-30 18:09:08 +04:00
|
|
|
trace_usb_ehci_itd(addr, itd->next,
|
|
|
|
get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT),
|
|
|
|
get_field(itd->bufptr[2], ITD_BUFPTR_MULT),
|
|
|
|
get_field(itd->bufptr[0], ITD_BUFPTR_EP),
|
|
|
|
get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR));
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static void ehci_trace_sitd(EHCIState *s, hwaddr addr,
|
2011-08-26 16:13:48 +04:00
|
|
|
EHCIsitd *sitd)
|
|
|
|
{
|
|
|
|
trace_usb_ehci_sitd(addr, sitd->next,
|
|
|
|
(bool)(sitd->results & SITD_RESULTS_ACTIVE));
|
|
|
|
}
|
|
|
|
|
2012-08-31 12:44:21 +04:00
|
|
|
static void ehci_trace_guest_bug(EHCIState *s, const char *message)
|
|
|
|
{
|
|
|
|
trace_usb_ehci_guest_bug(message);
|
|
|
|
fprintf(stderr, "ehci warning: %s\n", message);
|
|
|
|
}
|
|
|
|
|
2012-05-24 14:31:34 +04:00
|
|
|
static inline bool ehci_enabled(EHCIState *s)
|
|
|
|
{
|
|
|
|
return s->usbcmd & USBCMD_RUNSTOP;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool ehci_async_enabled(EHCIState *s)
|
|
|
|
{
|
|
|
|
return ehci_enabled(s) && (s->usbcmd & USBCMD_ASE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool ehci_periodic_enabled(EHCIState *s)
|
|
|
|
{
|
|
|
|
return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE);
|
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:25 +04:00
|
|
|
/* Get an array of dwords from main memory */
|
|
|
|
static inline int get_dwords(EHCIState *ehci, uint32_t addr,
|
|
|
|
uint32_t *buf, int num)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2013-04-10 20:15:49 +04:00
|
|
|
if (!ehci->as) {
|
2012-12-14 17:35:25 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_HSE);
|
|
|
|
ehci->usbcmd &= ~USBCMD_RUNSTOP;
|
|
|
|
trace_usb_ehci_dma_error();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
2013-04-10 20:15:49 +04:00
|
|
|
dma_memory_read(ehci->as, addr, buf, sizeof(*buf));
|
2012-12-14 17:35:25 +04:00
|
|
|
*buf = le32_to_cpu(*buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Put an array of dwords in to main memory */
|
|
|
|
static inline int put_dwords(EHCIState *ehci, uint32_t addr,
|
|
|
|
uint32_t *buf, int num)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2013-04-10 20:15:49 +04:00
|
|
|
if (!ehci->as) {
|
2012-12-14 17:35:25 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_HSE);
|
|
|
|
ehci->usbcmd &= ~USBCMD_RUNSTOP;
|
|
|
|
trace_usb_ehci_dma_error();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
|
|
|
uint32_t tmp = cpu_to_le32(*buf);
|
2013-04-10 20:15:49 +04:00
|
|
|
dma_memory_write(ehci->as, addr, &tmp, sizeof(tmp));
|
2012-12-14 17:35:25 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:28 +04:00
|
|
|
static int ehci_get_pid(EHCIqtd *qtd)
|
|
|
|
{
|
|
|
|
switch (get_field(qtd->token, QTD_TOKEN_PID)) {
|
|
|
|
case 0:
|
|
|
|
return USB_TOKEN_OUT;
|
|
|
|
case 1:
|
|
|
|
return USB_TOKEN_IN;
|
|
|
|
case 2:
|
|
|
|
return USB_TOKEN_SETUP;
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "bad token\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:23 +04:00
|
|
|
static bool ehci_verify_qh(EHCIQueue *q, EHCIqh *qh)
|
|
|
|
{
|
|
|
|
uint32_t devaddr = get_field(qh->epchar, QH_EPCHAR_DEVADDR);
|
|
|
|
uint32_t endp = get_field(qh->epchar, QH_EPCHAR_EP);
|
|
|
|
if ((devaddr != get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)) ||
|
|
|
|
(endp != get_field(q->qh.epchar, QH_EPCHAR_EP)) ||
|
|
|
|
(qh->current_qtd != q->qh.current_qtd) ||
|
|
|
|
(q->async && qh->next_qtd != q->qh.next_qtd) ||
|
|
|
|
(memcmp(&qh->altnext_qtd, &q->qh.altnext_qtd,
|
|
|
|
7 * sizeof(uint32_t)) != 0) ||
|
|
|
|
(q->dev != NULL && q->dev->addr != devaddr)) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool ehci_verify_qtd(EHCIPacket *p, EHCIqtd *qtd)
|
|
|
|
{
|
|
|
|
if (p->qtdaddr != p->queue->qtdaddr ||
|
|
|
|
(p->queue->async && !NLPTR_TBIT(p->qtd.next) &&
|
|
|
|
(p->qtd.next != qtd->next)) ||
|
|
|
|
(!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd->altnext)) ||
|
2012-12-14 17:35:24 +04:00
|
|
|
p->qtd.token != qtd->token ||
|
2012-12-14 17:35:23 +04:00
|
|
|
p->qtd.bufptr[0] != qtd->bufptr[0]) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:29 +04:00
|
|
|
static bool ehci_verify_pid(EHCIQueue *q, EHCIqtd *qtd)
|
|
|
|
{
|
|
|
|
int ep = get_field(q->qh.epchar, QH_EPCHAR_EP);
|
|
|
|
int pid = ehci_get_pid(qtd);
|
|
|
|
|
|
|
|
/* Note the pid changing is normal for ep 0 (the control ep) */
|
|
|
|
if (q->last_pid && ep != 0 && pid != q->last_pid) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:22 +04:00
|
|
|
/* Finish executing and writeback a packet outside of the regular
|
|
|
|
fetchqh -> fetchqtd -> execute -> writeback cycle */
|
|
|
|
static void ehci_writeback_async_complete_packet(EHCIPacket *p)
|
|
|
|
{
|
|
|
|
EHCIQueue *q = p->queue;
|
2012-12-14 17:35:26 +04:00
|
|
|
EHCIqtd qtd;
|
|
|
|
EHCIqh qh;
|
2012-12-14 17:35:22 +04:00
|
|
|
int state;
|
|
|
|
|
2012-12-14 17:35:26 +04:00
|
|
|
/* Verify the qh + qtd, like we do when going through fetchqh & fetchqtd */
|
|
|
|
get_dwords(q->ehci, NLPTR_GET(q->qhaddr),
|
|
|
|
(uint32_t *) &qh, sizeof(EHCIqh) >> 2);
|
|
|
|
get_dwords(q->ehci, NLPTR_GET(q->qtdaddr),
|
|
|
|
(uint32_t *) &qtd, sizeof(EHCIqtd) >> 2);
|
|
|
|
if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) {
|
|
|
|
p->async = EHCI_ASYNC_INITIALIZED;
|
|
|
|
ehci_free_packet(p);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:22 +04:00
|
|
|
state = ehci_get_state(q->ehci, q->async);
|
|
|
|
ehci_state_executing(q);
|
|
|
|
ehci_state_writeback(q); /* Frees the packet! */
|
|
|
|
if (!(q->qh.token & QTD_TOKEN_HALT)) {
|
|
|
|
ehci_state_advqueue(q);
|
|
|
|
}
|
|
|
|
ehci_set_state(q->ehci, q->async, state);
|
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
/* packet management */
|
|
|
|
|
|
|
|
static EHCIPacket *ehci_alloc_packet(EHCIQueue *q)
|
|
|
|
{
|
|
|
|
EHCIPacket *p;
|
|
|
|
|
|
|
|
p = g_new0(EHCIPacket, 1);
|
|
|
|
p->queue = q;
|
|
|
|
usb_packet_init(&p->packet);
|
|
|
|
QTAILQ_INSERT_TAIL(&q->packets, p, next);
|
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "alloc");
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_free_packet(EHCIPacket *p)
|
|
|
|
{
|
2013-04-09 12:24:22 +04:00
|
|
|
if (p->async == EHCI_ASYNC_FINISHED &&
|
|
|
|
!(p->queue->qh.token & QTD_TOKEN_HALT)) {
|
2012-12-14 17:35:22 +04:00
|
|
|
ehci_writeback_async_complete_packet(p);
|
2012-08-30 17:18:24 +04:00
|
|
|
return;
|
|
|
|
}
|
2012-08-31 12:31:54 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "free");
|
|
|
|
if (p->async == EHCI_ASYNC_INFLIGHT) {
|
|
|
|
usb_cancel_packet(&p->packet);
|
2013-04-09 12:24:22 +04:00
|
|
|
}
|
|
|
|
if (p->async == EHCI_ASYNC_FINISHED &&
|
|
|
|
p->packet.status == USB_RET_SUCCESS) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"EHCI: Dropping completed packet from halted %s ep %02X\n",
|
|
|
|
(p->pid == USB_TOKEN_IN) ? "in" : "out",
|
|
|
|
get_field(p->queue->qh.epchar, QH_EPCHAR_EP));
|
|
|
|
}
|
|
|
|
if (p->async != EHCI_ASYNC_NONE) {
|
2012-08-31 12:31:54 +04:00
|
|
|
usb_packet_unmap(&p->packet, &p->sgl);
|
|
|
|
qemu_sglist_destroy(&p->sgl);
|
|
|
|
}
|
2012-05-09 19:06:36 +04:00
|
|
|
QTAILQ_REMOVE(&p->queue->packets, p, next);
|
|
|
|
usb_packet_cleanup(&p->packet);
|
|
|
|
g_free(p);
|
|
|
|
}
|
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
/* queue management */
|
|
|
|
|
2012-05-11 10:56:49 +04:00
|
|
|
static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q;
|
|
|
|
|
2011-08-21 07:09:37 +04:00
|
|
|
q = g_malloc0(sizeof(*q));
|
2011-05-19 19:56:19 +04:00
|
|
|
q->ehci = ehci;
|
2012-05-11 10:56:49 +04:00
|
|
|
q->qhaddr = addr;
|
2012-05-11 11:05:15 +04:00
|
|
|
q->async = async;
|
2012-05-09 19:06:36 +04:00
|
|
|
QTAILQ_INIT(&q->packets);
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_INSERT_HEAD(head, q, next);
|
2011-05-19 19:56:19 +04:00
|
|
|
trace_usb_ehci_queue_action(q, "alloc");
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:40 +04:00
|
|
|
static void ehci_queue_stopped(EHCIQueue *q)
|
|
|
|
{
|
|
|
|
int endp = get_field(q->qh.epchar, QH_EPCHAR_EP);
|
|
|
|
|
|
|
|
if (!q->last_pid || !q->dev) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
usb_device_ep_stopped(q->dev, usb_ep_get(q->dev, q->last_pid, endp));
|
|
|
|
}
|
|
|
|
|
2012-08-31 12:44:21 +04:00
|
|
|
static int ehci_cancel_queue(EHCIQueue *q)
|
2012-08-21 15:58:40 +04:00
|
|
|
{
|
|
|
|
EHCIPacket *p;
|
2012-08-31 12:44:21 +04:00
|
|
|
int packets = 0;
|
2012-08-21 15:58:40 +04:00
|
|
|
|
|
|
|
p = QTAILQ_FIRST(&q->packets);
|
|
|
|
if (p == NULL) {
|
2012-12-14 17:35:40 +04:00
|
|
|
goto leave;
|
2012-08-21 15:58:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
trace_usb_ehci_queue_action(q, "cancel");
|
|
|
|
do {
|
|
|
|
ehci_free_packet(p);
|
2012-08-31 12:44:21 +04:00
|
|
|
packets++;
|
2012-08-21 15:58:40 +04:00
|
|
|
} while ((p = QTAILQ_FIRST(&q->packets)) != NULL);
|
2012-12-14 17:35:40 +04:00
|
|
|
|
|
|
|
leave:
|
|
|
|
ehci_queue_stopped(q);
|
2012-08-31 12:44:21 +04:00
|
|
|
return packets;
|
2012-08-21 15:58:40 +04:00
|
|
|
}
|
|
|
|
|
2012-08-31 12:44:21 +04:00
|
|
|
static int ehci_reset_queue(EHCIQueue *q)
|
2012-08-29 12:37:37 +04:00
|
|
|
{
|
2012-08-31 12:44:21 +04:00
|
|
|
int packets;
|
|
|
|
|
2012-08-29 12:37:37 +04:00
|
|
|
trace_usb_ehci_queue_action(q, "reset");
|
2012-08-31 12:44:21 +04:00
|
|
|
packets = ehci_cancel_queue(q);
|
2012-08-29 12:37:37 +04:00
|
|
|
q->dev = NULL;
|
|
|
|
q->qtdaddr = 0;
|
2012-12-14 17:35:29 +04:00
|
|
|
q->last_pid = 0;
|
2012-08-31 12:44:21 +04:00
|
|
|
return packets;
|
2012-08-29 12:37:37 +04:00
|
|
|
}
|
|
|
|
|
2012-09-03 12:22:16 +04:00
|
|
|
static void ehci_free_queue(EHCIQueue *q, const char *warn)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-05-11 11:05:15 +04:00
|
|
|
EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues;
|
2012-09-03 12:22:16 +04:00
|
|
|
int cancelled;
|
2012-05-09 19:06:36 +04:00
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
trace_usb_ehci_queue_action(q, "free");
|
2012-09-03 12:22:16 +04:00
|
|
|
cancelled = ehci_cancel_queue(q);
|
|
|
|
if (warn && cancelled > 0) {
|
|
|
|
ehci_trace_guest_bug(q->ehci, warn);
|
|
|
|
}
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_REMOVE(head, q, next);
|
2011-08-21 07:09:37 +04:00
|
|
|
g_free(q);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
|
|
|
|
int async)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH(q, head, next) {
|
2011-05-19 19:56:19 +04:00
|
|
|
if (addr == q->qhaddr) {
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-09-12 17:08:32 +04:00
|
|
|
static void ehci_queues_rip_unused(EHCIState *ehci, int async)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2012-09-12 17:08:32 +04:00
|
|
|
const char *warn = async ? "guest unlinked busy QH" : NULL;
|
2012-05-24 15:34:02 +04:00
|
|
|
uint64_t maxage = FRAME_TIMER_NS * ehci->maxframes * 4;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q, *tmp;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
|
2011-05-19 19:56:19 +04:00
|
|
|
if (q->seen) {
|
|
|
|
q->seen = 0;
|
2011-05-31 14:23:13 +04:00
|
|
|
q->ts = ehci->last_run_ns;
|
2011-05-19 19:56:19 +04:00
|
|
|
continue;
|
|
|
|
}
|
2012-09-12 17:08:32 +04:00
|
|
|
if (ehci->last_run_ns < q->ts + maxage) {
|
2011-05-19 19:56:19 +04:00
|
|
|
continue;
|
|
|
|
}
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_free_queue(q, warn);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-12 17:08:32 +04:00
|
|
|
static void ehci_queues_rip_unseen(EHCIState *ehci, int async)
|
|
|
|
{
|
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
|
|
|
EHCIQueue *q, *tmp;
|
|
|
|
|
|
|
|
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
|
|
|
|
if (!q->seen) {
|
|
|
|
ehci_free_queue(q, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async)
|
2011-05-23 19:37:12 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2011-05-23 19:37:12 +04:00
|
|
|
EHCIQueue *q, *tmp;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
|
2012-05-10 14:18:45 +04:00
|
|
|
if (q->dev != dev) {
|
2011-05-23 19:37:12 +04:00
|
|
|
continue;
|
|
|
|
}
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_free_queue(q, NULL);
|
2011-05-23 19:37:12 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
static void ehci_queues_rip_all(EHCIState *ehci, int async)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2012-09-03 12:22:16 +04:00
|
|
|
const char *warn = async ? "guest stopped busy async schedule" : NULL;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q, *tmp;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_free_queue(q, warn);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* Attach or detach a device on root hub */
|
|
|
|
|
|
|
|
static void ehci_attach(USBPort *port)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
|
|
|
uint32_t *portsc = &s->portsc[port->index];
|
2012-06-08 15:00:44 +04:00
|
|
|
const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-06-08 15:00:44 +04:00
|
|
|
trace_usb_ehci_port_attach(port->index, owner, port->dev->product_desc);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
if (*portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->dev = port->dev;
|
|
|
|
companion->ops->attach(companion);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
*portsc |= PORTSC_CONNECT;
|
|
|
|
*portsc |= PORTSC_CSC;
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(s, USBSTS_PCD);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_detach(USBPort *port)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
|
|
|
uint32_t *portsc = &s->portsc[port->index];
|
2012-06-08 15:00:44 +04:00
|
|
|
const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-06-08 15:00:44 +04:00
|
|
|
trace_usb_ehci_port_detach(port->index, owner);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
if (*portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->ops->detach(companion);
|
|
|
|
companion->dev = NULL;
|
2012-01-13 17:28:56 +04:00
|
|
|
/*
|
|
|
|
* EHCI spec 4.2.2: "When a disconnect occurs... On the event,
|
|
|
|
* the port ownership is returned immediately to the EHCI controller."
|
|
|
|
*/
|
|
|
|
*portsc &= ~PORTSC_POWNER;
|
2011-06-24 18:18:13 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
ehci_queues_rip_device(s, port->dev, 0);
|
|
|
|
ehci_queues_rip_device(s, port->dev, 1);
|
2011-06-24 14:31:11 +04:00
|
|
|
|
2011-06-24 16:36:13 +04:00
|
|
|
*portsc &= ~(PORTSC_CONNECT|PORTSC_PED);
|
2010-12-03 18:17:28 +03:00
|
|
|
*portsc |= PORTSC_CSC;
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(s, USBSTS_PCD);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-06-24 14:31:11 +04:00
|
|
|
static void ehci_child_detach(USBPort *port, USBDevice *child)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
2011-06-24 18:18:13 +04:00
|
|
|
uint32_t portsc = s->portsc[port->index];
|
|
|
|
|
|
|
|
if (portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->ops->child_detach(companion, child);
|
|
|
|
return;
|
|
|
|
}
|
2011-06-24 14:31:11 +04:00
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
ehci_queues_rip_device(s, child, 0);
|
|
|
|
ehci_queues_rip_device(s, child, 1);
|
2011-06-24 14:31:11 +04:00
|
|
|
}
|
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
static void ehci_wakeup(USBPort *port)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
2013-11-20 17:10:19 +04:00
|
|
|
uint32_t *portsc = &s->portsc[port->index];
|
2011-06-24 18:18:13 +04:00
|
|
|
|
2013-11-20 17:10:19 +04:00
|
|
|
if (*portsc & PORTSC_POWNER) {
|
2011-06-24 18:18:13 +04:00
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
if (companion->ops->wakeup) {
|
|
|
|
companion->ops->wakeup(companion);
|
|
|
|
}
|
2012-07-06 18:53:39 +04:00
|
|
|
return;
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
2012-07-06 18:53:39 +04:00
|
|
|
|
2013-11-20 17:10:19 +04:00
|
|
|
if (*portsc & PORTSC_SUSPEND) {
|
|
|
|
trace_usb_ehci_port_wakeup(port->index);
|
|
|
|
*portsc |= PORTSC_FPRES;
|
|
|
|
ehci_raise_irq(s, USBSTS_PCD);
|
|
|
|
}
|
|
|
|
|
2012-07-06 18:53:39 +04:00
|
|
|
qemu_bh_schedule(s->async_bh);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_register_companion(USBBus *bus, USBPort *ports[],
|
|
|
|
uint32_t portcount, uint32_t firstport)
|
|
|
|
{
|
|
|
|
EHCIState *s = container_of(bus, EHCIState, bus);
|
|
|
|
uint32_t i;
|
|
|
|
|
|
|
|
if (firstport + portcount > NB_PORTS) {
|
|
|
|
qerror_report(QERR_INVALID_PARAMETER_VALUE, "firstport",
|
|
|
|
"firstport on masterbus");
|
|
|
|
error_printf_unless_qmp(
|
|
|
|
"firstport value of %u makes companion take ports %u - %u, which "
|
|
|
|
"is outside of the valid range of 0 - %u\n", firstport, firstport,
|
|
|
|
firstport + portcount - 1, NB_PORTS - 1);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < portcount; i++) {
|
|
|
|
if (s->companion_ports[firstport + i]) {
|
|
|
|
qerror_report(QERR_INVALID_PARAMETER_VALUE, "masterbus",
|
|
|
|
"an USB masterbus");
|
|
|
|
error_printf_unless_qmp(
|
|
|
|
"port %u on masterbus %s already has a companion assigned\n",
|
|
|
|
firstport + i, bus->qbus.name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < portcount; i++) {
|
|
|
|
s->companion_ports[firstport + i] = ports[i];
|
|
|
|
s->ports[firstport + i].speedmask |=
|
|
|
|
USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL;
|
|
|
|
/* Ensure devs attached before the initial reset go to the companion */
|
|
|
|
s->portsc[firstport + i] = PORTSC_POWNER;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->companion_count++;
|
2012-09-06 13:24:51 +04:00
|
|
|
s->caps[0x05] = (s->companion_count << 4) | portcount;
|
2011-06-24 18:18:13 +04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-29 15:44:35 +04:00
|
|
|
static void ehci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep,
|
|
|
|
unsigned int stream)
|
2012-11-17 15:47:17 +04:00
|
|
|
{
|
|
|
|
EHCIState *s = container_of(bus, EHCIState, bus);
|
|
|
|
uint32_t portsc = s->portsc[ep->dev->port->index];
|
|
|
|
|
|
|
|
if (portsc & PORTSC_POWNER) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->periodic_sched_active = PERIODIC_ACTIVE;
|
|
|
|
qemu_bh_schedule(s->async_bh);
|
|
|
|
}
|
|
|
|
|
2012-01-10 20:46:15 +04:00
|
|
|
static USBDevice *ehci_find_device(EHCIState *ehci, uint8_t addr)
|
|
|
|
{
|
|
|
|
USBDevice *dev;
|
|
|
|
USBPort *port;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < NB_PORTS; i++) {
|
|
|
|
port = &ehci->ports[i];
|
|
|
|
if (!(ehci->portsc[i] & PORTSC_PED)) {
|
|
|
|
DPRINTF("Port %d not enabled\n", i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
dev = usb_find_device(port, addr);
|
|
|
|
if (dev != NULL) {
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* 4.1 host controller initialization */
|
|
|
|
static void ehci_reset(void *opaque)
|
|
|
|
{
|
|
|
|
EHCIState *s = opaque;
|
|
|
|
int i;
|
2011-06-24 18:18:13 +04:00
|
|
|
USBDevice *devs[NB_PORTS];
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-18 12:12:58 +04:00
|
|
|
trace_usb_ehci_reset();
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
/*
|
|
|
|
* Do the detach before touching portsc, so that it correctly gets send to
|
|
|
|
* us or to our companion based on PORTSC_POWNER before the reset.
|
|
|
|
*/
|
|
|
|
for(i = 0; i < NB_PORTS; i++) {
|
|
|
|
devs[i] = s->ports[i].dev;
|
2011-09-01 15:56:37 +04:00
|
|
|
if (devs[i] && devs[i]->attached) {
|
|
|
|
usb_detach(&s->ports[i]);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-06 13:24:51 +04:00
|
|
|
memset(&s->opreg, 0x00, sizeof(s->opreg));
|
|
|
|
memset(&s->portsc, 0x00, sizeof(s->portsc));
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
|
|
|
|
s->usbsts = USBSTS_HALT;
|
2012-07-11 13:06:05 +04:00
|
|
|
s->usbsts_pending = 0;
|
|
|
|
s->usbsts_frindex = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
s->astate = EST_INACTIVE;
|
|
|
|
s->pstate = EST_INACTIVE;
|
|
|
|
|
|
|
|
for(i = 0; i < NB_PORTS; i++) {
|
2011-06-24 18:18:13 +04:00
|
|
|
if (s->companion_ports[i]) {
|
|
|
|
s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER;
|
|
|
|
} else {
|
|
|
|
s->portsc[i] = PORTSC_PPOWER;
|
|
|
|
}
|
2011-09-01 15:56:37 +04:00
|
|
|
if (devs[i] && devs[i]->attached) {
|
|
|
|
usb_attach(&s->ports[i]);
|
2012-01-06 18:23:10 +04:00
|
|
|
usb_device_reset(devs[i]);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
2012-03-03 00:27:10 +04:00
|
|
|
ehci_queues_rip_all(s, 0);
|
|
|
|
ehci_queues_rip_all(s, 1);
|
2013-08-21 19:03:08 +04:00
|
|
|
timer_del(s->frame_timer);
|
2012-05-11 13:19:11 +04:00
|
|
|
qemu_bh_cancel(s->async_bh);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static uint64_t ehci_caps_read(void *ptr, hwaddr addr,
|
2012-09-06 13:24:51 +04:00
|
|
|
unsigned size)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
2012-09-06 13:24:51 +04:00
|
|
|
return s->caps[addr];
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static uint64_t ehci_opreg_read(void *ptr, hwaddr addr,
|
2012-09-06 13:24:51 +04:00
|
|
|
unsigned size)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
|
|
|
uint32_t val;
|
|
|
|
|
2012-12-18 17:17:02 +04:00
|
|
|
switch (addr) {
|
|
|
|
case FRINDEX:
|
|
|
|
/* Round down to mult of 8, else it can go backwards on migration */
|
|
|
|
val = s->frindex & ~7;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
val = s->opreg[addr >> 2];
|
|
|
|
}
|
|
|
|
|
2012-10-29 05:34:34 +04:00
|
|
|
trace_usb_ehci_opreg_read(addr + s->opregbase, addr2str(addr), val);
|
2010-12-03 18:17:28 +03:00
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static uint64_t ehci_port_read(void *ptr, hwaddr addr,
|
2012-09-06 13:24:51 +04:00
|
|
|
unsigned size)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
|
|
|
uint32_t val;
|
|
|
|
|
2012-09-06 13:24:51 +04:00
|
|
|
val = s->portsc[addr >> 2];
|
2013-06-06 17:41:12 +04:00
|
|
|
trace_usb_ehci_portsc_read(addr + s->portscbase, addr >> 2, val);
|
2010-12-03 18:17:28 +03:00
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner)
|
|
|
|
{
|
|
|
|
USBDevice *dev = s->ports[port].dev;
|
|
|
|
uint32_t *portsc = &s->portsc[port];
|
|
|
|
uint32_t orig;
|
|
|
|
|
|
|
|
if (s->companion_ports[port] == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
owner = owner & PORTSC_POWNER;
|
|
|
|
orig = *portsc & PORTSC_POWNER;
|
|
|
|
|
|
|
|
if (!(owner ^ orig)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached) {
|
|
|
|
usb_detach(&s->ports[port]);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
*portsc &= ~PORTSC_POWNER;
|
|
|
|
*portsc |= owner;
|
|
|
|
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached) {
|
|
|
|
usb_attach(&s->ports[port]);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static void ehci_port_write(void *ptr, hwaddr addr,
|
2012-09-06 13:24:51 +04:00
|
|
|
uint64_t val, unsigned size)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-09-06 13:24:51 +04:00
|
|
|
EHCIState *s = ptr;
|
|
|
|
int port = addr >> 2;
|
2010-12-03 18:17:28 +03:00
|
|
|
uint32_t *portsc = &s->portsc[port];
|
2012-09-06 13:24:51 +04:00
|
|
|
uint32_t old = *portsc;
|
2010-12-03 18:17:28 +03:00
|
|
|
USBDevice *dev = s->ports[port].dev;
|
|
|
|
|
2013-06-06 17:41:12 +04:00
|
|
|
trace_usb_ehci_portsc_write(addr + s->portscbase, addr >> 2, val);
|
2012-09-06 13:24:51 +04:00
|
|
|
|
2011-06-24 16:36:13 +04:00
|
|
|
/* Clear rwc bits */
|
|
|
|
*portsc &= ~(val & PORTSC_RWC_MASK);
|
|
|
|
/* The guest may clear, but not set the PED bit */
|
|
|
|
*portsc &= val | ~PORTSC_PED;
|
2011-06-24 18:18:13 +04:00
|
|
|
/* POWNER is masked out by RO_MASK as it is RO when we've no companion */
|
|
|
|
handle_port_owner_write(s, port, val);
|
|
|
|
/* And finally apply RO_MASK */
|
2010-12-03 18:17:28 +03:00
|
|
|
val &= PORTSC_RO_MASK;
|
|
|
|
|
|
|
|
if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
|
2011-05-19 10:46:53 +04:00
|
|
|
trace_usb_ehci_port_reset(port, 1);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
|
2011-05-19 10:46:53 +04:00
|
|
|
trace_usb_ehci_port_reset(port, 0);
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached) {
|
2012-01-06 18:23:10 +04:00
|
|
|
usb_port_reset(&s->ports[port]);
|
2010-12-03 18:17:28 +03:00
|
|
|
*portsc &= ~PORTSC_CSC;
|
|
|
|
}
|
|
|
|
|
2011-06-24 16:36:13 +04:00
|
|
|
/*
|
|
|
|
* Table 2.16 Set the enable bit(and enable bit change) to indicate
|
2010-12-03 18:17:28 +03:00
|
|
|
* to SW that this port has a high speed device attached
|
|
|
|
*/
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) {
|
2011-06-24 16:36:13 +04:00
|
|
|
val |= PORTSC_PED;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2013-11-20 17:10:19 +04:00
|
|
|
if ((val & PORTSC_SUSPEND) && !(*portsc & PORTSC_SUSPEND)) {
|
|
|
|
trace_usb_ehci_port_suspend(port);
|
|
|
|
}
|
|
|
|
if (!(val & PORTSC_FPRES) && (*portsc & PORTSC_FPRES)) {
|
|
|
|
trace_usb_ehci_port_resume(port);
|
|
|
|
val &= ~PORTSC_SUSPEND;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
*portsc &= ~PORTSC_RO_MASK;
|
|
|
|
*portsc |= val;
|
2013-06-06 17:41:12 +04:00
|
|
|
trace_usb_ehci_portsc_change(addr + s->portscbase, addr >> 2, *portsc, old);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-10-23 14:30:10 +04:00
|
|
|
static void ehci_opreg_write(void *ptr, hwaddr addr,
|
2012-09-06 13:24:51 +04:00
|
|
|
uint64_t val, unsigned size)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
2012-09-06 13:24:51 +04:00
|
|
|
uint32_t *mmio = s->opreg + (addr >> 2);
|
2011-05-19 10:55:09 +04:00
|
|
|
uint32_t old = *mmio;
|
2010-12-03 18:17:28 +03:00
|
|
|
int i;
|
2011-05-18 12:12:58 +04:00
|
|
|
|
2012-10-29 05:34:34 +04:00
|
|
|
trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-10-29 05:34:34 +04:00
|
|
|
switch (addr) {
|
2010-12-03 18:17:28 +03:00
|
|
|
case USBCMD:
|
2012-05-24 14:04:50 +04:00
|
|
|
if (val & USBCMD_HCRESET) {
|
|
|
|
ehci_reset(s);
|
|
|
|
val = s->usbcmd;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-08-15 19:08:54 +04:00
|
|
|
/* not supporting dynamic frame list size at the moment */
|
|
|
|
if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) {
|
|
|
|
fprintf(stderr, "attempt to set frame list size -- value %d\n",
|
2012-09-06 13:24:51 +04:00
|
|
|
(int)val & USBCMD_FLS);
|
2012-08-15 19:08:54 +04:00
|
|
|
val &= ~USBCMD_FLS;
|
|
|
|
}
|
|
|
|
|
2012-08-30 11:55:19 +04:00
|
|
|
if (val & USBCMD_IAAD) {
|
|
|
|
/*
|
|
|
|
* Process IAAD immediately, otherwise the Linux IAAD watchdog may
|
|
|
|
* trigger and re-use a qh without us seeing the unlink.
|
|
|
|
*/
|
|
|
|
s->async_stepdown = 0;
|
|
|
|
qemu_bh_schedule(s->async_bh);
|
2012-08-31 14:41:43 +04:00
|
|
|
trace_usb_ehci_doorbell_ring();
|
2012-08-30 11:55:19 +04:00
|
|
|
}
|
|
|
|
|
2012-05-24 14:53:43 +04:00
|
|
|
if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) !=
|
|
|
|
((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd)) {
|
2012-05-24 15:34:02 +04:00
|
|
|
if (s->pstate == EST_INACTIVE) {
|
2012-05-24 14:53:43 +04:00
|
|
|
SET_LAST_RUN_CLOCK(s);
|
|
|
|
}
|
2012-08-15 19:08:54 +04:00
|
|
|
s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */
|
2012-05-24 14:53:43 +04:00
|
|
|
ehci_update_halt(s);
|
2012-05-24 15:34:02 +04:00
|
|
|
s->async_stepdown = 0;
|
ehci: Improve latency of interrupt delivery and async schedule scanning
While doing various performance tests of reading from USB mass storage devices
I noticed the following::
1) When an async handled packet completes, we don't immediately report an
interrupt to the guest, instead we wait for the frame-timer to run and
report it from there
2) If 1) has been fixed and an async handled packet takes a while to complete,
then async_stepdown will become a high value, which means that there
will be a large latency before any new packets queued by the guest in
response to the interrupt get seen
1) was done deliberately as part of commit f0ad01f92:
http://www.kraxel.org/cgit/qemu/commit/?h=usb.57&id=f0ad01f92ca02eee7cadbfd225c5de753ebd5fce
Since setting the interrupt immediately on async packet completion was causing
issues with Linux guests, I believe this recently fixed Linux bug explains
why this is happening:
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=commitdiff;h=361aabf395e4a23cf554cf4ec0c0c6963b8beb01
Note that we can *not* count on this fix being present in all Linux guests!
I was hoping that the recently added support for Interrupt Threshold Control
would fix the issues with Linux guests, but adding a simple ehci_commit_irq()
call to ehci_async_bh() still caused problems with Linux guests.
The problem is, that when doing ehci_commit_irq() from ehci_async_bh(),
the "old" frindex value is used to calculate usbsts_frindex, and when
the frame-timer then runs possibly very shortly after ehci_async_bh(),
it increases the frame-timer, and thus any interrupts raised from that
frame-timer run, will also get reported to the guest immediately, rather
then being delayed to the next frame-timer run.
Luckily the solution for this is simple, this means that we need to
increase frindex before calling ehci_commit_irq() from ehci_async_bh(),
which in the end boils down to simple calling ehci_frame_timer() instead
of ehci_async_bh() from the bh.
This may seem like it causes a lot of extra work to be done, but this
is not true. Any work done from the frame-timer processing the periodic
schedule is work which then does not need to be done the next time the
frame timer runs, also the frame-timer will re-arm itself at (possibly)
a later time then it was armed for saving a vmexit at that time.
As an additional advantage moving to simply calling the frame-timer also
fixes 2) as the packet completion will set async_stepdown to 0, and the
re-arming of the timer with an async_stepdown of 0 ensures that any
newly queued up packets get seen in a reasonable amount of time.
This improves the speed (MB/s) of a Linux guest reading from a USB mass
storage device by a factor of 1.5 - 1.7 with input pipelining disabled,
and by a factor of 1.8 with input pipelining enabled.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-10-24 20:14:01 +04:00
|
|
|
qemu_bh_schedule(s->async_bh);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case USBSTS:
|
2012-05-09 09:12:04 +04:00
|
|
|
val &= USBSTS_RO_MASK; // bits 6 through 31 are RO
|
|
|
|
ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC
|
2011-05-18 12:12:58 +04:00
|
|
|
val = s->usbsts;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_update_irq(s);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case USBINTR:
|
|
|
|
val &= USBINTR_MASK;
|
2012-11-13 20:20:05 +04:00
|
|
|
if (ehci_enabled(s) && (USBSTS_FLR & val)) {
|
|
|
|
qemu_bh_schedule(s->async_bh);
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
2012-04-03 16:21:47 +04:00
|
|
|
case FRINDEX:
|
2012-12-18 17:17:02 +04:00
|
|
|
val &= 0x00003fff; /* frindex is 14bits */
|
|
|
|
s->usbsts_frindex = val;
|
2012-04-03 16:21:47 +04:00
|
|
|
break;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
case CONFIGFLAG:
|
|
|
|
val &= 0x1;
|
|
|
|
if (val) {
|
|
|
|
for(i = 0; i < NB_PORTS; i++)
|
2011-06-24 18:18:13 +04:00
|
|
|
handle_port_owner_write(s, i, 0);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PERIODICLISTBASE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (ehci_periodic_enabled(s)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
fprintf(stderr,
|
|
|
|
"ehci: PERIODIC list base register set while periodic schedule\n"
|
|
|
|
" is enabled and HC is enabled\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ASYNCLISTADDR:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (ehci_async_enabled(s)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
fprintf(stderr,
|
|
|
|
"ehci: ASYNC list address register set while async schedule\n"
|
|
|
|
" is enabled and HC is enabled\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-19 10:55:09 +04:00
|
|
|
*mmio = val;
|
2012-10-29 05:34:34 +04:00
|
|
|
trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr),
|
|
|
|
*mmio, old);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-06-19 18:23:32 +04:00
|
|
|
/*
|
|
|
|
* Write the qh back to guest physical memory. This step isn't
|
|
|
|
* in the EHCI spec but we need to do it since we don't share
|
|
|
|
* physical memory with our guest VM.
|
|
|
|
*
|
|
|
|
* The first three dwords are read-only for the EHCI, so skip them
|
|
|
|
* when writing back the qh.
|
|
|
|
*/
|
|
|
|
static void ehci_flush_qh(EHCIQueue *q)
|
|
|
|
{
|
|
|
|
uint32_t *qh = (uint32_t *) &q->qh;
|
|
|
|
uint32_t dwords = sizeof(EHCIqh) >> 2;
|
|
|
|
uint32_t addr = NLPTR_GET(q->qhaddr);
|
|
|
|
|
|
|
|
put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
// 4.10.2
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static int ehci_qh_do_overlay(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2010-12-03 18:17:28 +03:00
|
|
|
int i;
|
|
|
|
int dtoggle;
|
|
|
|
int ping;
|
|
|
|
int eps;
|
|
|
|
int reload;
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
// remember values in fields to preserve in qh after overlay
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
dtoggle = q->qh.token & QTD_TOKEN_DTOGGLE;
|
|
|
|
ping = q->qh.token & QTD_TOKEN_PING;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
q->qh.current_qtd = p->qtdaddr;
|
|
|
|
q->qh.next_qtd = p->qtd.next;
|
|
|
|
q->qh.altnext_qtd = p->qtd.altnext;
|
|
|
|
q->qh.token = p->qtd.token;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
eps = get_field(q->qh.epchar, QH_EPCHAR_EPS);
|
2010-12-03 18:17:28 +03:00
|
|
|
if (eps == EHCI_QH_EPS_HIGH) {
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.token &= ~QTD_TOKEN_PING;
|
|
|
|
q->qh.token |= ping;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
|
|
|
|
set_field(&q->qh.altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
for (i = 0; i < 5; i++) {
|
2012-05-09 19:06:36 +04:00
|
|
|
q->qh.bufptr[i] = p->qtd.bufptr[i];
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (!(q->qh.epchar & QH_EPCHAR_DTC)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
// preserve QH DT bit
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.token &= ~QTD_TOKEN_DTOGGLE;
|
|
|
|
q->qh.token |= dtoggle;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
|
|
|
|
q->qh.bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-06-19 18:23:32 +04:00
|
|
|
ehci_flush_qh(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
static int ehci_init_transfer(EHCIPacket *p)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-07-13 19:36:46 +04:00
|
|
|
uint32_t cpage, offset, bytes, plen;
|
2011-10-31 10:06:57 +04:00
|
|
|
dma_addr_t page;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
cpage = get_field(p->qtd.token, QTD_TOKEN_CPAGE);
|
|
|
|
bytes = get_field(p->qtd.token, QTD_TOKEN_TBYTES);
|
|
|
|
offset = p->qtd.bufptr[0] & ~QTD_BUFPTR_MASK;
|
2013-09-09 12:18:17 +04:00
|
|
|
qemu_sglist_init(&p->sgl, p->queue->ehci->device, 5, p->queue->ehci->as);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
while (bytes > 0) {
|
|
|
|
if (cpage > 4) {
|
|
|
|
fprintf(stderr, "cpage out of range (%d)\n", cpage);
|
2012-11-01 20:15:03 +04:00
|
|
|
return -1;
|
2011-07-13 19:36:46 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
|
2011-07-13 19:36:46 +04:00
|
|
|
page += offset;
|
|
|
|
plen = bytes;
|
|
|
|
if (plen > 4096 - offset) {
|
|
|
|
plen = 4096 - offset;
|
|
|
|
offset = 0;
|
|
|
|
cpage++;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
qemu_sglist_add(&p->sgl, page, plen);
|
2011-07-13 19:36:46 +04:00
|
|
|
bytes -= plen;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-01 20:15:04 +04:00
|
|
|
static void ehci_finish_transfer(EHCIQueue *q, int len)
|
2011-07-13 19:36:46 +04:00
|
|
|
{
|
|
|
|
uint32_t cpage, offset;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-01 20:15:04 +04:00
|
|
|
if (len > 0) {
|
2011-07-13 19:36:46 +04:00
|
|
|
/* update cpage & offset */
|
|
|
|
cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
|
|
|
|
offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-01 20:15:04 +04:00
|
|
|
offset += len;
|
2011-07-13 19:36:46 +04:00
|
|
|
cpage += offset >> QTD_BUFPTR_SH;
|
|
|
|
offset &= ~QTD_BUFPTR_MASK;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
|
|
|
|
q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
|
|
|
|
q->qh.bufptr[0] |= offset;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-06-21 13:52:28 +04:00
|
|
|
static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p;
|
2011-06-24 18:18:13 +04:00
|
|
|
EHCIState *s = port->opaque;
|
|
|
|
uint32_t portsc = s->portsc[port->index];
|
|
|
|
|
|
|
|
if (portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->ops->complete(companion, packet);
|
|
|
|
return;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
p = container_of(packet, EHCIPacket, packet);
|
|
|
|
assert(p->async == EHCI_ASYNC_INFLIGHT);
|
2012-10-24 20:14:08 +04:00
|
|
|
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
if (packet->status == USB_RET_REMOVE_FROM_QUEUE) {
|
2012-10-24 20:14:08 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "remove");
|
|
|
|
ehci_free_packet(p);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "wakeup");
|
2012-05-09 19:06:36 +04:00
|
|
|
p->async = EHCI_ASYNC_FINISHED;
|
2012-05-11 13:31:56 +04:00
|
|
|
|
2012-11-17 15:47:17 +04:00
|
|
|
if (!p->queue->async) {
|
|
|
|
s->periodic_sched_active = PERIODIC_ACTIVE;
|
2012-05-11 13:31:56 +04:00
|
|
|
}
|
2012-11-17 15:47:17 +04:00
|
|
|
qemu_bh_schedule(s->async_bh);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static void ehci_execute_complete(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2012-11-01 20:15:04 +04:00
|
|
|
uint32_t tbytes;
|
2012-05-09 19:06:36 +04:00
|
|
|
|
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
2012-09-03 13:01:13 +04:00
|
|
|
assert(p->async == EHCI_ASYNC_INITIALIZED ||
|
|
|
|
p->async == EHCI_ASYNC_FINISHED);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-01 20:15:04 +04:00
|
|
|
DPRINTF("execute_complete: qhaddr 0x%x, next 0x%x, qtdaddr 0x%x, "
|
|
|
|
"status %d, actual_length %d\n",
|
|
|
|
q->qhaddr, q->qh.next, q->qtdaddr,
|
|
|
|
p->packet.status, p->packet.actual_length);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-01 20:15:04 +04:00
|
|
|
switch (p->packet.status) {
|
|
|
|
case USB_RET_SUCCESS:
|
|
|
|
break;
|
|
|
|
case USB_RET_IOERROR:
|
|
|
|
case USB_RET_NODEV:
|
|
|
|
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR);
|
|
|
|
set_field(&q->qh.token, 0, QTD_TOKEN_CERR);
|
|
|
|
ehci_raise_irq(q->ehci, USBSTS_ERRINT);
|
|
|
|
break;
|
|
|
|
case USB_RET_STALL:
|
|
|
|
q->qh.token |= QTD_TOKEN_HALT;
|
|
|
|
ehci_raise_irq(q->ehci, USBSTS_ERRINT);
|
|
|
|
break;
|
|
|
|
case USB_RET_NAK:
|
|
|
|
set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT);
|
|
|
|
return; /* We're not done yet with this transaction */
|
|
|
|
case USB_RET_BABBLE:
|
|
|
|
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
|
|
|
|
ehci_raise_irq(q->ehci, USBSTS_ERRINT);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* should not be triggerable */
|
|
|
|
fprintf(stderr, "USB invalid response %d\n", p->packet.status);
|
2013-07-25 20:21:28 +04:00
|
|
|
g_assert_not_reached();
|
2012-11-01 20:15:04 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO check 4.12 for splits */
|
|
|
|
tbytes = get_field(q->qh.token, QTD_TOKEN_TBYTES);
|
|
|
|
if (tbytes && p->pid == USB_TOKEN_IN) {
|
|
|
|
tbytes -= p->packet.actual_length;
|
|
|
|
if (tbytes) {
|
|
|
|
/* 4.15.1.2 must raise int on a short input packet */
|
|
|
|
ehci_raise_irq(q->ehci, USBSTS_INT);
|
2012-12-14 17:35:31 +04:00
|
|
|
if (q->async) {
|
|
|
|
q->ehci->int_req_by_async = true;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
} else {
|
2012-11-01 20:15:04 +04:00
|
|
|
tbytes = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
2012-11-01 20:15:04 +04:00
|
|
|
DPRINTF("updating tbytes to %d\n", tbytes);
|
|
|
|
set_field(&q->qh.token, tbytes, QTD_TOKEN_TBYTES);
|
|
|
|
|
|
|
|
ehci_finish_transfer(q, p->packet.actual_length);
|
2012-06-27 08:50:42 +04:00
|
|
|
usb_packet_unmap(&p->packet, &p->sgl);
|
2012-05-09 19:06:36 +04:00
|
|
|
qemu_sglist_destroy(&p->sgl);
|
2012-09-03 13:01:13 +04:00
|
|
|
p->async = EHCI_ASYNC_NONE;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.token ^= QTD_TOKEN_DTOGGLE;
|
|
|
|
q->qh.token &= ~QTD_TOKEN_ACTIVE;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
usb-ehci: Fix and simplify nakcnt handling
The nakcnt code in ehci_execute_complete() marked transactions as finished
when a packet completed with a result of USB_RET_NAK, but USB_RET_NAK
means that the device cannot receive / send data at that time and that
the transaction should be retried later, which is also what the usb-uhci
and usb-ohci code does.
Note that there already was some special code in place to handle this
for interrupt endpoints in the form of doing a return from
ehci_execute_complete() when reload == 0, but that for bulk transactions
this was not handled correctly (where as for example the usb-ccid device does
return USB_RET_NAK for bulk packets).
Besides that the code in ehci_execute_complete() decrement nakcnt by 1
on a packet result of USB_RET_NAK, but
-since the transaction got marked as finished,
nakcnt would never be decremented again
-there is no code checking for nakcnt becoming 0
-there is no use in re-trying the transaction within the same usb frame /
usb-ehci frame-timer call, since the status of emulated devices won't change
as long as the usb-ehci frame-timer is running
So we should simply set the nakcnt to 0 when we get a USB_RET_NAK, thus
claiming that we've tried reload times (or as many times as possible if
reload is 0).
Besides the code in ehci_execute_complete() handling USB_RET_NAK there
was also code handling it in ehci_state_executing(), which calls
ehci_execute_complete(), and then does its own handling on top of the handling
in ehci_execute_complete(), this code would decrement nakcnt *again* (if not
already 0), or restore the reload value (which was never changed) on success.
Since the double decrement was wrong to begin with, and is no longer needed
now that we set nakcnt directly to 0 on USB_RET_NAK, and the restore of reload
is not needed either, this patch simply removes all nakcnt handling from
ehci_state_executing().
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:17 +04:00
|
|
|
if (q->qh.token & QTD_TOKEN_IOC) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(q->ehci, USBSTS_INT);
|
2012-10-24 20:14:02 +04:00
|
|
|
if (q->async) {
|
|
|
|
q->ehci->int_req_by_async = true;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-01 20:15:03 +04:00
|
|
|
/* 4.10.3 returns "again" */
|
2012-05-10 16:12:38 +04:00
|
|
|
static int ehci_execute(EHCIPacket *p, const char *action)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-01-12 16:23:01 +04:00
|
|
|
USBEndpoint *ep;
|
2010-12-03 18:17:28 +03:00
|
|
|
int endp;
|
2012-10-24 20:14:09 +04:00
|
|
|
bool spd;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-09-03 13:01:13 +04:00
|
|
|
assert(p->async == EHCI_ASYNC_NONE ||
|
|
|
|
p->async == EHCI_ASYNC_INITIALIZED);
|
|
|
|
|
2012-05-10 14:10:47 +04:00
|
|
|
if (!(p->qtd.token & QTD_TOKEN_ACTIVE)) {
|
|
|
|
fprintf(stderr, "Attempting to execute inactive qtd\n");
|
2012-11-01 20:15:03 +04:00
|
|
|
return -1;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-10-24 20:13:59 +04:00
|
|
|
if (get_field(p->qtd.token, QTD_TOKEN_TBYTES) > BUFF_SIZE) {
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_trace_guest_bug(p->queue->ehci,
|
|
|
|
"guest requested more bytes than allowed");
|
2012-11-01 20:15:03 +04:00
|
|
|
return -1;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-12-14 17:35:40 +04:00
|
|
|
if (!ehci_verify_pid(p->queue, &p->qtd)) {
|
|
|
|
ehci_queue_stopped(p->queue); /* Mark the ep in the prev dir stopped */
|
|
|
|
}
|
2012-12-14 17:35:28 +04:00
|
|
|
p->pid = ehci_get_pid(&p->qtd);
|
2012-12-14 17:35:29 +04:00
|
|
|
p->queue->last_pid = p->pid;
|
2012-05-10 14:10:47 +04:00
|
|
|
endp = get_field(p->queue->qh.epchar, QH_EPCHAR_EP);
|
2012-05-10 14:18:45 +04:00
|
|
|
ep = usb_ep_get(p->queue->dev, p->pid, endp);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-09-03 13:01:13 +04:00
|
|
|
if (p->async == EHCI_ASYNC_NONE) {
|
|
|
|
if (ehci_init_transfer(p) != 0) {
|
2012-11-01 20:15:03 +04:00
|
|
|
return -1;
|
2012-09-03 13:01:13 +04:00
|
|
|
}
|
|
|
|
|
2012-10-24 20:14:09 +04:00
|
|
|
spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0);
|
2013-01-29 15:44:35 +04:00
|
|
|
usb_packet_setup(&p->packet, p->pid, ep, 0, p->qtdaddr, spd,
|
2012-10-24 20:14:10 +04:00
|
|
|
(p->qtd.token & QTD_TOKEN_IOC) != 0);
|
2012-09-03 13:01:13 +04:00
|
|
|
usb_packet_map(&p->packet, &p->sgl);
|
|
|
|
p->async = EHCI_ASYNC_INITIALIZED;
|
|
|
|
}
|
2011-07-13 19:36:46 +04:00
|
|
|
|
2012-05-10 16:12:38 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, action);
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
usb_handle_packet(p->queue->dev, &p->packet);
|
|
|
|
DPRINTF("submit: qh 0x%x next 0x%x qtd 0x%x pid 0x%x len %zd endp 0x%x "
|
|
|
|
"status %d actual_length %d\n", p->queue->qhaddr, p->qtd.next,
|
|
|
|
p->qtdaddr, p->pid, p->packet.iov.size, endp, p->packet.status,
|
|
|
|
p->packet.actual_length);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
if (p->packet.actual_length > BUFF_SIZE) {
|
2010-12-03 18:17:28 +03:00
|
|
|
fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n");
|
2012-11-01 20:15:03 +04:00
|
|
|
return -1;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-11-01 20:15:03 +04:00
|
|
|
return 1;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 4.7.2
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int ehci_process_itd(EHCIState *ehci,
|
2012-08-23 15:30:13 +04:00
|
|
|
EHCIitd *itd,
|
|
|
|
uint32_t addr)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
USBDevice *dev;
|
2012-01-12 16:23:01 +04:00
|
|
|
USBEndpoint *ep;
|
2012-01-10 20:46:15 +04:00
|
|
|
uint32_t i, len, pid, dir, devaddr, endp;
|
2011-05-30 18:09:08 +04:00
|
|
|
uint32_t pg, off, ptr1, ptr2, max, mult;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-17 15:47:17 +04:00
|
|
|
ehci->periodic_sched_active = PERIODIC_ACTIVE;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
|
2011-05-30 18:09:08 +04:00
|
|
|
devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
|
2010-12-03 18:17:28 +03:00
|
|
|
endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP);
|
2011-05-30 18:09:08 +04:00
|
|
|
max = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT);
|
|
|
|
mult = get_field(itd->bufptr[2], ITD_BUFPTR_MULT);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
for(i = 0; i < 8; i++) {
|
|
|
|
if (itd->transact[i] & ITD_XACT_ACTIVE) {
|
2011-05-30 18:09:08 +04:00
|
|
|
pg = get_field(itd->transact[i], ITD_XACT_PGSEL);
|
|
|
|
off = itd->transact[i] & ITD_XACT_OFFSET_MASK;
|
|
|
|
ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK);
|
|
|
|
ptr2 = (itd->bufptr[pg+1] & ITD_BUFPTR_MASK);
|
|
|
|
len = get_field(itd->transact[i], ITD_XACT_LENGTH);
|
|
|
|
|
|
|
|
if (len > max * mult) {
|
|
|
|
len = max * mult;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
if (len > BUFF_SIZE) {
|
2012-11-01 20:15:03 +04:00
|
|
|
return -1;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2013-09-09 12:18:17 +04:00
|
|
|
qemu_sglist_init(&ehci->isgl, ehci->device, 2, ehci->as);
|
2011-05-30 18:09:08 +04:00
|
|
|
if (off + len > 4096) {
|
|
|
|
/* transfer crosses page border */
|
2011-07-13 19:36:46 +04:00
|
|
|
uint32_t len2 = off + len - 4096;
|
|
|
|
uint32_t len1 = len - len2;
|
|
|
|
qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
|
|
|
|
qemu_sglist_add(&ehci->isgl, ptr2, len2);
|
2011-05-30 18:09:08 +04:00
|
|
|
} else {
|
2011-07-13 19:36:46 +04:00
|
|
|
qemu_sglist_add(&ehci->isgl, ptr1 + off, len);
|
2011-05-30 18:09:08 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-01-12 16:23:01 +04:00
|
|
|
dev = ehci_find_device(ehci, devaddr);
|
|
|
|
ep = usb_ep_get(dev, pid, endp);
|
2012-08-28 13:50:26 +04:00
|
|
|
if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) {
|
2013-01-29 15:44:35 +04:00
|
|
|
usb_packet_setup(&ehci->ipacket, pid, ep, 0, addr, false,
|
2012-10-24 20:14:10 +04:00
|
|
|
(itd->transact[i] & ITD_XACT_IOC) != 0);
|
2012-02-27 14:23:08 +04:00
|
|
|
usb_packet_map(&ehci->ipacket, &ehci->isgl);
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
usb_handle_packet(dev, &ehci->ipacket);
|
2012-06-27 08:50:42 +04:00
|
|
|
usb_packet_unmap(&ehci->ipacket, &ehci->isgl);
|
2012-02-27 14:23:08 +04:00
|
|
|
} else {
|
|
|
|
DPRINTF("ISOCH: attempt to addess non-iso endpoint\n");
|
2012-11-01 20:15:04 +04:00
|
|
|
ehci->ipacket.status = USB_RET_NAK;
|
|
|
|
ehci->ipacket.actual_length = 0;
|
2012-02-27 14:23:08 +04:00
|
|
|
}
|
2011-07-13 19:36:46 +04:00
|
|
|
qemu_sglist_destroy(&ehci->isgl);
|
|
|
|
|
2012-11-01 20:15:04 +04:00
|
|
|
switch (ehci->ipacket.status) {
|
|
|
|
case USB_RET_SUCCESS:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Unexpected iso usb result: %d\n",
|
|
|
|
ehci->ipacket.status);
|
|
|
|
/* Fall through */
|
|
|
|
case USB_RET_IOERROR:
|
|
|
|
case USB_RET_NODEV:
|
|
|
|
/* 3.3.2: XACTERR is only allowed on IN transactions */
|
|
|
|
if (dir) {
|
|
|
|
itd->transact[i] |= ITD_XACT_XACTERR;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_ERRINT);
|
2012-03-03 00:27:18 +04:00
|
|
|
}
|
2012-11-01 20:15:04 +04:00
|
|
|
break;
|
|
|
|
case USB_RET_BABBLE:
|
|
|
|
itd->transact[i] |= ITD_XACT_BABBLE;
|
|
|
|
ehci_raise_irq(ehci, USBSTS_ERRINT);
|
|
|
|
break;
|
|
|
|
case USB_RET_NAK:
|
|
|
|
/* no data for us, so do a zero-length transfer */
|
|
|
|
ehci->ipacket.actual_length = 0;
|
|
|
|
break;
|
2012-03-03 00:27:18 +04:00
|
|
|
}
|
2012-11-01 20:15:04 +04:00
|
|
|
if (!dir) {
|
|
|
|
set_field(&itd->transact[i], len - ehci->ipacket.actual_length,
|
|
|
|
ITD_XACT_LENGTH); /* OUT */
|
|
|
|
} else {
|
|
|
|
set_field(&itd->transact[i], ehci->ipacket.actual_length,
|
|
|
|
ITD_XACT_LENGTH); /* IN */
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
2012-02-26 19:14:48 +04:00
|
|
|
if (itd->transact[i] & ITD_XACT_IOC) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_INT);
|
2012-02-26 19:14:48 +04:00
|
|
|
}
|
2011-05-30 18:09:08 +04:00
|
|
|
itd->transact[i] &= ~ITD_XACT_ACTIVE;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-05-10 16:13:41 +04:00
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* This state is the entry point for asynchronous schedule
|
|
|
|
* processing. Entry here consitutes a EHCI start event state (4.8.5)
|
|
|
|
*/
|
2011-05-18 16:23:35 +04:00
|
|
|
static int ehci_state_waitlisthead(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-19 12:49:03 +04:00
|
|
|
EHCIqh qh;
|
2010-12-03 18:17:28 +03:00
|
|
|
int i = 0;
|
|
|
|
int again = 0;
|
|
|
|
uint32_t entry = ehci->asynclistaddr;
|
|
|
|
|
|
|
|
/* set reclamation flag at start event (4.8.6) */
|
|
|
|
if (async) {
|
2011-05-18 12:12:58 +04:00
|
|
|
ehci_set_usbsts(ehci, USBSTS_REC);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-09-12 17:08:32 +04:00
|
|
|
ehci_queues_rip_unused(ehci, async);
|
2011-05-19 19:56:19 +04:00
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* Find the head of the list (4.9.1.1) */
|
|
|
|
for(i = 0; i < MAX_QH; i++) {
|
2012-11-15 16:07:49 +04:00
|
|
|
if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
|
|
|
|
sizeof(EHCIqh) >> 2) < 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
2011-05-19 19:56:19 +04:00
|
|
|
ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (qh.epchar & QH_EPCHAR_H) {
|
2010-12-03 18:17:28 +03:00
|
|
|
if (async) {
|
|
|
|
entry |= (NLPTR_TYPE_QH << 1);
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_fetch_addr(ehci, async, entry);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
entry = qh.next;
|
2010-12-03 18:17:28 +03:00
|
|
|
if (entry == ehci->asynclistaddr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no head found for list. */
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
out:
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* This state is the entry point for periodic schedule processing as
|
|
|
|
* well as being a continuation state for async processing.
|
|
|
|
*/
|
2011-05-18 16:23:35 +04:00
|
|
|
static int ehci_state_fetchentry(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int again = 0;
|
2011-05-19 12:49:03 +04:00
|
|
|
uint32_t entry = ehci_get_fetch_addr(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-03-03 00:27:09 +04:00
|
|
|
if (NLPTR_TBIT(entry)) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* section 4.8, only QH in async schedule */
|
|
|
|
if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
|
|
|
|
fprintf(stderr, "non queue head request in async schedule\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (NLPTR_TYPE_GET(entry)) {
|
|
|
|
case NLPTR_TYPE_QH:
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NLPTR_TYPE_ITD:
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHITD);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
break;
|
|
|
|
|
2011-08-26 16:13:48 +04:00
|
|
|
case NLPTR_TYPE_STITD:
|
|
|
|
ehci_set_state(ehci, async, EST_FETCHSITD);
|
|
|
|
again = 1;
|
|
|
|
break;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
default:
|
2011-08-26 16:13:48 +04:00
|
|
|
/* TODO: handle FSTN type */
|
2010-12-03 18:17:28 +03:00
|
|
|
fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
|
|
|
|
"which is not supported yet\n", entry, NLPTR_TYPE_GET(entry));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-12-14 17:35:23 +04:00
|
|
|
uint32_t entry;
|
2011-05-19 12:49:03 +04:00
|
|
|
EHCIQueue *q;
|
2012-08-29 12:37:37 +04:00
|
|
|
EHCIqh qh;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
entry = ehci_get_fetch_addr(ehci, async);
|
2012-03-03 00:27:10 +04:00
|
|
|
q = ehci_find_queue_by_qh(ehci, entry, async);
|
2014-08-11 17:00:52 +04:00
|
|
|
if (q == NULL) {
|
2012-05-11 10:56:49 +04:00
|
|
|
q = ehci_alloc_queue(ehci, entry, async);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
|
|
|
|
2012-05-11 10:56:49 +04:00
|
|
|
q->seen++;
|
2011-05-19 19:56:19 +04:00
|
|
|
if (q->seen > 1) {
|
|
|
|
/* we are going in circles -- stop processing */
|
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
|
|
|
q = NULL;
|
|
|
|
goto out;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-15 16:07:49 +04:00
|
|
|
if (get_dwords(ehci, NLPTR_GET(q->qhaddr),
|
|
|
|
(uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) {
|
|
|
|
q = NULL;
|
|
|
|
goto out;
|
|
|
|
}
|
2012-08-29 12:37:37 +04:00
|
|
|
ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The overlay area of the qh should never be changed by the guest,
|
|
|
|
* except when idle, in which case the reset is a nop.
|
|
|
|
*/
|
2012-12-14 17:35:23 +04:00
|
|
|
if (!ehci_verify_qh(q, &qh)) {
|
2012-08-31 12:44:21 +04:00
|
|
|
if (ehci_reset_queue(q) > 0) {
|
|
|
|
ehci_trace_guest_bug(ehci, "guest updated active QH");
|
|
|
|
}
|
2012-08-29 12:37:37 +04:00
|
|
|
}
|
|
|
|
q->qh = qh;
|
|
|
|
|
ehci: Fix interrupt packet MULT handling
There are several issues with our handling of the MULT epcap field
of interrupt qhs, which this patch fixes.
1) When we don't execute a transaction because of the transaction counter
being 0, p->async stays EHCI_ASYNC_NONE, and the next time we process the
same qtd we hit an assert in ehci_state_fetchqtd because of this. Even though
I believe that this is caused by 3 below, this patch still removes the assert,
as that can still happen without 3, when multiple packets are queued for the
same interrupt ep.
2) We only *check* the transaction counter from ehci_state_execute, any
packets queued up by fill_queue bypass this check. This is fixed by not calling
fill_queue for interrupt packets.
3) Some versions of Windows set the MULT field of the qh to 0, which is a
clear violation of the EHCI spec, but still they do it. This means that we
will never execute a qtd for these, making interrupt ep-s on USB-2 devices
not work, and after recent changes, triggering 1).
So far we've stored the transaction counter in our copy of the mult field,
but with this beginnig at 0 already when dealing with these version of windows
this won't work. So this patch adds a transact_ctr field to our qh struct,
and sets this to the MULT field value on fetchqh. When the MULT field value
is 0, we set it to 4. Assuming that windows gets way with setting it to 0,
by the actual hardware going horizontal on a 1 -> 0 transition, which will
give it 4 transactions (MULT goes from 0 - 3).
Note that we cannot stop on detecting the 1 -> 0 transition, as our decrement
of the transaction counter, and checking for it are done in 2 different places.
Reported-by: Shawn Starr <shawn.starr@rogers.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-09-20 19:38:07 +04:00
|
|
|
q->transact_ctr = get_field(q->qh.epcap, QH_EPCAP_MULT);
|
|
|
|
if (q->transact_ctr == 0) { /* Guest bug in some versions of windows */
|
|
|
|
q->transact_ctr = 4;
|
|
|
|
}
|
|
|
|
|
2012-05-10 14:18:45 +04:00
|
|
|
if (q->dev == NULL) {
|
2012-12-14 17:35:23 +04:00
|
|
|
q->dev = ehci_find_device(q->ehci,
|
|
|
|
get_field(q->qh.epchar, QH_EPCHAR_DEVADDR));
|
2012-05-10 14:18:45 +04:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (async && (q->qh.epchar & QH_EPCHAR_H)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
|
|
|
|
if (ehci->usbsts & USBSTS_REC) {
|
2011-05-18 12:12:58 +04:00
|
|
|
ehci_clear_usbsts(ehci, USBSTS_REC);
|
2010-12-03 18:17:28 +03:00
|
|
|
} else {
|
|
|
|
DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset"
|
2011-05-19 12:49:03 +04:00
|
|
|
" - done processing\n", q->qhaddr);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2011-05-19 12:49:03 +04:00
|
|
|
q = NULL;
|
2010-12-03 18:17:28 +03:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if EHCI_DEBUG
|
2011-05-19 12:49:03 +04:00
|
|
|
if (q->qhaddr != q->qh.next) {
|
2010-12-03 18:17:28 +03:00
|
|
|
DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n",
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qhaddr,
|
|
|
|
q->qh.epchar & QH_EPCHAR_H,
|
|
|
|
q->qh.token & QTD_TOKEN_HALT,
|
|
|
|
q->qh.token & QTD_TOKEN_ACTIVE,
|
|
|
|
q->qh.next);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (q->qh.token & QTD_TOKEN_HALT) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-03-03 00:27:09 +04:00
|
|
|
} else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
|
|
|
|
(NLPTR_TBIT(q->qh.current_qtd) == 0)) {
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qtdaddr = q->qh.current_qtd;
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHQTD);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
} else {
|
|
|
|
/* EHCI spec version 1.0 Section 4.10.2 */
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ADVANCEQUEUE);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2011-05-19 12:49:03 +04:00
|
|
|
return q;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static int ehci_state_fetchitd(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-19 12:49:03 +04:00
|
|
|
uint32_t entry;
|
2010-12-03 18:17:28 +03:00
|
|
|
EHCIitd itd;
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
assert(!async);
|
|
|
|
entry = ehci_get_fetch_addr(ehci, async);
|
|
|
|
|
2012-11-15 16:07:49 +04:00
|
|
|
if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
|
|
|
|
sizeof(EHCIitd) >> 2) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_trace_itd(ehci, entry, &itd);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-08-23 15:30:13 +04:00
|
|
|
if (ehci_process_itd(ehci, &itd, entry) != 0) {
|
2010-12-03 18:17:28 +03:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-10-31 10:06:57 +04:00
|
|
|
put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
|
|
|
|
sizeof(EHCIitd) >> 2);
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_fetch_addr(ehci, async, itd.next);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-08-26 16:13:48 +04:00
|
|
|
static int ehci_state_fetchsitd(EHCIState *ehci, int async)
|
|
|
|
{
|
|
|
|
uint32_t entry;
|
|
|
|
EHCIsitd sitd;
|
|
|
|
|
|
|
|
assert(!async);
|
|
|
|
entry = ehci_get_fetch_addr(ehci, async);
|
|
|
|
|
2012-11-15 16:07:49 +04:00
|
|
|
if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
|
|
|
|
sizeof(EHCIsitd) >> 2) < 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
2011-08-26 16:13:48 +04:00
|
|
|
ehci_trace_sitd(ehci, entry, &sitd);
|
|
|
|
|
|
|
|
if (!(sitd.results & SITD_RESULTS_ACTIVE)) {
|
|
|
|
/* siTD is not active, nothing to do */;
|
|
|
|
} else {
|
|
|
|
/* TODO: split transfers are not implemented */
|
|
|
|
fprintf(stderr, "WARNING: Skipping active siTD\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
ehci_set_fetch_addr(ehci, async, sitd.next);
|
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* Section 4.10.2 - paragraph 3 */
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_advqueue(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
/* TO-DO: 4.10.2 - paragraph 2
|
|
|
|
* if I-bit is set to 1 and QH is not active
|
|
|
|
* go to horizontal QH
|
|
|
|
*/
|
|
|
|
if (I-bit set) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* want data and alt-next qTD is valid
|
|
|
|
*/
|
2011-05-19 12:49:03 +04:00
|
|
|
if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
|
|
|
|
(NLPTR_TBIT(q->qh.altnext_qtd) == 0)) {
|
|
|
|
q->qtdaddr = q->qh.altnext_qtd;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* next qTD is valid
|
|
|
|
*/
|
2012-03-03 00:27:09 +04:00
|
|
|
} else if (NLPTR_TBIT(q->qh.next_qtd) == 0) {
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qtdaddr = q->qh.next_qtd;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* no valid qTD, try next QH
|
|
|
|
*/
|
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Section 4.10.2 - paragraph 4 */
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_fetchqtd(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIqtd qtd;
|
|
|
|
EHCIPacket *p;
|
2012-10-24 20:14:04 +04:00
|
|
|
int again = 1;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-11-15 16:07:49 +04:00
|
|
|
if (get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd,
|
|
|
|
sizeof(EHCIqtd) >> 2) < 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
2012-05-09 19:06:36 +04:00
|
|
|
ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-10 16:12:38 +04:00
|
|
|
p = QTAILQ_FIRST(&q->packets);
|
|
|
|
if (p != NULL) {
|
2012-12-14 17:35:23 +04:00
|
|
|
if (!ehci_verify_qtd(p, &qtd)) {
|
2012-08-21 16:03:09 +04:00
|
|
|
ehci_cancel_queue(q);
|
2012-12-14 17:35:24 +04:00
|
|
|
if (qtd.token & QTD_TOKEN_ACTIVE) {
|
|
|
|
ehci_trace_guest_bug(q->ehci, "guest updated active qTD");
|
|
|
|
}
|
2012-08-21 16:03:09 +04:00
|
|
|
p = NULL;
|
|
|
|
} else {
|
|
|
|
p->qtd = qtd;
|
|
|
|
ehci_qh_do_overlay(q);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
|
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
|
|
|
} else if (p != NULL) {
|
2012-08-30 13:20:51 +04:00
|
|
|
switch (p->async) {
|
|
|
|
case EHCI_ASYNC_NONE:
|
2012-09-03 13:01:13 +04:00
|
|
|
case EHCI_ASYNC_INITIALIZED:
|
ehci: Fix interrupt packet MULT handling
There are several issues with our handling of the MULT epcap field
of interrupt qhs, which this patch fixes.
1) When we don't execute a transaction because of the transaction counter
being 0, p->async stays EHCI_ASYNC_NONE, and the next time we process the
same qtd we hit an assert in ehci_state_fetchqtd because of this. Even though
I believe that this is caused by 3 below, this patch still removes the assert,
as that can still happen without 3, when multiple packets are queued for the
same interrupt ep.
2) We only *check* the transaction counter from ehci_state_execute, any
packets queued up by fill_queue bypass this check. This is fixed by not calling
fill_queue for interrupt packets.
3) Some versions of Windows set the MULT field of the qh to 0, which is a
clear violation of the EHCI spec, but still they do it. This means that we
will never execute a qtd for these, making interrupt ep-s on USB-2 devices
not work, and after recent changes, triggering 1).
So far we've stored the transaction counter in our copy of the mult field,
but with this beginnig at 0 already when dealing with these version of windows
this won't work. So this patch adds a transact_ctr field to our qh struct,
and sets this to the MULT field value on fetchqh. When the MULT field value
is 0, we set it to 4. Assuming that windows gets way with setting it to 0,
by the actual hardware going horizontal on a 1 -> 0 transition, which will
give it 4 transactions (MULT goes from 0 - 3).
Note that we cannot stop on detecting the 1 -> 0 transition, as our decrement
of the transaction counter, and checking for it are done in 2 different places.
Reported-by: Shawn Starr <shawn.starr@rogers.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-09-20 19:38:07 +04:00
|
|
|
/* Not yet executed (MULT), or previously nacked (int) packet */
|
2012-09-03 13:01:13 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTE);
|
|
|
|
break;
|
2012-08-30 13:20:51 +04:00
|
|
|
case EHCI_ASYNC_INFLIGHT:
|
2012-10-24 20:14:04 +04:00
|
|
|
/* Check if the guest has added new tds to the queue */
|
2012-11-01 20:15:03 +04:00
|
|
|
again = ehci_fill_queue(QTAILQ_LAST(&q->packets, pkts_head));
|
2012-09-03 13:01:13 +04:00
|
|
|
/* Unfinished async handled packet, go horizontal */
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2012-08-30 13:20:51 +04:00
|
|
|
break;
|
|
|
|
case EHCI_ASYNC_FINISHED:
|
2012-12-14 17:35:27 +04:00
|
|
|
/* Complete executing of the packet */
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTING);
|
2012-08-30 13:20:51 +04:00
|
|
|
break;
|
2012-05-10 16:12:38 +04:00
|
|
|
}
|
2012-08-21 16:03:09 +04:00
|
|
|
} else {
|
2012-05-09 19:06:36 +04:00
|
|
|
p = ehci_alloc_packet(q);
|
|
|
|
p->qtdaddr = q->qtdaddr;
|
|
|
|
p->qtd = qtd;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTE);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_horizqh(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int again = 0;
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
if (ehci_get_fetch_addr(q->ehci, q->async) != q->qh.next) {
|
|
|
|
ehci_set_fetch_addr(q->ehci, q->async, q->qh.next);
|
|
|
|
ehci_set_state(q->ehci, q->async, EST_FETCHENTRY);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2012-11-01 20:15:03 +04:00
|
|
|
/* Returns "again" */
|
2012-09-03 13:35:58 +04:00
|
|
|
static int ehci_fill_queue(EHCIPacket *p)
|
2012-05-10 16:12:38 +04:00
|
|
|
{
|
2012-10-24 20:14:07 +04:00
|
|
|
USBEndpoint *ep = p->packet.ep;
|
2012-05-10 16:12:38 +04:00
|
|
|
EHCIQueue *q = p->queue;
|
|
|
|
EHCIqtd qtd = p->qtd;
|
2012-11-14 20:21:38 +04:00
|
|
|
uint32_t qtdaddr;
|
2012-05-10 16:12:38 +04:00
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (NLPTR_TBIT(qtd.next) != 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
qtdaddr = qtd.next;
|
2012-10-24 20:14:03 +04:00
|
|
|
/*
|
|
|
|
* Detect circular td lists, Windows creates these, counting on the
|
|
|
|
* active bit going low after execution to make the queue stop.
|
|
|
|
*/
|
2012-11-14 20:21:38 +04:00
|
|
|
QTAILQ_FOREACH(p, &q->packets, next) {
|
|
|
|
if (p->qtdaddr == qtdaddr) {
|
|
|
|
goto leave;
|
|
|
|
}
|
2012-10-24 20:14:03 +04:00
|
|
|
}
|
2012-11-15 16:07:49 +04:00
|
|
|
if (get_dwords(q->ehci, NLPTR_GET(qtdaddr),
|
|
|
|
(uint32_t *) &qtd, sizeof(EHCIqtd) >> 2) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2012-05-10 16:12:38 +04:00
|
|
|
ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd);
|
|
|
|
if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
|
|
|
|
break;
|
|
|
|
}
|
2012-12-14 17:35:29 +04:00
|
|
|
if (!ehci_verify_pid(q, &qtd)) {
|
|
|
|
ehci_trace_guest_bug(q->ehci, "guest queued token with wrong pid");
|
|
|
|
break;
|
|
|
|
}
|
2012-05-10 16:12:38 +04:00
|
|
|
p = ehci_alloc_packet(q);
|
|
|
|
p->qtdaddr = qtdaddr;
|
|
|
|
p->qtd = qtd;
|
2012-11-01 20:15:03 +04:00
|
|
|
if (ehci_execute(p, "queue") == -1) {
|
|
|
|
return -1;
|
2012-09-03 13:35:58 +04:00
|
|
|
}
|
2012-11-01 20:15:03 +04:00
|
|
|
assert(p->packet.status == USB_RET_ASYNC);
|
2012-05-10 16:12:38 +04:00
|
|
|
p->async = EHCI_ASYNC_INFLIGHT;
|
|
|
|
}
|
2012-11-14 20:21:38 +04:00
|
|
|
leave:
|
2012-11-01 20:15:03 +04:00
|
|
|
usb_device_flush_ep_queue(ep->dev, ep);
|
|
|
|
return 1;
|
2012-05-10 16:12:38 +04:00
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_execute(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2010-12-03 18:17:28 +03:00
|
|
|
int again = 0;
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (ehci_qh_do_overlay(q) != 0) {
|
2010-12-03 18:17:28 +03:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO verify enough time remains in the uframe as in 4.4.1.1
|
|
|
|
// TODO write back ptr to async list when done or out of time
|
|
|
|
|
ehci: Fix interrupt packet MULT handling
There are several issues with our handling of the MULT epcap field
of interrupt qhs, which this patch fixes.
1) When we don't execute a transaction because of the transaction counter
being 0, p->async stays EHCI_ASYNC_NONE, and the next time we process the
same qtd we hit an assert in ehci_state_fetchqtd because of this. Even though
I believe that this is caused by 3 below, this patch still removes the assert,
as that can still happen without 3, when multiple packets are queued for the
same interrupt ep.
2) We only *check* the transaction counter from ehci_state_execute, any
packets queued up by fill_queue bypass this check. This is fixed by not calling
fill_queue for interrupt packets.
3) Some versions of Windows set the MULT field of the qh to 0, which is a
clear violation of the EHCI spec, but still they do it. This means that we
will never execute a qtd for these, making interrupt ep-s on USB-2 devices
not work, and after recent changes, triggering 1).
So far we've stored the transaction counter in our copy of the mult field,
but with this beginnig at 0 already when dealing with these version of windows
this won't work. So this patch adds a transact_ctr field to our qh struct,
and sets this to the MULT field value on fetchqh. When the MULT field value
is 0, we set it to 4. Assuming that windows gets way with setting it to 0,
by the actual hardware going horizontal on a 1 -> 0 transition, which will
give it 4 transactions (MULT goes from 0 - 3).
Note that we cannot stop on detecting the 1 -> 0 transition, as our decrement
of the transaction counter, and checking for it are done in 2 different places.
Reported-by: Shawn Starr <shawn.starr@rogers.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-09-20 19:38:07 +04:00
|
|
|
/* 4.10.3, bottom of page 82, go horizontal on transaction counter == 0 */
|
|
|
|
if (!q->async && q->transact_ctr == 0) {
|
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
|
|
|
again = 1;
|
|
|
|
goto out;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
if (q->async) {
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_usbsts(q->ehci, USBSTS_REC);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-11-01 20:15:03 +04:00
|
|
|
again = ehci_execute(p, "process");
|
|
|
|
if (again == -1) {
|
2010-12-03 18:17:28 +03:00
|
|
|
goto out;
|
|
|
|
}
|
2012-11-01 20:15:03 +04:00
|
|
|
if (p->packet.status == USB_RET_ASYNC) {
|
2011-05-19 19:56:19 +04:00
|
|
|
ehci_flush_qh(q);
|
2012-05-10 16:12:38 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "async");
|
2012-05-09 19:06:36 +04:00
|
|
|
p->async = EHCI_ASYNC_INFLIGHT;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
ehci: Fix interrupt packet MULT handling
There are several issues with our handling of the MULT epcap field
of interrupt qhs, which this patch fixes.
1) When we don't execute a transaction because of the transaction counter
being 0, p->async stays EHCI_ASYNC_NONE, and the next time we process the
same qtd we hit an assert in ehci_state_fetchqtd because of this. Even though
I believe that this is caused by 3 below, this patch still removes the assert,
as that can still happen without 3, when multiple packets are queued for the
same interrupt ep.
2) We only *check* the transaction counter from ehci_state_execute, any
packets queued up by fill_queue bypass this check. This is fixed by not calling
fill_queue for interrupt packets.
3) Some versions of Windows set the MULT field of the qh to 0, which is a
clear violation of the EHCI spec, but still they do it. This means that we
will never execute a qtd for these, making interrupt ep-s on USB-2 devices
not work, and after recent changes, triggering 1).
So far we've stored the transaction counter in our copy of the mult field,
but with this beginnig at 0 already when dealing with these version of windows
this won't work. So this patch adds a transact_ctr field to our qh struct,
and sets this to the MULT field value on fetchqh. When the MULT field value
is 0, we set it to 4. Assuming that windows gets way with setting it to 0,
by the actual hardware going horizontal on a 1 -> 0 transition, which will
give it 4 transactions (MULT goes from 0 - 3).
Note that we cannot stop on detecting the 1 -> 0 transition, as our decrement
of the transaction counter, and checking for it are done in 2 different places.
Reported-by: Shawn Starr <shawn.starr@rogers.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-09-20 19:38:07 +04:00
|
|
|
if (q->async) {
|
2012-11-01 20:15:03 +04:00
|
|
|
again = ehci_fill_queue(p);
|
ehci: Fix interrupt packet MULT handling
There are several issues with our handling of the MULT epcap field
of interrupt qhs, which this patch fixes.
1) When we don't execute a transaction because of the transaction counter
being 0, p->async stays EHCI_ASYNC_NONE, and the next time we process the
same qtd we hit an assert in ehci_state_fetchqtd because of this. Even though
I believe that this is caused by 3 below, this patch still removes the assert,
as that can still happen without 3, when multiple packets are queued for the
same interrupt ep.
2) We only *check* the transaction counter from ehci_state_execute, any
packets queued up by fill_queue bypass this check. This is fixed by not calling
fill_queue for interrupt packets.
3) Some versions of Windows set the MULT field of the qh to 0, which is a
clear violation of the EHCI spec, but still they do it. This means that we
will never execute a qtd for these, making interrupt ep-s on USB-2 devices
not work, and after recent changes, triggering 1).
So far we've stored the transaction counter in our copy of the mult field,
but with this beginnig at 0 already when dealing with these version of windows
this won't work. So this patch adds a transact_ctr field to our qh struct,
and sets this to the MULT field value on fetchqh. When the MULT field value
is 0, we set it to 4. Assuming that windows gets way with setting it to 0,
by the actual hardware going horizontal on a 1 -> 0 transition, which will
give it 4 transactions (MULT goes from 0 - 3).
Note that we cannot stop on detecting the 1 -> 0 transition, as our decrement
of the transaction counter, and checking for it are done in 2 different places.
Reported-by: Shawn Starr <shawn.starr@rogers.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-09-20 19:38:07 +04:00
|
|
|
} else {
|
|
|
|
again = 1;
|
|
|
|
}
|
2011-05-19 19:56:19 +04:00
|
|
|
goto out;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTING);
|
2011-05-19 19:56:19 +04:00
|
|
|
again = 1;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
out:
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_executing(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_execute_complete(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
ehci: Fix interrupt packet MULT handling
There are several issues with our handling of the MULT epcap field
of interrupt qhs, which this patch fixes.
1) When we don't execute a transaction because of the transaction counter
being 0, p->async stays EHCI_ASYNC_NONE, and the next time we process the
same qtd we hit an assert in ehci_state_fetchqtd because of this. Even though
I believe that this is caused by 3 below, this patch still removes the assert,
as that can still happen without 3, when multiple packets are queued for the
same interrupt ep.
2) We only *check* the transaction counter from ehci_state_execute, any
packets queued up by fill_queue bypass this check. This is fixed by not calling
fill_queue for interrupt packets.
3) Some versions of Windows set the MULT field of the qh to 0, which is a
clear violation of the EHCI spec, but still they do it. This means that we
will never execute a qtd for these, making interrupt ep-s on USB-2 devices
not work, and after recent changes, triggering 1).
So far we've stored the transaction counter in our copy of the mult field,
but with this beginnig at 0 already when dealing with these version of windows
this won't work. So this patch adds a transact_ctr field to our qh struct,
and sets this to the MULT field value on fetchqh. When the MULT field value
is 0, we set it to 4. Assuming that windows gets way with setting it to 0,
by the actual hardware going horizontal on a 1 -> 0 transition, which will
give it 4 transactions (MULT goes from 0 - 3).
Note that we cannot stop on detecting the 1 -> 0 transition, as our decrement
of the transaction counter, and checking for it are done in 2 different places.
Reported-by: Shawn Starr <shawn.starr@rogers.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-09-20 19:38:07 +04:00
|
|
|
/* 4.10.3 */
|
|
|
|
if (!q->async && q->transact_ctr > 0) {
|
|
|
|
q->transact_ctr--;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 4.10.5 */
|
2012-11-01 20:15:04 +04:00
|
|
|
if (p->packet.status == USB_RET_NAK) {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_WRITEBACK);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
ehci_flush_qh(q);
|
2012-08-17 13:39:17 +04:00
|
|
|
return 1;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_writeback(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2012-06-19 15:53:28 +04:00
|
|
|
uint32_t *qtd, addr;
|
2010-12-03 18:17:28 +03:00
|
|
|
int again = 0;
|
|
|
|
|
|
|
|
/* Write back the QTD from the QH area */
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
|
|
|
ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd);
|
2012-06-19 15:53:28 +04:00
|
|
|
qtd = (uint32_t *) &q->qh.next_qtd;
|
|
|
|
addr = NLPTR_GET(p->qtdaddr);
|
|
|
|
put_dwords(q->ehci, addr + 2 * sizeof(uint32_t), qtd + 2, 2);
|
2012-05-09 19:06:36 +04:00
|
|
|
ehci_free_packet(p);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-23 11:50:27 +04:00
|
|
|
/*
|
|
|
|
* EHCI specs say go horizontal here.
|
|
|
|
*
|
|
|
|
* We can also advance the queue here for performance reasons. We
|
|
|
|
* need to take care to only take that shortcut in case we've
|
|
|
|
* processed the qtd just written back without errors, i.e. halt
|
|
|
|
* bit is clear.
|
2010-12-03 18:17:28 +03:00
|
|
|
*/
|
2011-05-23 11:50:27 +04:00
|
|
|
if (q->qh.token & QTD_TOKEN_HALT) {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2011-05-23 11:50:27 +04:00
|
|
|
again = 1;
|
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_ADVANCEQUEUE);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
2011-05-23 11:50:27 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is the state machine that is common to both async and periodic
|
|
|
|
*/
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static void ehci_advance_state(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-19 12:49:03 +04:00
|
|
|
EHCIQueue *q = NULL;
|
2010-12-03 18:17:28 +03:00
|
|
|
int again;
|
|
|
|
|
|
|
|
do {
|
2011-05-18 16:23:35 +04:00
|
|
|
switch(ehci_get_state(ehci, async)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_WAITLISTHEAD:
|
2011-05-18 16:23:35 +04:00
|
|
|
again = ehci_state_waitlisthead(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHENTRY:
|
2011-05-18 16:23:35 +04:00
|
|
|
again = ehci_state_fetchentry(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHQH:
|
2011-05-19 12:49:03 +04:00
|
|
|
q = ehci_state_fetchqh(ehci, async);
|
2012-05-11 11:05:15 +04:00
|
|
|
if (q != NULL) {
|
|
|
|
assert(q->async == async);
|
|
|
|
again = 1;
|
|
|
|
} else {
|
|
|
|
again = 0;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHITD:
|
2011-05-18 16:23:35 +04:00
|
|
|
again = ehci_state_fetchitd(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
2011-08-26 16:13:48 +04:00
|
|
|
case EST_FETCHSITD:
|
|
|
|
again = ehci_state_fetchsitd(ehci, async);
|
|
|
|
break;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_ADVANCEQUEUE:
|
2013-01-10 17:33:23 +04:00
|
|
|
assert(q != NULL);
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_advqueue(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHQTD:
|
2013-01-10 17:33:23 +04:00
|
|
|
assert(q != NULL);
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_fetchqtd(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_HORIZONTALQH:
|
2013-01-10 17:33:23 +04:00
|
|
|
assert(q != NULL);
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_horizqh(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_EXECUTE:
|
2013-01-10 17:33:23 +04:00
|
|
|
assert(q != NULL);
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_execute(q);
|
2012-05-24 15:34:02 +04:00
|
|
|
if (async) {
|
|
|
|
ehci->async_stepdown = 0;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_EXECUTING:
|
2011-05-19 19:56:19 +04:00
|
|
|
assert(q != NULL);
|
2012-05-24 15:34:02 +04:00
|
|
|
if (async) {
|
|
|
|
ehci->async_stepdown = 0;
|
|
|
|
}
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_executing(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_WRITEBACK:
|
2011-11-09 15:20:20 +04:00
|
|
|
assert(q != NULL);
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_writeback(q);
|
2012-11-17 15:47:17 +04:00
|
|
|
if (!async) {
|
|
|
|
ehci->periodic_sched_active = PERIODIC_ACTIVE;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Bad state!\n");
|
|
|
|
again = -1;
|
2013-07-25 20:21:28 +04:00
|
|
|
g_assert_not_reached();
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (again < 0) {
|
|
|
|
fprintf(stderr, "processing error - resetting ehci HC\n");
|
|
|
|
ehci_reset(ehci);
|
|
|
|
again = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (again);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_advance_async_state(EHCIState *ehci)
|
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
const int async = 1;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
switch(ehci_get_state(ehci, async)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_INACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!ehci_async_enabled(ehci)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
// No break, fall through to ACTIVE
|
|
|
|
|
|
|
|
case EST_ACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!ehci_async_enabled(ehci)) {
|
2012-03-03 00:27:13 +04:00
|
|
|
ehci_queues_rip_all(ehci, async);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_INACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
/* make sure guest has acknowledged the doorbell interrupt */
|
2010-12-03 18:17:28 +03:00
|
|
|
/* TO-DO: is this really needed? */
|
|
|
|
if (ehci->usbsts & USBSTS_IAA) {
|
|
|
|
DPRINTF("IAA status bit still set.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check that address register has been set */
|
|
|
|
if (ehci->asynclistaddr == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_WAITLISTHEAD);
|
|
|
|
ehci_advance_state(ehci, async);
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
|
|
|
|
/* If the doorbell is set, the guest wants to make a change to the
|
|
|
|
* schedule. The host controller needs to release cached data.
|
|
|
|
* (section 4.8.2)
|
|
|
|
*/
|
|
|
|
if (ehci->usbcmd & USBCMD_IAAD) {
|
|
|
|
/* Remove all unseen qhs from the async qhs queue */
|
2012-09-12 17:08:32 +04:00
|
|
|
ehci_queues_rip_unseen(ehci, async);
|
2012-08-31 14:41:43 +04:00
|
|
|
trace_usb_ehci_doorbell_ack();
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
ehci->usbcmd &= ~USBCMD_IAAD;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_IAA);
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* this should only be due to a developer mistake */
|
|
|
|
fprintf(stderr, "ehci: Bad asynchronous state %d. "
|
|
|
|
"Resetting to active\n", ehci->astate);
|
2013-07-25 20:21:28 +04:00
|
|
|
g_assert_not_reached();
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_advance_periodic_state(EHCIState *ehci)
|
|
|
|
{
|
|
|
|
uint32_t entry;
|
|
|
|
uint32_t list;
|
2012-03-03 00:27:10 +04:00
|
|
|
const int async = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
// 4.6
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
switch(ehci_get_state(ehci, async)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_INACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!(ehci->frindex & 7) && ehci_periodic_enabled(ehci)) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
// No break, fall through to ACTIVE
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_ACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!(ehci->frindex & 7) && !ehci_periodic_enabled(ehci)) {
|
2012-03-03 00:27:13 +04:00
|
|
|
ehci_queues_rip_all(ehci, async);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_INACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
list = ehci->periodiclistbase & 0xfffff000;
|
|
|
|
/* check that register has been set */
|
|
|
|
if (list == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list |= ((ehci->frindex & 0x1ff8) >> 1);
|
|
|
|
|
2012-11-15 16:07:49 +04:00
|
|
|
if (get_dwords(ehci, list, &entry, 1) < 0) {
|
|
|
|
break;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
|
|
|
|
ehci->frindex / 8, list, entry);
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_fetch_addr(ehci, async,entry);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
|
|
|
ehci_advance_state(ehci, async);
|
2012-09-12 17:08:32 +04:00
|
|
|
ehci_queues_rip_unused(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* this should only be due to a developer mistake */
|
|
|
|
fprintf(stderr, "ehci: Bad periodic state %d. "
|
|
|
|
"Resetting to active\n", ehci->pstate);
|
2013-07-25 20:21:28 +04:00
|
|
|
g_assert_not_reached();
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-18 17:17:02 +04:00
|
|
|
static void ehci_update_frindex(EHCIState *ehci, int uframes)
|
2012-05-24 15:28:32 +04:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2012-12-18 17:17:02 +04:00
|
|
|
if (!ehci_enabled(ehci) && ehci->pstate == EST_INACTIVE) {
|
2012-05-24 15:28:32 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-18 17:17:02 +04:00
|
|
|
for (i = 0; i < uframes; i++) {
|
|
|
|
ehci->frindex++;
|
2012-05-24 15:28:32 +04:00
|
|
|
|
|
|
|
if (ehci->frindex == 0x00002000) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_FLR);
|
2012-05-24 15:28:32 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ehci->frindex == 0x00004000) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_FLR);
|
2012-05-24 15:28:32 +04:00
|
|
|
ehci->frindex = 0;
|
2012-09-10 14:44:10 +04:00
|
|
|
if (ehci->usbsts_frindex >= 0x00004000) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci->usbsts_frindex -= 0x00004000;
|
|
|
|
} else {
|
|
|
|
ehci->usbsts_frindex = 0;
|
|
|
|
}
|
2012-05-24 15:28:32 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
static void ehci_frame_timer(void *opaque)
|
|
|
|
{
|
|
|
|
EHCIState *ehci = opaque;
|
2012-07-11 13:06:05 +04:00
|
|
|
int need_timer = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
int64_t expire_time, t_now;
|
2011-05-31 14:23:13 +04:00
|
|
|
uint64_t ns_elapsed;
|
2012-12-18 17:17:02 +04:00
|
|
|
int uframes, skipped_uframes;
|
2010-12-03 18:17:28 +03:00
|
|
|
int i;
|
|
|
|
|
2013-08-21 19:03:08 +04:00
|
|
|
t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
2011-05-31 14:23:13 +04:00
|
|
|
ns_elapsed = t_now - ehci->last_run_ns;
|
2012-12-18 17:17:02 +04:00
|
|
|
uframes = ns_elapsed / UFRAME_TIMER_NS;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-24 15:34:02 +04:00
|
|
|
if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) {
|
2012-07-11 13:06:05 +04:00
|
|
|
need_timer++;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-12-18 17:17:02 +04:00
|
|
|
if (uframes > (ehci->maxframes * 8)) {
|
|
|
|
skipped_uframes = uframes - (ehci->maxframes * 8);
|
|
|
|
ehci_update_frindex(ehci, skipped_uframes);
|
|
|
|
ehci->last_run_ns += UFRAME_TIMER_NS * skipped_uframes;
|
|
|
|
uframes -= skipped_uframes;
|
|
|
|
DPRINTF("WARNING - EHCI skipped %d uframes\n", skipped_uframes);
|
2012-05-25 10:13:55 +04:00
|
|
|
}
|
|
|
|
|
2012-12-18 17:17:02 +04:00
|
|
|
for (i = 0; i < uframes; i++) {
|
2012-09-10 14:44:11 +04:00
|
|
|
/*
|
|
|
|
* If we're running behind schedule, we should not catch up
|
|
|
|
* too fast, as that will make some guests unhappy:
|
2012-12-18 17:17:02 +04:00
|
|
|
* 1) We must process a minimum of MIN_UFR_PER_TICK frames,
|
2012-09-10 14:44:11 +04:00
|
|
|
* otherwise we will never catch up
|
|
|
|
* 2) Process frames until the guest has requested an irq (IOC)
|
|
|
|
*/
|
2012-12-18 17:17:02 +04:00
|
|
|
if (i >= MIN_UFR_PER_TICK) {
|
2012-09-10 14:44:11 +04:00
|
|
|
ehci_commit_irq(ehci);
|
|
|
|
if ((ehci->usbsts & USBINTR_MASK) & ehci->usbintr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-11-17 15:47:17 +04:00
|
|
|
if (ehci->periodic_sched_active) {
|
|
|
|
ehci->periodic_sched_active--;
|
|
|
|
}
|
2012-05-24 15:34:02 +04:00
|
|
|
ehci_update_frindex(ehci, 1);
|
2012-12-18 17:17:02 +04:00
|
|
|
if ((ehci->frindex & 7) == 0) {
|
|
|
|
ehci_advance_periodic_state(ehci);
|
|
|
|
}
|
|
|
|
ehci->last_run_ns += UFRAME_TIMER_NS;
|
2012-05-24 15:34:02 +04:00
|
|
|
}
|
|
|
|
} else {
|
2012-11-17 15:47:17 +04:00
|
|
|
ehci->periodic_sched_active = 0;
|
2012-12-18 17:17:02 +04:00
|
|
|
ehci_update_frindex(ehci, uframes);
|
|
|
|
ehci->last_run_ns += UFRAME_TIMER_NS * uframes;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-11-17 15:47:17 +04:00
|
|
|
if (ehci->periodic_sched_active) {
|
|
|
|
ehci->async_stepdown = 0;
|
|
|
|
} else if (ehci->async_stepdown < ehci->maxframes / 2) {
|
|
|
|
ehci->async_stepdown++;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* Async is not inside loop since it executes everything it can once
|
|
|
|
* called
|
|
|
|
*/
|
2012-05-24 15:34:02 +04:00
|
|
|
if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) {
|
2012-07-11 13:06:05 +04:00
|
|
|
need_timer++;
|
2012-07-11 13:23:17 +04:00
|
|
|
ehci_advance_async_state(ehci);
|
2012-05-24 15:34:02 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_commit_irq(ehci);
|
|
|
|
if (ehci->usbsts_pending) {
|
|
|
|
need_timer++;
|
|
|
|
ehci->async_stepdown = 0;
|
2012-05-24 14:53:43 +04:00
|
|
|
}
|
2012-07-10 20:00:50 +04:00
|
|
|
|
2012-11-13 20:20:05 +04:00
|
|
|
if (ehci_enabled(ehci) && (ehci->usbintr & USBSTS_FLR)) {
|
|
|
|
need_timer++;
|
|
|
|
}
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
if (need_timer) {
|
2012-10-24 20:14:02 +04:00
|
|
|
/* 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)) {
|
2012-12-14 17:35:31 +04:00
|
|
|
expire_time = t_now + get_ticks_per_sec() / (FRAME_TIMER_FREQ * 4);
|
2012-10-24 20:14:02 +04:00
|
|
|
ehci->int_req_by_async = false;
|
|
|
|
} else {
|
|
|
|
expire_time = t_now + (get_ticks_per_sec()
|
2012-07-11 13:23:17 +04:00
|
|
|
* (ehci->async_stepdown+1) / FRAME_TIMER_FREQ);
|
2012-10-24 20:14:02 +04:00
|
|
|
}
|
2013-08-21 19:03:08 +04:00
|
|
|
timer_mod(ehci->frame_timer, expire_time);
|
2012-07-11 13:06:05 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-09-06 13:24:51 +04:00
|
|
|
static const MemoryRegionOps ehci_mmio_caps_ops = {
|
|
|
|
.read = ehci_caps_read,
|
|
|
|
.valid.min_access_size = 1,
|
|
|
|
.valid.max_access_size = 4,
|
|
|
|
.impl.min_access_size = 1,
|
|
|
|
.impl.max_access_size = 1,
|
|
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const MemoryRegionOps ehci_mmio_opreg_ops = {
|
|
|
|
.read = ehci_opreg_read,
|
|
|
|
.write = ehci_opreg_write,
|
|
|
|
.valid.min_access_size = 4,
|
|
|
|
.valid.max_access_size = 4,
|
|
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const MemoryRegionOps ehci_mmio_port_ops = {
|
|
|
|
.read = ehci_port_read,
|
|
|
|
.write = ehci_port_write,
|
|
|
|
.valid.min_access_size = 4,
|
|
|
|
.valid.max_access_size = 4,
|
2011-08-08 17:09:23 +04:00
|
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
2010-12-03 18:17:28 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static USBPortOps ehci_port_ops = {
|
|
|
|
.attach = ehci_attach,
|
|
|
|
.detach = ehci_detach,
|
2011-06-24 14:31:11 +04:00
|
|
|
.child_detach = ehci_child_detach,
|
2011-06-24 18:18:13 +04:00
|
|
|
.wakeup = ehci_wakeup,
|
2010-12-03 18:17:28 +03:00
|
|
|
.complete = ehci_async_complete_packet,
|
|
|
|
};
|
|
|
|
|
2014-08-29 16:40:08 +04:00
|
|
|
static USBBusOps ehci_bus_ops_companion = {
|
2011-06-24 18:18:13 +04:00
|
|
|
.register_companion = ehci_register_companion,
|
2012-11-17 15:47:17 +04:00
|
|
|
.wakeup_endpoint = ehci_wakeup_endpoint,
|
2011-05-23 19:37:12 +04:00
|
|
|
};
|
2014-08-29 16:40:08 +04:00
|
|
|
static USBBusOps ehci_bus_ops_standalone = {
|
|
|
|
.wakeup_endpoint = ehci_wakeup_endpoint,
|
|
|
|
};
|
2011-05-23 19:37:12 +04:00
|
|
|
|
2012-12-18 17:17:02 +04:00
|
|
|
static void usb_ehci_pre_save(void *opaque)
|
|
|
|
{
|
|
|
|
EHCIState *ehci = opaque;
|
|
|
|
uint32_t new_frindex;
|
|
|
|
|
|
|
|
/* Round down frindex to a multiple of 8 for migration compatibility */
|
|
|
|
new_frindex = ehci->frindex & ~7;
|
|
|
|
ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS;
|
|
|
|
ehci->frindex = new_frindex;
|
|
|
|
}
|
|
|
|
|
2012-05-14 15:55:44 +04:00
|
|
|
static int usb_ehci_post_load(void *opaque, int version_id)
|
|
|
|
{
|
|
|
|
EHCIState *s = opaque;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < NB_PORTS; i++) {
|
|
|
|
USBPort *companion = s->companion_ports[i];
|
|
|
|
if (companion == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (s->portsc[i] & PORTSC_POWNER) {
|
|
|
|
companion->dev = s->ports[i].dev;
|
|
|
|
} else {
|
|
|
|
companion->dev = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-09-12 17:08:33 +04:00
|
|
|
static void usb_ehci_vm_state_change(void *opaque, int running, RunState state)
|
|
|
|
{
|
|
|
|
EHCIState *ehci = opaque;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We don't migrate the EHCIQueue-s, instead we rebuild them for the
|
|
|
|
* schedule in guest memory. We must do the rebuilt ASAP, so that
|
|
|
|
* USB-devices which have async handled packages have a packet in the
|
|
|
|
* ep queue to match the completion with.
|
|
|
|
*/
|
|
|
|
if (state == RUN_STATE_RUNNING) {
|
|
|
|
ehci_advance_async_state(ehci);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The schedule rebuilt from guest memory could cause the migration dest
|
|
|
|
* to miss a QH unlink, and fail to cancel packets, since the unlinked QH
|
|
|
|
* will never have existed on the destination. Therefor we must flush the
|
|
|
|
* async schedule on savevm to catch any not yet noticed unlinks.
|
|
|
|
*/
|
|
|
|
if (state == RUN_STATE_SAVE_VM) {
|
|
|
|
ehci_advance_async_state(ehci);
|
|
|
|
ehci_queues_rip_unseen(ehci, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-30 15:20:06 +04:00
|
|
|
const VMStateDescription vmstate_ehci = {
|
2012-10-29 05:34:36 +04:00
|
|
|
.name = "ehci-core",
|
2012-08-15 15:55:40 +04:00
|
|
|
.version_id = 2,
|
|
|
|
.minimum_version_id = 1,
|
2012-12-18 17:17:02 +04:00
|
|
|
.pre_save = usb_ehci_pre_save,
|
2012-05-14 15:55:44 +04:00
|
|
|
.post_load = usb_ehci_post_load,
|
2014-04-16 15:31:26 +04:00
|
|
|
.fields = (VMStateField[]) {
|
2012-05-14 15:55:44 +04:00
|
|
|
/* mmio registers */
|
|
|
|
VMSTATE_UINT32(usbcmd, EHCIState),
|
|
|
|
VMSTATE_UINT32(usbsts, EHCIState),
|
2012-08-15 15:55:40 +04:00
|
|
|
VMSTATE_UINT32_V(usbsts_pending, EHCIState, 2),
|
|
|
|
VMSTATE_UINT32_V(usbsts_frindex, EHCIState, 2),
|
2012-05-14 15:55:44 +04:00
|
|
|
VMSTATE_UINT32(usbintr, EHCIState),
|
|
|
|
VMSTATE_UINT32(frindex, EHCIState),
|
|
|
|
VMSTATE_UINT32(ctrldssegment, EHCIState),
|
|
|
|
VMSTATE_UINT32(periodiclistbase, EHCIState),
|
|
|
|
VMSTATE_UINT32(asynclistaddr, EHCIState),
|
|
|
|
VMSTATE_UINT32(configflag, EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[0], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[1], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[2], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[3], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[4], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[5], EHCIState),
|
|
|
|
/* frame timer */
|
|
|
|
VMSTATE_TIMER(frame_timer, EHCIState),
|
|
|
|
VMSTATE_UINT64(last_run_ns, EHCIState),
|
|
|
|
VMSTATE_UINT32(async_stepdown, EHCIState),
|
|
|
|
/* schedule state */
|
|
|
|
VMSTATE_UINT32(astate, EHCIState),
|
|
|
|
VMSTATE_UINT32(pstate, EHCIState),
|
|
|
|
VMSTATE_UINT32(a_fetch_addr, EHCIState),
|
|
|
|
VMSTATE_UINT32(p_fetch_addr, EHCIState),
|
|
|
|
VMSTATE_END_OF_LIST()
|
|
|
|
}
|
2011-07-08 12:48:46 +04:00
|
|
|
};
|
|
|
|
|
2013-06-06 17:41:09 +04:00
|
|
|
void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2013-06-06 17:41:12 +04:00
|
|
|
if (s->portnr > NB_PORTS) {
|
|
|
|
error_setg(errp, "Too many ports! Max. port number is %d.",
|
|
|
|
NB_PORTS);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-29 16:40:08 +04:00
|
|
|
usb_bus_new(&s->bus, sizeof(s->bus), s->companion_enable ?
|
|
|
|
&ehci_bus_ops_companion : &ehci_bus_ops_standalone, dev);
|
2013-06-06 17:41:12 +04:00
|
|
|
for (i = 0; i < s->portnr; i++) {
|
2013-06-06 17:41:10 +04:00
|
|
|
usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops,
|
|
|
|
USB_SPEED_MASK_HIGH);
|
|
|
|
s->ports[i].dev = 0;
|
|
|
|
}
|
|
|
|
|
2013-08-21 19:03:08 +04:00
|
|
|
s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_frame_timer, s);
|
2013-06-06 17:41:10 +04:00
|
|
|
s->async_bh = qemu_bh_new(ehci_frame_timer, s);
|
2013-09-09 12:18:17 +04:00
|
|
|
s->device = dev;
|
2013-06-06 17:41:10 +04:00
|
|
|
|
|
|
|
qemu_register_reset(ehci_reset, s);
|
2014-06-04 12:31:50 +04:00
|
|
|
s->vmstate = qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s);
|
2013-06-06 17:41:10 +04:00
|
|
|
}
|
|
|
|
|
2014-06-04 12:31:51 +04:00
|
|
|
void usb_ehci_unrealize(EHCIState *s, DeviceState *dev, Error **errp)
|
|
|
|
{
|
2014-06-04 12:31:55 +04:00
|
|
|
trace_usb_ehci_unrealize();
|
|
|
|
|
2014-06-04 12:31:51 +04:00
|
|
|
if (s->frame_timer) {
|
|
|
|
timer_del(s->frame_timer);
|
|
|
|
timer_free(s->frame_timer);
|
|
|
|
s->frame_timer = NULL;
|
|
|
|
}
|
|
|
|
if (s->async_bh) {
|
|
|
|
qemu_bh_delete(s->async_bh);
|
|
|
|
}
|
|
|
|
|
|
|
|
ehci_queues_rip_all(s, 0);
|
|
|
|
ehci_queues_rip_all(s, 1);
|
|
|
|
|
|
|
|
memory_region_del_subregion(&s->mem, &s->mem_caps);
|
|
|
|
memory_region_del_subregion(&s->mem, &s->mem_opreg);
|
|
|
|
memory_region_del_subregion(&s->mem, &s->mem_ports);
|
|
|
|
|
|
|
|
usb_bus_release(&s->bus);
|
|
|
|
|
|
|
|
if (s->vmstate) {
|
|
|
|
qemu_del_vm_change_state_handler(s->vmstate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-06 17:41:10 +04:00
|
|
|
void usb_ehci_init(EHCIState *s, DeviceState *dev)
|
|
|
|
{
|
2012-09-06 13:24:51 +04:00
|
|
|
/* 2.2 host controller interface version */
|
2012-10-29 05:34:34 +04:00
|
|
|
s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase);
|
2012-09-06 13:24:51 +04:00
|
|
|
s->caps[0x01] = 0x00;
|
|
|
|
s->caps[0x02] = 0x00;
|
|
|
|
s->caps[0x03] = 0x01; /* HC version */
|
2013-06-06 17:41:12 +04:00
|
|
|
s->caps[0x04] = s->portnr; /* Number of downstream ports */
|
2012-09-06 13:24:51 +04:00
|
|
|
s->caps[0x05] = 0x00; /* No companion ports at present */
|
|
|
|
s->caps[0x06] = 0x00;
|
|
|
|
s->caps[0x07] = 0x00;
|
|
|
|
s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */
|
|
|
|
s->caps[0x0a] = 0x00;
|
|
|
|
s->caps[0x0b] = 0x00;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_INIT(&s->aqueues);
|
|
|
|
QTAILQ_INIT(&s->pqueues);
|
2012-07-06 14:09:33 +04:00
|
|
|
usb_packet_init(&s->ipacket);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2013-06-07 05:25:08 +04:00
|
|
|
memory_region_init(&s->mem, OBJECT(dev), "ehci", MMIO_SIZE);
|
|
|
|
memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s,
|
2012-10-29 05:34:34 +04:00
|
|
|
"capabilities", CAPA_SIZE);
|
2013-06-07 05:25:08 +04:00
|
|
|
memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s,
|
2013-06-06 17:41:12 +04:00
|
|
|
"operational", s->portscbase);
|
2013-06-07 05:25:08 +04:00
|
|
|
memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s,
|
2013-06-06 17:41:12 +04:00
|
|
|
"ports", 4 * s->portnr);
|
2012-09-06 13:24:51 +04:00
|
|
|
|
2012-10-29 05:34:34 +04:00
|
|
|
memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
|
|
|
|
memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
|
2013-06-06 17:41:12 +04:00
|
|
|
memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase,
|
2012-10-29 05:34:34 +04:00
|
|
|
&s->mem_ports);
|
2012-10-29 05:34:36 +04:00
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/*
|
|
|
|
* vim: expandtab ts=4
|
|
|
|
*/
|