-
-
Notifications
You must be signed in to change notification settings - Fork 289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OHCI: Fixed a bug in the OHCI implementation from QEMU #1531
base: master
Are you sure you want to change the base?
Changes from 2 commits
ff79cd5
a3d61ca
b5de99f
28119cf
7e0734c
d3e4b0e
114d994
fe10357
51a0a4e
d265219
adb0eb3
98aa18b
950519a
3b15f6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -281,10 +281,7 @@ void ohci_stop_endpoints(OHCIState *ohci) | |
USBDevice *dev; | ||
int i, j; | ||
|
||
if (ohci->async_td) { | ||
usb_cancel_packet(&ohci->usb_packet); | ||
ohci->async_td = 0; | ||
} | ||
ohci_clear_active_packets(ohci); | ||
for (i = 0; i < ohci->num_ports; i++) { | ||
dev = ohci->rhport[i].port.dev; | ||
if (dev && dev->attached) { | ||
|
@@ -869,18 +866,18 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) | |
return 1; | ||
} | ||
|
||
/* See if this TD has already been submitted to the device. */ | ||
completion = (addr == ohci->async_td); | ||
if (completion && !ohci->async_complete) { | ||
trace_usb_ohci_td_skip_async(); | ||
return 1; | ||
} | ||
if (ohci_read_td(ohci, addr, &td)) { | ||
trace_usb_ohci_td_read_error(addr); | ||
ohci_die(ohci); | ||
return 1; | ||
} | ||
|
||
dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA)); | ||
if (dev == NULL) { | ||
trace_usb_ohci_td_dev_error(); | ||
return 1; | ||
} | ||
|
||
dir = OHCI_BM(ed->flags, ED_D); | ||
switch (dir) { | ||
case OHCI_TD_DIR_OUT: | ||
|
@@ -909,6 +906,35 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) | |
trace_usb_ohci_td_bad_direction(dir); | ||
return 1; | ||
} | ||
|
||
ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN)); | ||
USBActivePacket *packet = NULL; | ||
USBActivePacket *iter; | ||
QTAILQ_FOREACH(iter, &ohci->active_packets, next) { | ||
if(iter->ep == ep) { | ||
packet = iter; | ||
break; | ||
} | ||
} | ||
|
||
// A packet for this endpoint doesn't exist yet. Make one | ||
faha223 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if(packet == NULL) { | ||
packet = g_malloc(sizeof(USBActivePacket)); | ||
usb_packet_init(&packet->usb_packet); | ||
packet->ep = ep; | ||
packet->async_complete = false; | ||
packet->async_td = 0; | ||
packet->ohci = ohci; | ||
QTAILQ_INSERT_TAIL(&ohci->active_packets, packet, next); | ||
} | ||
|
||
/* See if this TD has already been submitted to the device. */ | ||
completion = (addr == packet->async_td); | ||
if (completion && !packet->async_complete) { | ||
trace_usb_ohci_td_skip_async(); | ||
return 1; | ||
} | ||
|
||
if (td.cbp && td.be) { | ||
if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) { | ||
len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff); | ||
|
@@ -920,8 +946,8 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) | |
} | ||
len = (td.be - td.cbp) + 1; | ||
} | ||
if (len > sizeof(ohci->usb_buf)) { | ||
len = sizeof(ohci->usb_buf); | ||
if (len > sizeof(packet->usb_buf)) { | ||
len = sizeof(packet->usb_buf); | ||
} | ||
|
||
pktlen = len; | ||
|
@@ -932,7 +958,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) | |
pktlen = len; | ||
} | ||
if (!completion) { | ||
if (ohci_copy_td(ohci, &td, ohci->usb_buf, pktlen, | ||
if (ohci_copy_td(ohci, &td, packet->usb_buf, pktlen, | ||
DMA_DIRECTION_TO_DEVICE)) { | ||
ohci_die(ohci); | ||
} | ||
|
@@ -943,52 +969,42 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) | |
flag_r = (td.flags & OHCI_TD_R) != 0; | ||
trace_usb_ohci_td_pkt_hdr(addr, (int64_t)pktlen, (int64_t)len, str, | ||
flag_r, td.cbp, td.be); | ||
ohci_td_pkt("OUT", ohci->usb_buf, pktlen); | ||
ohci_td_pkt("OUT", packet->usb_buf, pktlen); | ||
|
||
if (completion) { | ||
ohci->async_td = 0; | ||
ohci->async_complete = false; | ||
packet->async_td = 0; | ||
packet->async_complete = false; | ||
} else { | ||
dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA)); | ||
if (dev == NULL) { | ||
trace_usb_ohci_td_dev_error(); | ||
return 1; | ||
} | ||
ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN)); | ||
if (ohci->async_td) { | ||
/* ??? The hardware should allow one active packet per | ||
endpoint. We only allow one active packet per controller. | ||
This should be sufficient as long as devices respond in a | ||
timely manner. | ||
*/ | ||
if (packet->async_td) { | ||
// Only allow one active packet per endpoint. | ||
trace_usb_ohci_td_too_many_pending(ep->nr); | ||
return 1; | ||
} | ||
usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, !flag_r, | ||
usb_packet_setup(&packet->usb_packet, pid, ep, 0, addr, !flag_r, | ||
OHCI_BM(td.flags, TD_DI) == 0); | ||
usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, pktlen); | ||
usb_handle_packet(dev, &ohci->usb_packet); | ||
trace_usb_ohci_td_packet_status(ohci->usb_packet.status); | ||
usb_packet_addbuf(&packet->usb_packet, packet->usb_buf, pktlen); | ||
usb_handle_packet(dev, &packet->usb_packet); | ||
trace_usb_ohci_td_packet_status(packet->usb_packet.status); | ||
|
||
if (ohci->usb_packet.status == USB_RET_ASYNC) { | ||
if (packet->usb_packet.status == USB_RET_ASYNC) { | ||
usb_device_flush_ep_queue(dev, ep); | ||
ohci->async_td = addr; | ||
packet->async_td = addr; | ||
return 1; | ||
} | ||
} | ||
if (ohci->usb_packet.status == USB_RET_SUCCESS) { | ||
ret = ohci->usb_packet.actual_length; | ||
if (packet->usb_packet.status == USB_RET_SUCCESS) { | ||
ret = packet->usb_packet.actual_length; | ||
} else { | ||
ret = ohci->usb_packet.status; | ||
ret = packet->usb_packet.status; | ||
} | ||
|
||
if (ret >= 0) { | ||
if (dir == OHCI_TD_DIR_IN) { | ||
if (ohci_copy_td(ohci, &td, ohci->usb_buf, ret, | ||
if (ohci_copy_td(ohci, &td, packet->usb_buf, ret, | ||
DMA_DIRECTION_FROM_DEVICE)) { | ||
ohci_die(ohci); | ||
} | ||
ohci_td_pkt("IN", ohci->usb_buf, pktlen); | ||
ohci_td_pkt("IN", packet->usb_buf, pktlen); | ||
} else { | ||
ret = pktlen; | ||
} | ||
|
@@ -1078,15 +1094,20 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) | |
/* Service an endpoint list. Returns nonzero if active TD were found. */ | ||
static int ohci_service_ed_list(OHCIState *ohci, uint32_t head) | ||
{ | ||
struct ohci_td td; | ||
struct ohci_ed ed; | ||
uint32_t next_ed; | ||
uint32_t cur; | ||
int active; | ||
int dir, pid; | ||
uint32_t link_cnt = 0; | ||
active = 0; | ||
USBDevice *dev; | ||
USBEndpoint *ep; | ||
|
||
if (head == 0) | ||
if (head == 0) { | ||
return 0; | ||
} | ||
|
||
for (cur = head; cur && link_cnt++ < ED_LINK_LIMIT; cur = next_ed) { | ||
if (ohci_read_ed(ohci, cur, &ed)) { | ||
|
@@ -1101,11 +1122,62 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head) | |
uint32_t addr; | ||
/* Cancel pending packets for ED that have been paused. */ | ||
addr = ed.head & OHCI_DPTR_MASK; | ||
if (ohci->async_td && addr == ohci->async_td) { | ||
usb_cancel_packet(&ohci->usb_packet); | ||
ohci->async_td = 0; | ||
usb_device_ep_stopped(ohci->usb_packet.ep->dev, | ||
ohci->usb_packet.ep); | ||
|
||
if (ohci_read_td(ohci, addr, &td)) { | ||
trace_usb_ohci_td_read_error(addr); | ||
ohci_die(ohci); | ||
return 1; | ||
} | ||
|
||
dir = OHCI_BM(ed.flags, ED_D); | ||
switch (dir) { | ||
case OHCI_TD_DIR_OUT: | ||
case OHCI_TD_DIR_IN: | ||
/* Same value. */ | ||
break; | ||
default: | ||
dir = OHCI_BM(td.flags, TD_DP); | ||
break; | ||
} | ||
|
||
switch (dir) { | ||
case OHCI_TD_DIR_IN: | ||
pid = USB_TOKEN_IN; | ||
break; | ||
case OHCI_TD_DIR_OUT: | ||
pid = USB_TOKEN_OUT; | ||
break; | ||
case OHCI_TD_DIR_SETUP: | ||
pid = USB_TOKEN_SETUP; | ||
break; | ||
default: | ||
continue; | ||
} | ||
|
||
dev = ohci_find_device(ohci, OHCI_BM(ed.flags, ED_FA)); | ||
if (dev != NULL) { | ||
ep = usb_ep_get(dev, pid, OHCI_BM(ed.flags, ED_EN)); | ||
|
||
if(ep != NULL) { | ||
USBActivePacket *iter; | ||
QTAILQ_FOREACH(iter, &ohci->active_packets, next) { | ||
if(iter->ep == ep) { | ||
if (iter->async_td && addr == iter->async_td) { | ||
if(usb_packet_is_inflight(&iter->usb_packet)) | ||
usb_cancel_packet(&iter->usb_packet); | ||
iter->async_td = 0; | ||
usb_device_ep_stopped(iter->usb_packet.ep->dev, | ||
iter->usb_packet.ep); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} else { | ||
if (ohci_put_ed(ohci, cur, &ed)) { | ||
ohci_die(ohci); | ||
return 0; | ||
} | ||
} | ||
continue; | ||
} | ||
|
@@ -1751,11 +1823,19 @@ static void ohci_child_detach(USBPort *port1, USBDevice *dev) | |
{ | ||
OHCIState *ohci = port1->opaque; | ||
|
||
if (ohci->async_td && | ||
usb_packet_is_inflight(&ohci->usb_packet) && | ||
ohci->usb_packet.ep->dev == dev) { | ||
usb_cancel_packet(&ohci->usb_packet); | ||
ohci->async_td = 0; | ||
USBActivePacket *iter, *iter2; | ||
QTAILQ_FOREACH_SAFE(iter, &ohci->active_packets, next, iter2) { | ||
if(iter->usb_packet.ep->dev == dev) { | ||
faha223 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (iter->async_td && | ||
usb_packet_is_inflight(&iter->usb_packet) && | ||
iter->usb_packet.ep->dev == dev) { | ||
usb_cancel_packet(&iter->usb_packet); | ||
iter->async_td = 0; | ||
faha223 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
QTAILQ_REMOVE(&ohci->active_packets, iter, next); | ||
g_free(iter); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -1812,11 +1892,11 @@ static void ohci_wakeup(USBPort *port1) | |
|
||
static void ohci_async_complete_packet(USBPort *port, USBPacket *packet) | ||
{ | ||
OHCIState *ohci = container_of(packet, OHCIState, usb_packet); | ||
|
||
USBActivePacket *active_packet = container_of(packet, USBActivePacket, usb_packet); | ||
trace_usb_ohci_async_complete(); | ||
ohci->async_complete = true; | ||
ohci_process_lists(ohci); | ||
active_packet->async_complete = true; | ||
ohci_process_lists(active_packet->ohci); | ||
} | ||
|
||
static USBPortOps ohci_port_ops = { | ||
|
@@ -1890,9 +1970,10 @@ void usb_ohci_init(OHCIState *ohci, DeviceState *dev, uint32_t num_ports, | |
ohci->localmem_base = localmem_base; | ||
|
||
ohci->name = object_get_typename(OBJECT(dev)); | ||
usb_packet_init(&ohci->usb_packet); | ||
|
||
ohci->async_td = 0; | ||
|
||
// Inialize the QTAILQ_HEAD. QTAILQ_HEAD_INITIALIZER doesn't work here | ||
faha223 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ohci->active_packets.tqh_circ.tql_next = NULL; | ||
ohci->active_packets.tqh_circ.tql_prev = &(ohci->active_packets).tqh_circ; | ||
|
||
ohci->eof_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, | ||
ohci_frame_boundary, ohci); | ||
|
@@ -1906,10 +1987,26 @@ void ohci_sysbus_die(struct OHCIState *ohci) | |
{ | ||
trace_usb_ohci_die(); | ||
|
||
ohci_clear_active_packets(ohci); | ||
ohci_set_interrupt(ohci, OHCI_INTR_UE); | ||
ohci_bus_stop(ohci); | ||
} | ||
|
||
void ohci_clear_active_packets(struct OHCIState *ohci) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clear because this function makes the list empty. I am not opposed to changing it to cancel |
||
{ | ||
while(!QTAILQ_EMPTY(&ohci->active_packets)) { | ||
USBActivePacket *packet = QTAILQ_FIRST(&ohci->active_packets); | ||
if(packet->async_td) { | ||
usb_cancel_packet(&packet->usb_packet); | ||
packet->async_td = 0; | ||
usb_device_ep_stopped(packet->usb_packet.ep->dev, | ||
packet->usb_packet.ep); | ||
} | ||
QTAILQ_REMOVE(&ohci->active_packets, packet, next); | ||
g_free(packet); | ||
} | ||
} | ||
|
||
static void ohci_realize_pxa(DeviceState *dev, Error **errp) | ||
{ | ||
OHCISysBusState *s = SYSBUS_OHCI(dev); | ||
|
@@ -1999,9 +2096,6 @@ const VMStateDescription vmstate_ohci_state = { | |
VMSTATE_UINT32(hreset, OHCIState), | ||
VMSTATE_UINT32(htest, OHCIState), | ||
VMSTATE_UINT32(old_ctl, OHCIState), | ||
VMSTATE_UINT8_ARRAY(usb_buf, OHCIState, 8192), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When loading a snapshot, Saving a snapshot does the same thing, but using the These fields are not defined in I suspect there's a real possibility that the I tested saving and loading snapshots with a libusb controller attached, both while interacting with the controller and while not interacting with the controller and it seemed to be mostly stable. I was able to crash the emulator during vmload a few times but the point of failure was clearing out the existing async packets before restoring the snapshot data. It didn't have anything to do with the actual snapshot data. I've added back the fields I removed from |
||
VMSTATE_UINT32(async_td, OHCIState), | ||
VMSTATE_BOOL(async_complete, OHCIState), | ||
VMSTATE_END_OF_LIST() | ||
}, | ||
.subsections = (const VMStateDescription*[]) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should you clean up packets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No.
I removed this foreach loop because the call right after this (ohci_stop_endpoints) actually calls (ohci_clear_active_packets) and does the work of this foreach loop already.