Implemented USB remote wakeup mechanism.

- Extended USB callback definition to support different event types sent from
  device to HC. Currently we only have the two events "async completion" and
  "remote wakeup".
- Implemented event handlers in the UHCI, OHCI and EHCI. For the EHCI version
  we currently have no test case.
- The external USB hub now uses this feature at device connect/disconnect.
  Now the device change on the hub is correctly detected by the guest OS.
- TODO #1: remote wakeup and async completion for the xHCI.
- TODO #2: event handler for the external hub to make a USB device work on
  a chain of hubs.
- TODO #3: the event handler possibly could be used for the packet copy code.
This commit is contained in:
Volker Ruppert 2016-12-10 13:45:49 +00:00
parent 7b2a8bb340
commit c3b608a3c9
8 changed files with 117 additions and 41 deletions

View File

@ -738,16 +738,33 @@ void bx_uhci_core_c::uhci_timer(void)
// However, since we don't do anything, let's not.
}
void uhci_async_complete_packet(USBPacket *packet, void *dev)
void uhci_event_handler(int event, USBPacket *packet, void *dev, int port)
{
((bx_uhci_core_c*)dev)->async_complete_packet(packet);
((bx_uhci_core_c*)dev)->event_handler(event, packet, port);
}
void bx_uhci_core_c::async_complete_packet(USBPacket *packet)
void bx_uhci_core_c::event_handler(int event, USBPacket *packet, int port)
{
BX_DEBUG(("Experimental async packet completion"));
USBAsync *p = container_of_usb_packet(packet);
p->done = 1;
if (event == USB_EVENT_ASYNC) {
BX_DEBUG(("Experimental async packet completion"));
USBAsync *p = container_of_usb_packet(packet);
p->done = 1;
} else if (event == USB_EVENT_WAKEUP) {
if (hub.usb_port[port].suspend && !hub.usb_port[port].resume) {
hub.usb_port[port].resume = 1;
}
// if in suspend state, signal resume
if (hub.usb_command.suspend) {
hub.usb_command.resume = 1;
hub.usb_status.resume = 1;
if (hub.usb_enable.resume) {
hub.usb_status.interrupt = 1;
}
update_irq();
}
} else {
BX_ERROR(("unknown/unsupported event (id=%d) on port #%d", event, port+1));
}
}
bx_bool bx_uhci_core_c::DoTransfer(Bit32u address, Bit32u queue_num, struct TD *td) {
@ -795,7 +812,7 @@ bx_bool bx_uhci_core_c::DoTransfer(Bit32u address, Bit32u queue_num, struct TD *
p->packet.pid = pid;
p->packet.devaddr = addr;
p->packet.devep = endpt;
p->packet.complete_cb = uhci_async_complete_packet;
p->packet.complete_cb = uhci_event_handler;
p->packet.complete_dev = this;
switch (pid) {
case USB_TOKEN_OUT:
@ -1008,11 +1025,13 @@ void bx_uhci_core_c::set_connect_status(Bit8u port, int type, bx_bool connected)
if (!device->init()) {
set_connect_status(port, type, 0);
BX_ERROR(("port #%d: connect failed", port+1));
return;
} else {
BX_INFO(("port #%d: connect: %s", port+1, device->get_info()));
device->set_async_mode(1);
}
}
device->set_event_handler(this, uhci_event_handler, port);
} else {
hub.usb_port[port].status = 0;
hub.usb_port[port].connect_changed = 1;

View File

@ -182,7 +182,7 @@ public:
virtual Bit32u pci_read_handler(Bit8u address, unsigned io_len);
virtual void pci_write_handler(Bit8u address, Bit32u value, unsigned io_len);
void async_complete_packet(USBPacket *packet);
void event_handler(int event, USBPacket *packet, int port);
protected:
bx_uhci_core_t hub;

View File

@ -120,7 +120,10 @@
typedef struct USBPacket USBPacket;
typedef void USBCallback(USBPacket *packet, void *dev);
#define USB_EVENT_WAKEUP 0
#define USB_EVENT_ASYNC 1
typedef void USBCallback(int event, USBPacket *packet, void *dev, int port);
class usb_device_c;
@ -191,6 +194,12 @@ public:
void set_speed(int speed) {d.speed = speed;}
Bit8u get_address() {return d.addr;}
void set_async_mode(bx_bool async) {d.async_mode = async;}
void set_event_handler(void *dev, USBCallback *cb, int port)
{
d.event.dev = dev;
d.event.cb = cb;
d.event.port = port;
}
void set_debug_mode();
void usb_send_msg(int msg);
@ -222,6 +231,11 @@ protected:
int setup_index;
bx_bool stall;
bx_bool async_mode;
struct {
USBCallback *cb;
void *dev;
int port;
} event;
bx_list_c *sr;
} d;
@ -262,7 +276,7 @@ static BX_CPP_INLINE void usb_cancel_packet(USBPacket *p)
static BX_CPP_INLINE void usb_packet_complete(USBPacket *p)
{
p->complete_cb(p, p->complete_dev);
p->complete_cb(USB_EVENT_ASYNC, p, p->complete_dev, 0);
}
// Async packet support

View File

@ -123,6 +123,8 @@ static inline struct EHCIPacket *ehci_container_of_usb_packet(void *ptr)
reinterpret_cast<size_t>(&(static_cast<struct EHCIPacket*>(0)->packet)));
}
void ehci_event_handler(int event, USBPacket *packet, void *dev, int port);
// builtin configuration handling functions
Bit32s usb_ehci_options_parser(const char *context, int num_params, char *params[])
@ -583,11 +585,13 @@ void bx_usb_ehci_c::set_connect_status(Bit8u port, int type, bx_bool connected)
if (!device->init()) {
set_connect_status(port, type, 0);
BX_ERROR(("port #%d: connect failed", port+1));
return;
} else {
BX_INFO(("port #%d: connect: %s", port+1, device->get_info()));
device->set_async_mode(1);
}
}
device->set_event_handler(BX_EHCI_THIS_PTR, ehci_event_handler, port);
} else { // not connected
if (BX_EHCI_THIS hub.usb_port[port].portsc.po) {
BX_EHCI_THIS uhci[port >> 1]->set_port_device(port & 1, NULL);
@ -1258,26 +1262,35 @@ void bx_usb_ehci_c::finish_transfer(EHCIQueue *q, int status)
}
}
void ehci_async_complete_packet(USBPacket *packet, void *dev)
void ehci_event_handler(int event, USBPacket *packet, void *dev, int port)
{
((bx_usb_ehci_c*)dev)->async_complete_packet(packet);
((bx_usb_ehci_c*)dev)->event_handler(event, packet, port);
}
void bx_usb_ehci_c::async_complete_packet(USBPacket *packet)
void bx_usb_ehci_c::event_handler(int event, USBPacket *packet, int port)
{
EHCIPacket *p;
BX_DEBUG(("Experimental async packet completion"));
p = ehci_container_of_usb_packet(packet);
if (p->pid == USB_TOKEN_IN) {
BX_EHCI_THIS transfer(p);
}
BX_ASSERT(p->async == EHCI_ASYNC_INFLIGHT);
p->async = EHCI_ASYNC_FINISHED;
p->usb_status = packet->len;
if (event == USB_EVENT_ASYNC) {
BX_DEBUG(("Experimental async packet completion"));
p = ehci_container_of_usb_packet(packet);
if (p->pid == USB_TOKEN_IN) {
BX_EHCI_THIS transfer(p);
}
BX_ASSERT(p->async == EHCI_ASYNC_INFLIGHT);
p->async = EHCI_ASYNC_FINISHED;
p->usb_status = packet->len;
if (p->queue->async) {
BX_EHCI_THIS advance_async_state();
if (p->queue->async) {
BX_EHCI_THIS advance_async_state();
}
} else if (event == USB_EVENT_WAKEUP) {
if (BX_EHCI_THIS hub.usb_port[port].portsc.sus) {
BX_EHCI_THIS hub.usb_port[port].portsc.fpr = 1;
raise_irq(USBSTS_PCD);
}
} else {
BX_ERROR(("unknown/unsupported event (id=%d) on port #%d", event, port+1));
}
}
@ -1388,7 +1401,7 @@ int bx_usb_ehci_c::execute(EHCIPacket *p)
p->packet.pid = p->pid;
p->packet.devaddr = p->queue->dev->get_address();
p->packet.devep = endp;
p->packet.complete_cb = ehci_async_complete_packet;
p->packet.complete_cb = ehci_event_handler;
p->packet.complete_dev = BX_EHCI_THIS_PTR;
p->async = EHCI_ASYNC_INITIALIZED;

View File

@ -333,7 +333,7 @@ public:
virtual Bit32u pci_read_handler(Bit8u address, unsigned io_len);
virtual void pci_write_handler(Bit8u address, Bit32u value, unsigned io_len);
void async_complete_packet(USBPacket *packet);
void event_handler(int event, USBPacket *packet, int port);
static const char *usb_param_handler(bx_param_string_c *param, int set,
const char *oldval, const char *val, int maxlen);

View File

@ -570,7 +570,9 @@ void usb_hub_device_c::usb_set_connect_status(Bit8u port, int type, bx_bool conn
hub.usb_port[port].PortChange |= PORT_STAT_C_CONNECTION;
if (hub.usb_port[port].PortStatus & PORT_STAT_SUSPEND) {
hub.usb_port[port].PortChange |= PORT_STAT_C_SUSPEND;
// TODO: signal resume to upstream port
}
if (d.event.dev != NULL) {
d.event.cb(USB_EVENT_WAKEUP, NULL, d.event.dev, d.event.port);
}
if (!device->get_connected()) {
if (!device->init()) {
@ -581,6 +583,9 @@ void usb_hub_device_c::usb_set_connect_status(Bit8u port, int type, bx_bool conn
}
}
} else {
if (d.event.dev != NULL) {
d.event.cb(USB_EVENT_WAKEUP, NULL, d.event.dev, d.event.port);
}
hub.usb_port[port].PortStatus &= ~PORT_STAT_CONNECTION;
hub.usb_port[port].PortChange |= PORT_STAT_C_CONNECTION;
if (hub.usb_port[port].PortStatus & PORT_STAT_ENABLE) {

View File

@ -271,7 +271,7 @@ void bx_usb_ohci_c::reset_hc()
BX_OHCI_THIS hub.op_regs.HcControl.rwe = 0;
BX_OHCI_THIS hub.op_regs.HcControl.rwc = 0;
BX_OHCI_THIS hub.op_regs.HcControl.ir = 0;
BX_OHCI_THIS hub.op_regs.HcControl.hcfs = 0;
BX_OHCI_THIS hub.op_regs.HcControl.hcfs = OHCI_USB_RESET;
BX_OHCI_THIS hub.op_regs.HcControl.ble = 0;
BX_OHCI_THIS hub.op_regs.HcControl.cle = 0;
BX_OHCI_THIS hub.op_regs.HcControl.ie = 0;
@ -755,9 +755,9 @@ bx_bool bx_usb_ohci_c::write_handler(bx_phy_address addr, unsigned len, void *da
BX_OHCI_THIS hub.op_regs.HcControl.ie = (value & (1<< 3)) ? 1 : 0;
BX_OHCI_THIS hub.op_regs.HcControl.ple = (value & (1<< 2)) ? 1 : 0;
BX_OHCI_THIS hub.op_regs.HcControl.cbsr = (value & (3<< 0)) >> 0;
if (BX_OHCI_THIS hub.op_regs.HcControl.hcfs == 0x02) {
if (BX_OHCI_THIS hub.op_regs.HcControl.hcfs == OHCI_USB_OPERATIONAL) {
BX_OHCI_THIS hub.op_regs.HcFmRemainingToggle = 0;
if (org_state != 2)
if (org_state != OHCI_USB_OPERATIONAL)
BX_OHCI_THIS hub.use_control_head = BX_OHCI_THIS hub.use_bulk_head = 1;
}
break;
@ -773,7 +773,7 @@ bx_bool bx_usb_ohci_c::write_handler(bx_phy_address addr, unsigned len, void *da
if (value & (1<< 0)) {
BX_OHCI_THIS hub.op_regs.HcCommandStatus.hcr = 1;
BX_OHCI_THIS reset_hc();
BX_OHCI_THIS hub.op_regs.HcControl.hcfs = 3; // suspend state
BX_OHCI_THIS hub.op_regs.HcControl.hcfs = OHCI_USB_SUSPEND;
for (unsigned i=0; i<USB_OHCI_PORTS; i++)
if (BX_OHCI_THIS hub.usb_port[i].HcRhPortStatus.ccs && (BX_OHCI_THIS hub.usb_port[i].device != NULL))
DEV_usb_send_msg(BX_OHCI_THIS hub.usb_port[i].device, USB_MSG_RESET);
@ -1014,7 +1014,7 @@ Bit32u bx_usb_ohci_c::get_frame_remaining(void)
Bit16u bit_time, fr;
bit_time = (Bit16u)((bx_pc_system.time_usec() - BX_OHCI_THIS hub.sof_time) * 12);
if ((BX_OHCI_THIS hub.op_regs.HcControl.hcfs != 2) ||
if ((BX_OHCI_THIS hub.op_regs.HcControl.hcfs != OHCI_USB_OPERATIONAL) ||
(bit_time > BX_OHCI_THIS hub.op_regs.HcFmInterval.fi)) {
fr = 0;
} else {
@ -1036,7 +1036,7 @@ void bx_usb_ohci_c::usb_frame_timer(void)
Bit32u address, ed_address;
Bit16u zero = 0;
if (BX_OHCI_THIS hub.op_regs.HcControl.hcfs == 2) {
if (BX_OHCI_THIS hub.op_regs.HcControl.hcfs == OHCI_USB_OPERATIONAL) {
// set remaining to the interval amount.
BX_OHCI_THIS hub.op_regs.HcFmRemainingToggle = BX_OHCI_THIS hub.op_regs.HcFmInterval.fit;
BX_OHCI_THIS hub.sof_time = bx_pc_system.time_usec();
@ -1188,17 +1188,34 @@ bx_bool bx_usb_ohci_c::process_ed(struct OHCI_ED *ed, const Bit32u ed_address)
return ret;
}
void ohci_async_complete_packet(USBPacket *packet, void *dev)
void ohci_event_handler(int event, USBPacket *packet, void *dev, int port)
{
((bx_usb_ohci_c*)dev)->async_complete_packet(packet);
((bx_usb_ohci_c*)dev)->event_handler(event, packet, port);
}
void bx_usb_ohci_c::async_complete_packet(USBPacket *packet)
void bx_usb_ohci_c::event_handler(int event, USBPacket *packet, int port)
{
BX_DEBUG(("Async packet completion"));
USBAsync *p = container_of_usb_packet(packet);
p->done = 1;
BX_OHCI_THIS process_lists();
Bit32u intr = 0;
if (event == USB_EVENT_ASYNC) {
BX_DEBUG(("Async packet completion"));
USBAsync *p = container_of_usb_packet(packet);
p->done = 1;
BX_OHCI_THIS process_lists();
} else if (event == USB_EVENT_WAKEUP) {
if (BX_OHCI_THIS hub.usb_port[port].HcRhPortStatus.pss) {
BX_OHCI_THIS hub.usb_port[port].HcRhPortStatus.pss = 0;
BX_OHCI_THIS hub.usb_port[port].HcRhPortStatus.pssc = 1;
intr = OHCI_INTR_RHSC;
}
if (BX_OHCI_THIS hub.op_regs.HcControl.hcfs == OHCI_USB_SUSPEND) {
BX_OHCI_THIS hub.op_regs.HcControl.hcfs = OHCI_USB_RESUME;
intr = OHCI_INTR_RD;
}
set_interrupt(intr);
} else {
BX_ERROR(("unknown/unsupported event (id=%d) on port #%d", event, port+1));
}
}
bx_bool bx_usb_ohci_c::process_td(struct OHCI_TD *td, struct OHCI_ED *ed)
@ -1266,7 +1283,7 @@ bx_bool bx_usb_ohci_c::process_td(struct OHCI_TD *td, struct OHCI_ED *ed)
p->packet.pid = pid;
p->packet.devaddr = ED_GET_FA(ed);
p->packet.devep = ED_GET_EN(ed);
p->packet.complete_cb = ohci_async_complete_packet;
p->packet.complete_cb = ohci_event_handler;
p->packet.complete_dev = this;
BX_DEBUG((" pid = %s addr = %i endpnt = %i len = %i mps = %i (td->cbp = 0x%08X, td->be = 0x%08X)",
@ -1526,11 +1543,13 @@ void bx_usb_ohci_c::usb_set_connect_status(Bit8u port, int type, bx_bool connect
if (!device->init()) {
usb_set_connect_status(port, type, 0);
BX_ERROR(("port #%d: connect failed", port+1));
return;
} else {
BX_INFO(("port #%d: connect: %s", port+1, device->get_info()));
device->set_async_mode(1);
}
}
device->set_event_handler(BX_OHCI_THIS_PTR, ohci_event_handler, port);
} else { // not connected
BX_OHCI_THIS hub.usb_port[port].HcRhPortStatus.ccs = 0;
BX_OHCI_THIS hub.usb_port[port].HcRhPortStatus.pes = 0;

View File

@ -33,6 +33,12 @@
#define USB_OHCI_PORTS 2
// HCFS values
#define OHCI_USB_RESET 0x00
#define OHCI_USB_RESUME 0x01
#define OHCI_USB_OPERATIONAL 0x02
#define OHCI_USB_SUSPEND 0x03
#define OHCI_INTR_SO (1<<0) // Scheduling overrun
#define OHCI_INTR_WD (1<<1) // HcDoneHead writeback
#define OHCI_INTR_SF (1<<2) // Start of frame
@ -253,7 +259,7 @@ public:
virtual Bit32u pci_read_handler(Bit8u address, unsigned io_len);
virtual void pci_write_handler(Bit8u address, Bit32u value, unsigned io_len);
void async_complete_packet(USBPacket *packet);
void event_handler(int event, USBPacket *packet, int port);
static const char *usb_param_handler(bx_param_string_c *param, int set,
const char *oldval, const char *val, int maxlen);