usb-uhci: add UHCIQueue

UHCIAsync structs (in-flight requests) grouped in UHCIQueue now.
Each (active) usb endpoint gets its own UHCIQueue.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Gerd Hoffmann 2012-01-27 14:17:06 +01:00
parent 326700e35d
commit f8af1e889b

View file

@ -95,23 +95,32 @@ static const char *pid2str(int pid)
#endif #endif
typedef struct UHCIState UHCIState; typedef struct UHCIState UHCIState;
typedef struct UHCIAsync UHCIAsync;
typedef struct UHCIQueue UHCIQueue;
/* /*
* Pending async transaction. * Pending async transaction.
* 'packet' must be the first field because completion * 'packet' must be the first field because completion
* handler does "(UHCIAsync *) pkt" cast. * handler does "(UHCIAsync *) pkt" cast.
*/ */
typedef struct UHCIAsync {
struct UHCIAsync {
USBPacket packet; USBPacket packet;
QEMUSGList sgl; QEMUSGList sgl;
UHCIState *uhci; UHCIQueue *queue;
QTAILQ_ENTRY(UHCIAsync) next; QTAILQ_ENTRY(UHCIAsync) next;
uint32_t td; uint32_t td;
uint32_t token;
int8_t valid;
uint8_t isoc; uint8_t isoc;
uint8_t done; uint8_t done;
} UHCIAsync; };
struct UHCIQueue {
uint32_t token;
UHCIState *uhci;
QTAILQ_ENTRY(UHCIQueue) next;
QTAILQ_HEAD(, UHCIAsync) asyncs;
int8_t valid;
};
typedef struct UHCIPort { typedef struct UHCIPort {
USBPort port; USBPort port;
@ -137,7 +146,7 @@ struct UHCIState {
uint32_t pending_int_mask; uint32_t pending_int_mask;
/* Active packets */ /* Active packets */
QTAILQ_HEAD(,UHCIAsync) async_pending; QTAILQ_HEAD(, UHCIQueue) queues;
uint8_t num_ports_vmstate; uint8_t num_ports_vmstate;
/* Properties */ /* Properties */
@ -157,56 +166,90 @@ typedef struct UHCI_QH {
uint32_t el_link; uint32_t el_link;
} UHCI_QH; } UHCI_QH;
static UHCIAsync *uhci_async_alloc(UHCIState *s) static inline int32_t uhci_queue_token(UHCI_TD *td)
{
/* covers ep, dev, pid -> identifies the endpoint */
return td->token & 0x7ffff;
}
static UHCIQueue *uhci_queue_get(UHCIState *s, UHCI_TD *td)
{
uint32_t token = uhci_queue_token(td);
UHCIQueue *queue;
QTAILQ_FOREACH(queue, &s->queues, next) {
if (queue->token == token) {
return queue;
}
}
queue = g_new0(UHCIQueue, 1);
queue->uhci = s;
queue->token = token;
QTAILQ_INIT(&queue->asyncs);
QTAILQ_INSERT_HEAD(&s->queues, queue, next);
return queue;
}
static void uhci_queue_free(UHCIQueue *queue)
{
UHCIState *s = queue->uhci;
QTAILQ_REMOVE(&s->queues, queue, next);
g_free(queue);
}
static UHCIAsync *uhci_async_alloc(UHCIQueue *queue)
{ {
UHCIAsync *async = g_new0(UHCIAsync, 1); UHCIAsync *async = g_new0(UHCIAsync, 1);
async->uhci = s; async->queue = queue;
usb_packet_init(&async->packet); usb_packet_init(&async->packet);
pci_dma_sglist_init(&async->sgl, &s->dev, 1); pci_dma_sglist_init(&async->sgl, &queue->uhci->dev, 1);
return async; return async;
} }
static void uhci_async_free(UHCIState *s, UHCIAsync *async) static void uhci_async_free(UHCIAsync *async)
{ {
usb_packet_cleanup(&async->packet); usb_packet_cleanup(&async->packet);
qemu_sglist_destroy(&async->sgl); qemu_sglist_destroy(&async->sgl);
g_free(async); g_free(async);
} }
static void uhci_async_link(UHCIState *s, UHCIAsync *async) static void uhci_async_link(UHCIAsync *async)
{ {
QTAILQ_INSERT_HEAD(&s->async_pending, async, next); UHCIQueue *queue = async->queue;
QTAILQ_INSERT_TAIL(&queue->asyncs, async, next);
} }
static void uhci_async_unlink(UHCIState *s, UHCIAsync *async) static void uhci_async_unlink(UHCIAsync *async)
{ {
QTAILQ_REMOVE(&s->async_pending, async, next); UHCIQueue *queue = async->queue;
QTAILQ_REMOVE(&queue->asyncs, async, next);
} }
static void uhci_async_cancel(UHCIState *s, UHCIAsync *async) static void uhci_async_cancel(UHCIAsync *async)
{ {
DPRINTF("uhci: cancel td 0x%x token 0x%x done %u\n", DPRINTF("uhci: cancel td 0x%x token 0x%x done %u\n",
async->td, async->token, async->done); async->td, async->token, async->done);
if (!async->done) if (!async->done)
usb_cancel_packet(&async->packet); usb_cancel_packet(&async->packet);
uhci_async_free(s, async); uhci_async_free(async);
} }
/* /*
* Mark all outstanding async packets as invalid. * Mark all outstanding async packets as invalid.
* This is used for canceling them when TDs are removed by the HCD. * This is used for canceling them when TDs are removed by the HCD.
*/ */
static UHCIAsync *uhci_async_validate_begin(UHCIState *s) static void uhci_async_validate_begin(UHCIState *s)
{ {
UHCIAsync *async; UHCIQueue *queue;
QTAILQ_FOREACH(async, &s->async_pending, next) { QTAILQ_FOREACH(queue, &s->queues, next) {
async->valid--; queue->valid--;
} }
return NULL;
} }
/* /*
@ -214,77 +257,74 @@ static UHCIAsync *uhci_async_validate_begin(UHCIState *s)
*/ */
static void uhci_async_validate_end(UHCIState *s) static void uhci_async_validate_end(UHCIState *s)
{ {
UHCIAsync *curr, *n; UHCIQueue *queue, *n;
UHCIAsync *async;
QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) { QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) {
if (curr->valid > 0) { if (queue->valid > 0) {
continue; continue;
} }
uhci_async_unlink(s, curr); while (!QTAILQ_EMPTY(&queue->asyncs)) {
uhci_async_cancel(s, curr); async = QTAILQ_FIRST(&queue->asyncs);
uhci_async_unlink(async);
uhci_async_cancel(async);
}
uhci_queue_free(queue);
} }
} }
static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev) static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev)
{ {
UHCIQueue *queue;
UHCIAsync *curr, *n; UHCIAsync *curr, *n;
QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) { QTAILQ_FOREACH(queue, &s->queues, next) {
if (!usb_packet_is_inflight(&curr->packet) || QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) {
curr->packet.ep->dev != dev) { if (!usb_packet_is_inflight(&curr->packet) ||
continue; curr->packet.ep->dev != dev) {
continue;
}
uhci_async_unlink(curr);
uhci_async_cancel(curr);
} }
uhci_async_unlink(s, curr);
uhci_async_cancel(s, curr);
} }
} }
static void uhci_async_cancel_all(UHCIState *s) static void uhci_async_cancel_all(UHCIState *s)
{ {
UHCIQueue *queue;
UHCIAsync *curr, *n; UHCIAsync *curr, *n;
QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) { QTAILQ_FOREACH(queue, &s->queues, next) {
uhci_async_unlink(s, curr); QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) {
uhci_async_cancel(s, curr); uhci_async_unlink(curr);
uhci_async_cancel(curr);
}
} }
} }
static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, uint32_t token) static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, UHCI_TD *td)
{ {
uint32_t token = uhci_queue_token(td);
UHCIQueue *queue;
UHCIAsync *async; UHCIAsync *async;
UHCIAsync *match = NULL;
int count = 0;
/* QTAILQ_FOREACH(queue, &s->queues, next) {
* We're looking for the best match here. ie both td addr and token. if (queue->token == token) {
* Otherwise we return last good match. ie just token. break;
* It's ok to match just token because it identifies the transaction
* rather well, token includes: device addr, endpoint, size, etc.
*
* Also since we queue async transactions in reverse order by returning
* last good match we restores the order.
*
* It's expected that we wont have a ton of outstanding transactions.
* If we ever do we'd want to optimize this algorithm.
*/
QTAILQ_FOREACH(async, &s->async_pending, next) {
if (async->token == token) {
/* Good match */
match = async;
if (async->td == addr) {
/* Best match */
break;
}
} }
count++; }
if (queue == NULL) {
return NULL;
} }
if (count > 64) QTAILQ_FOREACH(async, &queue->asyncs, next) {
fprintf(stderr, "uhci: warning lots of async transactions\n"); if (async->td == addr) {
return async;
}
}
return match; return NULL;
} }
static void uhci_update_irq(UHCIState *s) static void uhci_update_irq(UHCIState *s)
@ -753,8 +793,7 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *in
{ {
UHCIAsync *async; UHCIAsync *async;
int len = 0, max_len; int len = 0, max_len;
uint8_t pid, isoc; uint8_t pid;
uint32_t token;
USBDevice *dev; USBDevice *dev;
USBEndpoint *ep; USBEndpoint *ep;
@ -762,41 +801,29 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *in
if (!(td->ctrl & TD_CTRL_ACTIVE)) if (!(td->ctrl & TD_CTRL_ACTIVE))
return 1; return 1;
/* token field is not unique for isochronous requests, async = uhci_async_find_td(s, addr, td);
* so use the destination buffer
*/
if (td->ctrl & TD_CTRL_IOS) {
token = td->buffer;
isoc = 1;
} else {
token = td->token;
isoc = 0;
}
async = uhci_async_find_td(s, addr, token);
if (async) { if (async) {
/* Already submitted */ /* Already submitted */
async->valid = 32; async->queue->valid = 32;
if (!async->done) if (!async->done)
return 1; return 1;
uhci_async_unlink(s, async); uhci_async_unlink(async);
goto done; goto done;
} }
/* Allocate new packet */ /* Allocate new packet */
async = uhci_async_alloc(s); async = uhci_async_alloc(uhci_queue_get(s, td));
if (!async) if (!async)
return 1; return 1;
/* valid needs to be large enough to handle 10 frame delay /* valid needs to be large enough to handle 10 frame delay
* for initial isochronous requests * for initial isochronous requests
*/ */
async->valid = 32; async->queue->valid = 32;
async->td = addr; async->td = addr;
async->token = token; async->isoc = td->ctrl & TD_CTRL_IOS;
async->isoc = isoc;
max_len = ((td->token >> 21) + 1) & 0x7ff; max_len = ((td->token >> 21) + 1) & 0x7ff;
pid = td->token & 0xff; pid = td->token & 0xff;
@ -821,14 +848,14 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *in
default: default:
/* invalid pid : frame interrupted */ /* invalid pid : frame interrupted */
uhci_async_free(s, async); uhci_async_free(async);
s->status |= UHCI_STS_HCPERR; s->status |= UHCI_STS_HCPERR;
uhci_update_irq(s); uhci_update_irq(s);
return -1; return -1;
} }
if (len == USB_RET_ASYNC) { if (len == USB_RET_ASYNC) {
uhci_async_link(s, async); uhci_async_link(async);
return 2; return 2;
} }
@ -837,14 +864,14 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *in
done: done:
len = uhci_complete_td(s, td, async, int_mask); len = uhci_complete_td(s, td, async, int_mask);
usb_packet_unmap(&async->packet); usb_packet_unmap(&async->packet);
uhci_async_free(s, async); uhci_async_free(async);
return len; return len;
} }
static void uhci_async_complete(USBPort *port, USBPacket *packet) static void uhci_async_complete(USBPort *port, USBPacket *packet)
{ {
UHCIAsync *async = container_of(packet, UHCIAsync, packet); UHCIAsync *async = container_of(packet, UHCIAsync, packet);
UHCIState *s = async->uhci; UHCIState *s = async->queue->uhci;
DPRINTF("uhci: async complete. td 0x%x token 0x%x\n", async->td, async->token); DPRINTF("uhci: async complete. td 0x%x token 0x%x\n", async->td, async->token);
@ -859,14 +886,14 @@ static void uhci_async_complete(USBPort *port, USBPacket *packet)
le32_to_cpus(&td.token); le32_to_cpus(&td.token);
le32_to_cpus(&td.buffer); le32_to_cpus(&td.buffer);
uhci_async_unlink(s, async); uhci_async_unlink(async);
uhci_complete_td(s, &td, async, &int_mask); uhci_complete_td(s, &td, async, &int_mask);
s->pending_int_mask |= int_mask; s->pending_int_mask |= int_mask;
/* update the status bits of the TD */ /* update the status bits of the TD */
val = cpu_to_le32(td.ctrl); val = cpu_to_le32(td.ctrl);
pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val)); pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val));
uhci_async_free(s, async); uhci_async_free(async);
} else { } else {
async->done = 1; async->done = 1;
uhci_process_frame(s); uhci_process_frame(s);
@ -1142,7 +1169,7 @@ static int usb_uhci_common_initfn(PCIDevice *dev)
} }
s->frame_timer = qemu_new_timer_ns(vm_clock, uhci_frame_timer, s); s->frame_timer = qemu_new_timer_ns(vm_clock, uhci_frame_timer, s);
s->num_ports_vmstate = NB_PORTS; s->num_ports_vmstate = NB_PORTS;
QTAILQ_INIT(&s->async_pending); QTAILQ_INIT(&s->queues);
qemu_register_reset(uhci_reset, s); qemu_register_reset(uhci_reset, s);