Bochs/bochs/iodev/usb/usb_ehci.cc
Volker Ruppert 87145baf61 Rewrite of the PCI base address (BAR) handling to reduce code duplication.
- Added new structure bx_pci_bar_t that contains all parameters related to the
  PCI BARs (type, size, address and r/w handlers).
- Added new methods init_bar_io() and init_bar_mem() to set up the new structure
  in the pci device init code.
- Added new method pci_write_handler_common() to handle writes to the normal
  BARs and the ROM BAR. Writes to other registers are forwarded to the device
  specific PCI write handlers. Removed BAR and ROM BAR handling from the
  specific code.
- Added new method pci_bar_change_notify() to execute specific code after BAR
  update (vga, ne2k).
- Moved normal BAR handling to method after_restore_pci_state().
- Store pointer to PCI device name in bx_pci_device_c and use it for i/o setup.
2018-02-04 09:41:50 +00:00

2312 lines
70 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Experimental USB EHCI adapter (partly ported from Qemu)
//
// Copyright (C) 2015-2018 The Bochs Project
//
// Copyright(c) 2008 Emutex Ltd. (address@hidden)
// Copyright(c) 2011-2012 Red Hat, Inc.
//
// Red Hat Authors:
// Gerd Hoffmann <kraxel@redhat.com>
// Hans de Goede <hdegoede@redhat.com>
//
// 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 Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
/////////////////////////////////////////////////////////////////////////
// Define BX_PLUGGABLE in files that can be compiled into plugins. For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE
#include "iodev.h"
#if BX_SUPPORT_PCI && BX_SUPPORT_USB_EHCI
#include "pci.h"
#include "usb_common.h"
#include "uhci_core.h"
#include "qemu-queue.h"
#include "usb_ehci.h"
#define LOG_THIS theUSB_EHCI->
bx_usb_ehci_c* theUSB_EHCI = NULL;
#define USB_RET_PROCERR (-99)
#define IO_SPACE_SIZE 256
#define OPS_REGS_OFFSET 0x20
#define USBSTS_INT (1 << 0) // USB Interrupt
#define USBSTS_ERRINT (1 << 1) // Error Interrupt
#define USBSTS_PCD (1 << 2) // Port Change Detect
#define USBSTS_FLR (1 << 3) // Frame List Rollover
#define USBSTS_HSE (1 << 4) // Host System Error
#define USBSTS_IAA (1 << 5) // Interrupt on Async Advance
#define USBINTR_MASK 0x0000003f
#define FRAME_TIMER_FREQ 1000
#define FRAME_TIMER_USEC (1000000 / FRAME_TIMER_FREQ)
#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
#define MAX_QH 100 // Max allowable queue heads in a chain
#define MIN_FR_PER_TICK 3 // Min frames to process when catching up
/* 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,
EST_FETCHSITD,
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
/* nifty macros from Arnon's EHCI version */
#define get_field(data, field) \
(((data) & field##_MASK) >> field##_SH)
#define set_field(data, newval, field) do { \
Bit32u val = *data; \
val &= ~ field##_MASK; \
val |= ((newval) << field##_SH) & field##_MASK; \
*data = val; \
} while(0)
static inline struct EHCIPacket *ehci_container_of_usb_packet(void *ptr)
{
return reinterpret_cast<struct EHCIPacket*>(static_cast<char*>(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[])
{
if (!strcmp(params[0], "usb_ehci")) {
bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_USB_EHCI);
for (int i = 1; i < num_params; i++) {
if (!strncmp(params[i], "enabled=", 8)) {
SIM->get_param_bool(BXPN_EHCI_ENABLED)->set(atol(&params[i][8]));
} else if (!strncmp(params[i], "port", 4)) {
if (SIM->parse_usb_port_params(context, 0, params[i], USB_EHCI_PORTS, base) < 0) {
return -1;
}
} else if (!strncmp(params[i], "options", 7)) {
if (SIM->parse_usb_port_params(context, 1, params[i], USB_EHCI_PORTS, base) < 0) {
return -1;
}
} else {
BX_ERROR(("%s: unknown parameter '%s' for usb_ehci ignored.", context, params[i]));
}
}
} else {
BX_PANIC(("%s: unknown directive '%s'", context, params[0]));
}
return 0;
}
Bit32s usb_ehci_options_save(FILE *fp)
{
bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_USB_EHCI);
SIM->write_usb_options(fp, USB_EHCI_PORTS, base);
return 0;
}
// device plugin entry points
int CDECL libusb_ehci_LTX_plugin_init(plugin_t *plugin, plugintype_t type)
{
theUSB_EHCI = new bx_usb_ehci_c();
BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theUSB_EHCI, BX_PLUGIN_USB_EHCI);
// add new configuration parameter for the config interface
SIM->init_usb_options("EHCI", "ehci", USB_EHCI_PORTS);
// register add-on option for bochsrc and command line
SIM->register_addon_option("usb_ehci", usb_ehci_options_parser, usb_ehci_options_save);
return 0; // Success
}
void CDECL libusb_ehci_LTX_plugin_fini(void)
{
SIM->unregister_addon_option("usb_ehci");
bx_list_c *menu = (bx_list_c*)SIM->get_param("ports.usb");
delete theUSB_EHCI;
menu->remove("ehci");
}
// the device object
bx_usb_ehci_c::bx_usb_ehci_c()
{
put("usb_ehci", "EHCI");
memset((void*)&hub, 0, sizeof(bx_usb_ehci_t));
rt_conf_id = -1;
hub.frame_timer_index = BX_NULL_TIMER_HANDLE;
}
bx_usb_ehci_c::~bx_usb_ehci_c()
{
char pname[16];
int i;
SIM->unregister_runtime_config_handler(rt_conf_id);
for (i = 0; i < 3; i++) {
if (BX_EHCI_THIS uhci[i] != NULL)
delete BX_EHCI_THIS uhci[i];
}
for (i=0; i<USB_EHCI_PORTS; i++) {
sprintf(pname, "port%d.device", i+1);
SIM->get_param_string(pname, SIM->get_param(BXPN_USB_EHCI))->set_handler(NULL);
remove_device(i);
}
SIM->get_bochs_root()->remove("usb_ehci");
bx_list_c *usb_rt = (bx_list_c*)SIM->get_param(BXPN_MENU_RUNTIME_USB);
usb_rt->remove("ehci");
BX_DEBUG(("Exit"));
}
void bx_usb_ehci_c::init(void)
{
unsigned i;
char pname[6], lfname[10];
bx_list_c *ehci, *port;
bx_param_string_c *device;
Bit8u devfunc;
// Read in values from config interface
ehci = (bx_list_c*) SIM->get_param(BXPN_USB_EHCI);
// Check if the device is disabled or not configured
if (!SIM->get_param_bool("enabled", ehci)->get()) {
BX_INFO(("USB EHCI disabled"));
// mark unused plugin for removal
((bx_param_bool_c*)((bx_list_c*)SIM->get_param(BXPN_PLUGIN_CTRL))->get_by_name("usb_ehci"))->set(0);
return;
}
// Call our frame timer routine every 1mS (1,024uS)
// Continuous and active
BX_EHCI_THIS hub.frame_timer_index = DEV_register_timer(this, ehci_frame_handler,
FRAME_TIMER_USEC, 1, 1, "ehci.frame_timer");
BX_EHCI_THIS devfunc = 0x07;
DEV_register_pci_handlers(this, &BX_EHCI_THIS devfunc, BX_PLUGIN_USB_EHCI,
"Experimental USB EHCI");
// initialize readonly registers (same as QEMU)
// 0x8086 = vendor (Intel)
// 0x24cd = device (82801D)
// revision number (0x10)
init_pci_conf(0x8086, 0x24cd, 0x10, 0x0c0320, 0x00);
BX_EHCI_THIS pci_conf[0x3d] = BX_PCI_INTD;
BX_EHCI_THIS pci_conf[0x60] = 0x20;
BX_EHCI_THIS init_bar_mem(0, IO_SPACE_SIZE, read_handler, write_handler);
for (i = 0; i < 3; i++) {
BX_EHCI_THIS uhci[i] = new bx_uhci_core_c();
sprintf(lfname, "usb_uchi%d", i);
sprintf(pname, "UHCI%d", i);
BX_EHCI_THIS uhci[i]->put(lfname, pname);
}
devfunc = BX_EHCI_THIS devfunc & 0xf8;
BX_EHCI_THIS uhci[0]->init_uhci(devfunc, 0x24c2, 0x80, BX_PCI_INTA);
BX_EHCI_THIS uhci[1]->init_uhci(devfunc | 0x01, 0x24c4, 0x00, BX_PCI_INTB);
BX_EHCI_THIS uhci[2]->init_uhci(devfunc | 0x02, 0x24c7, 0x00, BX_PCI_INTC);
// initialize capability registers
BX_EHCI_THIS hub.cap_regs.CapLength = OPS_REGS_OFFSET;
BX_EHCI_THIS hub.cap_regs.HciVersion = 0x0100;
BX_EHCI_THIS hub.cap_regs.HcsParams = 0x00103200 | USB_EHCI_PORTS;
BX_EHCI_THIS hub.cap_regs.HccParams = 0x00006871;
// initialize runtime configuration
bx_list_c *usb_rt = (bx_list_c*)SIM->get_param(BXPN_MENU_RUNTIME_USB);
bx_list_c *ehci_rt = new bx_list_c(usb_rt, "ehci", "EHCI Runtime Options");
ehci_rt->set_options(ehci_rt->SHOW_PARENT | ehci_rt->USE_BOX_TITLE);
for (i=0; i<USB_EHCI_PORTS; i++) {
sprintf(pname, "port%d", i+1);
port = (bx_list_c*)SIM->get_param(pname, ehci);
ehci_rt->add(port);
device = (bx_param_string_c*)port->get_by_name("device");
device->set_handler(usb_param_handler);
BX_EHCI_THIS hub.usb_port[i].device = NULL;
BX_EHCI_THIS hub.usb_port[i].owner_change = 0;
BX_EHCI_THIS hub.usb_port[i].portsc.ccs = 0;
BX_EHCI_THIS hub.usb_port[i].portsc.csc = 0;
}
// register handler for correct device connect handling after runtime config
BX_EHCI_THIS rt_conf_id = SIM->register_runtime_config_handler(BX_EHCI_THIS_PTR, runtime_config_handler);
BX_EHCI_THIS device_change = 0;
BX_EHCI_THIS maxframes = 128;
QTAILQ_INIT(&BX_EHCI_THIS hub.aqueues);
QTAILQ_INIT(&BX_EHCI_THIS hub.pqueues);
BX_INFO(("USB EHCI initialized"));
}
void bx_usb_ehci_c::reset(unsigned type)
{
unsigned i;
for (i = 0; i < 3; i++) {
uhci[i]->reset_uhci(type);
}
if (type == BX_RESET_HARDWARE) {
static const struct reset_vals_t {
unsigned addr;
unsigned char val;
} reset_vals[] = {
{ 0x04, 0x00 }, { 0x05, 0x00 }, // command_io
{ 0x06, 0x90 }, { 0x07, 0x02 }, // status
{ 0x0C, 0x08 }, // cache line size (should be done by BIOS)
{ 0x0D, 0x00 }, // bus latency
{ 0x0F, 0x00 }, // BIST is not supported
// address space 0x10 - 0x17
{ 0x10, 0x00 }, { 0x11, 0x00 },
{ 0x12, 0x00 }, { 0x13, 0x00 }, //
{ 0x34, 0x50 }, // Capabilities Pointer
{ 0x50, 0x01 }, // PCI Power Management Capability ID
{ 0x51, 0x58 }, // Next Item Pointer
{ 0x52, 0xc2 }, // Power Management Capabilities
{ 0x53, 0xc9 }, //
{ 0x54, 0x00 }, // Power Management Control/Status
{ 0x55, 0x00 }, //
{ 0x58, 0x0a }, // Debug Port Capability ID
{ 0x59, 0x00 }, // Next Item Pointer
{ 0x5a, 0x80 }, // Debug Port Base Offset
{ 0x5b, 0x20 }, //
{ 0x61, 0x20 }, // Frame Length Adjustment
{ 0x62, 0x7f }, // Port Wake Capability
{ 0x68, 0x01 }, // USB EHCI Legacy Support Extended Capability
{ 0x69, 0x00 }, //
{ 0x6a, 0x00 }, //
{ 0x6b, 0x00 }, //
{ 0x6c, 0x00 }, // USB EHCI Legacy Support Extended Control/Status
{ 0x6d, 0x00 }, //
{ 0x6e, 0x00 }, //
{ 0x6f, 0x00 }, //
{ 0x70, 0x00 }, // Intel Specific USB EHCI SMI
{ 0x71, 0x00 }, //
{ 0x72, 0x00 }, //
{ 0x73, 0x00 }, //
{ 0x80, 0x00 }, // Access Control Register
{ 0xdc, 0x00 }, // USB HS Reference Voltage
{ 0xdd, 0x00 }, //
{ 0xde, 0x00 }, //
{ 0xdf, 0x00 } //
};
for (i = 0; i < sizeof(reset_vals) / sizeof(*reset_vals); i++) {
BX_EHCI_THIS pci_conf[reset_vals[i].addr] = reset_vals[i].val;
}
}
BX_EHCI_THIS reset_hc();
}
void bx_usb_ehci_c::register_state(void)
{
unsigned i;
char tmpname[16];
bx_list_c *hub, *op_regs, *port, *reg, *uhcic;
bx_list_c *list = new bx_list_c(SIM->get_bochs_root(), "usb_ehci", "USB EHCI State");
hub = new bx_list_c(list, "hub");
BXRS_DEC_PARAM_FIELD(hub, usbsts_pending, BX_EHCI_THIS hub.usbsts_pending);
BXRS_DEC_PARAM_FIELD(hub, usbsts_frindex, BX_EHCI_THIS hub.usbsts_frindex);
BXRS_DEC_PARAM_FIELD(hub, pstate, BX_EHCI_THIS hub.pstate);
BXRS_DEC_PARAM_FIELD(hub, astate, BX_EHCI_THIS hub.astate);
BXRS_DEC_PARAM_FIELD(hub, last_run_usec, BX_EHCI_THIS hub.last_run_usec);
BXRS_DEC_PARAM_FIELD(hub, async_stepdown, BX_EHCI_THIS hub.async_stepdown);
op_regs = new bx_list_c(hub, "op_regs");
reg = new bx_list_c(op_regs, "UsbCmd");
BXRS_HEX_PARAM_FIELD(reg, itc, BX_EHCI_THIS hub.op_regs.UsbCmd.itc);
BXRS_PARAM_BOOL(reg, iaad, BX_EHCI_THIS hub.op_regs.UsbCmd.iaad);
BXRS_PARAM_BOOL(reg, ase, BX_EHCI_THIS hub.op_regs.UsbCmd.ase);
BXRS_PARAM_BOOL(reg, pse, BX_EHCI_THIS hub.op_regs.UsbCmd.pse);
BXRS_PARAM_BOOL(reg, hcreset, BX_EHCI_THIS hub.op_regs.UsbCmd.hcreset);
BXRS_PARAM_BOOL(reg, rs, BX_EHCI_THIS hub.op_regs.UsbCmd.rs);
reg = new bx_list_c(op_regs, "UsbSts");
BXRS_PARAM_BOOL(reg, ass, BX_EHCI_THIS hub.op_regs.UsbSts.ass);
BXRS_PARAM_BOOL(reg, pss, BX_EHCI_THIS hub.op_regs.UsbSts.pss);
BXRS_PARAM_BOOL(reg, recl, BX_EHCI_THIS hub.op_regs.UsbSts.recl);
BXRS_PARAM_BOOL(reg, hchalted, BX_EHCI_THIS hub.op_regs.UsbSts.hchalted);
BXRS_HEX_PARAM_FIELD(reg, inti, BX_EHCI_THIS hub.op_regs.UsbSts.inti);
BXRS_HEX_PARAM_FIELD(op_regs, UsbIntr, BX_EHCI_THIS hub.op_regs.UsbIntr);
BXRS_HEX_PARAM_FIELD(op_regs, FrIndex, BX_EHCI_THIS hub.op_regs.FrIndex);
BXRS_HEX_PARAM_FIELD(op_regs, CtrlDsSegment, BX_EHCI_THIS hub.op_regs.CtrlDsSegment);
BXRS_HEX_PARAM_FIELD(op_regs, PeriodicListBase, BX_EHCI_THIS hub.op_regs.PeriodicListBase);
BXRS_HEX_PARAM_FIELD(op_regs, AsyncListAddr, BX_EHCI_THIS hub.op_regs.AsyncListAddr);
BXRS_HEX_PARAM_FIELD(op_regs, ConfigFlag, BX_EHCI_THIS hub.op_regs.ConfigFlag);
for (i = 0; i < USB_EHCI_PORTS; i++) {
sprintf(tmpname, "port%d", i+1);
port = new bx_list_c(hub, tmpname);
reg = new bx_list_c(port, "portsc");
BXRS_PARAM_BOOL(reg, woe, BX_EHCI_THIS hub.usb_port[i].portsc.woe);
BXRS_PARAM_BOOL(reg, wde, BX_EHCI_THIS hub.usb_port[i].portsc.wde);
BXRS_PARAM_BOOL(reg, wce, BX_EHCI_THIS hub.usb_port[i].portsc.wce);
BXRS_HEX_PARAM_FIELD(reg, ptc, BX_EHCI_THIS hub.usb_port[i].portsc.ptc);
BXRS_HEX_PARAM_FIELD(reg, pic, BX_EHCI_THIS hub.usb_port[i].portsc.pic);
BXRS_PARAM_BOOL(reg, po, BX_EHCI_THIS hub.usb_port[i].portsc.po);
BXRS_HEX_PARAM_FIELD(reg, ls, BX_EHCI_THIS hub.usb_port[i].portsc.ls);
BXRS_PARAM_BOOL(reg, pr, BX_EHCI_THIS hub.usb_port[i].portsc.pr);
BXRS_PARAM_BOOL(reg, sus, BX_EHCI_THIS hub.usb_port[i].portsc.sus);
BXRS_PARAM_BOOL(reg, fpr, BX_EHCI_THIS hub.usb_port[i].portsc.fpr);
BXRS_PARAM_BOOL(reg, occ, BX_EHCI_THIS hub.usb_port[i].portsc.occ);
BXRS_PARAM_BOOL(reg, oca, BX_EHCI_THIS hub.usb_port[i].portsc.oca);
BXRS_PARAM_BOOL(reg, pec, BX_EHCI_THIS hub.usb_port[i].portsc.pec);
BXRS_PARAM_BOOL(reg, ped, BX_EHCI_THIS hub.usb_port[i].portsc.ped);
BXRS_PARAM_BOOL(reg, csc, BX_EHCI_THIS hub.usb_port[i].portsc.csc);
BXRS_PARAM_BOOL(reg, ccs, BX_EHCI_THIS hub.usb_port[i].portsc.ccs);
// empty list for USB device state
new bx_list_c(port, "device");
}
for (i = 0; i < 3; i++) {
sprintf(tmpname, "uhci%d", i);
uhcic = new bx_list_c(list, tmpname);
uhci[i]->register_state(uhcic);
}
register_pci_state(hub);
}
void bx_usb_ehci_c::after_restore_state(void)
{
int i;
bx_pci_device_c::after_restore_pci_state(NULL);
for (i=0; i<USB_EHCI_PORTS; i++) {
if (BX_EHCI_THIS hub.usb_port[i].device != NULL) {
BX_EHCI_THIS hub.usb_port[i].device->after_restore_state();
}
}
for (i = 0; i < 3; i++) {
uhci[i]->after_restore_state();
}
}
void bx_usb_ehci_c::reset_hc()
{
int i;
char pname[6];
BX_EHCI_THIS hub.op_regs.UsbCmd.itc = 0x08;
BX_EHCI_THIS hub.op_regs.UsbCmd.iaad = 0;
BX_EHCI_THIS hub.op_regs.UsbCmd.ase = 0;
BX_EHCI_THIS hub.op_regs.UsbCmd.pse = 0;
BX_EHCI_THIS hub.op_regs.UsbCmd.hcreset = 0;
BX_EHCI_THIS hub.op_regs.UsbCmd.rs = 0;
BX_EHCI_THIS hub.op_regs.UsbSts.ass = 0;
BX_EHCI_THIS hub.op_regs.UsbSts.pss = 0;
BX_EHCI_THIS hub.op_regs.UsbSts.recl = 0;
BX_EHCI_THIS hub.op_regs.UsbSts.hchalted = 1;
BX_EHCI_THIS hub.op_regs.UsbSts.inti = 0;
BX_EHCI_THIS hub.op_regs.UsbIntr = 0x0;
BX_EHCI_THIS hub.op_regs.FrIndex = 0x0;
BX_EHCI_THIS hub.op_regs.CtrlDsSegment = 0x0;
BX_EHCI_THIS hub.op_regs.PeriodicListBase = 0x0;
BX_EHCI_THIS hub.op_regs.AsyncListAddr = 0x0;
BX_EHCI_THIS hub.op_regs.ConfigFlag = 0x0;
// Ports[x]
for (i=0; i<USB_EHCI_PORTS; i++) {
reset_port(i);
if (BX_EHCI_THIS hub.usb_port[i].device == NULL) {
sprintf(pname, "port%d", i+1);
init_device(i, (bx_list_c*)SIM->get_param(pname, SIM->get_param(BXPN_USB_EHCI)));
} else {
set_connect_status(i, BX_EHCI_THIS hub.usb_port[i].device->get_type(), 1);
}
}
BX_EHCI_THIS hub.usbsts_pending = 0;
BX_EHCI_THIS hub.usbsts_frindex = 0;
BX_EHCI_THIS hub.astate = EST_INACTIVE;
BX_EHCI_THIS hub.pstate = EST_INACTIVE;
BX_EHCI_THIS queues_rip_all(0);
BX_EHCI_THIS queues_rip_all(1);
BX_EHCI_THIS update_irq();
}
void bx_usb_ehci_c::reset_port(int p)
{
BX_EHCI_THIS hub.usb_port[p].portsc.woe = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.wde = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.wce = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.ptc = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.pic = 0;
if (!BX_EHCI_THIS hub.usb_port[p].portsc.po) {
BX_EHCI_THIS hub.usb_port[p].owner_change = 1;
BX_EHCI_THIS change_port_owner(p);
}
BX_EHCI_THIS hub.usb_port[p].portsc.pp = 1;
BX_EHCI_THIS hub.usb_port[p].portsc.ls = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.pr = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.sus = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.fpr = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.occ = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.oca = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.pec = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.ped = 0;
BX_EHCI_THIS hub.usb_port[p].portsc.csc = 0;
}
void bx_usb_ehci_c::init_device(Bit8u port, bx_list_c *portconf)
{
usbdev_type type;
char pname[BX_PATHNAME_LEN];
const char *devname = NULL;
devname = ((bx_param_string_c*)portconf->get_by_name("device"))->getptr();
if (devname == NULL) return;
if (!strlen(devname) || !strcmp(devname, "none")) return;
if (BX_EHCI_THIS hub.usb_port[port].device != NULL) {
BX_ERROR(("init_device(): port%d already in use", port+1));
return;
}
sprintf(pname, "usb_ehci.hub.port%d.device", port+1);
bx_list_c *sr_list = (bx_list_c*)SIM->get_param(pname, SIM->get_bochs_root());
type = DEV_usb_init_device(portconf, BX_EHCI_THIS_PTR, &BX_EHCI_THIS hub.usb_port[port].device, sr_list);
if (BX_EHCI_THIS hub.usb_port[port].device != NULL) {
set_connect_status(port, type, 1);
}
}
void bx_usb_ehci_c::remove_device(Bit8u port)
{
if (BX_EHCI_THIS hub.usb_port[port].device != NULL) {
delete BX_EHCI_THIS hub.usb_port[port].device;
BX_EHCI_THIS hub.usb_port[port].device = NULL;
}
}
void bx_usb_ehci_c::set_connect_status(Bit8u port, int type, bx_bool connected)
{
const bx_bool ccs_org = BX_EHCI_THIS hub.usb_port[port].portsc.ccs;
const bx_bool ped_org = BX_EHCI_THIS hub.usb_port[port].portsc.ped;
usb_device_c *device = BX_EHCI_THIS hub.usb_port[port].device;
if (device != NULL) {
if (device->get_type() == type) {
if (connected) {
if (BX_EHCI_THIS hub.usb_port[port].portsc.po) {
BX_EHCI_THIS uhci[port >> 1]->set_port_device(port & 1, device);
return;
}
if (device->get_speed() == USB_SPEED_SUPER) {
BX_PANIC(("Super-speed device not supported on USB2 port."));
set_connect_status(port, type, 0);
return;
}
switch (device->get_speed()) {
case USB_SPEED_LOW:
BX_INFO(("Low speed device connected to port #%d", port+1));
BX_EHCI_THIS hub.usb_port[port].portsc.ls = 0x1;
BX_EHCI_THIS hub.usb_port[port].portsc.ped = 0;
break;
case USB_SPEED_FULL:
BX_INFO(("Full speed device connected to port #%d", port+1));
BX_EHCI_THIS hub.usb_port[port].portsc.ls = 0x2;
BX_EHCI_THIS hub.usb_port[port].portsc.ped = 0;
break;
case USB_SPEED_HIGH:
BX_INFO(("High speed device connected to port #%d", port+1));
BX_EHCI_THIS hub.usb_port[port].portsc.ls = 0x0;
BX_EHCI_THIS hub.usb_port[port].portsc.ped = 1;
break;
default:
BX_ERROR(("device->get_speed() returned invalid speed value"));
}
BX_EHCI_THIS hub.usb_port[port].portsc.ccs = 1;
if (!device->get_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_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);
if ((!BX_EHCI_THIS hub.usb_port[port].owner_change) &&
(BX_EHCI_THIS hub.op_regs.ConfigFlag & 1)) {
BX_EHCI_THIS hub.usb_port[port].portsc.po = 0;
BX_EHCI_THIS hub.usb_port[port].portsc.csc = 1;
}
} else {
BX_EHCI_THIS hub.usb_port[port].portsc.ccs = 0;
BX_EHCI_THIS hub.usb_port[port].portsc.ped = 0;
BX_EHCI_THIS queues_rip_device(device, 0);
BX_EHCI_THIS queues_rip_device(device, 1);
device->set_async_mode(0);
}
if (!BX_EHCI_THIS hub.usb_port[port].owner_change) {
remove_device(port);
}
if (BX_EHCI_THIS hub.usb_port[port].portsc.po)
return;
}
}
if (ccs_org != BX_EHCI_THIS hub.usb_port[port].portsc.ccs)
BX_EHCI_THIS hub.usb_port[port].portsc.csc = 1;
if (ped_org != BX_EHCI_THIS hub.usb_port[port].portsc.ped)
BX_EHCI_THIS hub.usb_port[port].portsc.pec = 1;
BX_EHCI_THIS hub.op_regs.UsbSts.inti |= USBSTS_PCD;
BX_EHCI_THIS update_irq();
}
}
void bx_usb_ehci_c::change_port_owner(int port)
{
if (port < 0) {
for (int i=0; i<USB_EHCI_PORTS; i++) {
change_port_owner(i);
}
} else {
usb_device_c *device = BX_EHCI_THIS hub.usb_port[port].device;
if (BX_EHCI_THIS hub.usb_port[port].owner_change) {
BX_INFO(("port #%d: owner change to %s", port+1,
BX_EHCI_THIS hub.usb_port[port].portsc.po ? "EHCI":"UHCI"));
if (device != NULL) {
set_connect_status(port, device->get_type(), 0);
}
BX_EHCI_THIS hub.usb_port[port].portsc.po ^= 1;
if (device != NULL) {
set_connect_status(port, device->get_type(), 1);
}
}
BX_EHCI_THIS hub.usb_port[port].owner_change = 0;
}
}
bx_bool bx_usb_ehci_c::read_handler(bx_phy_address addr, unsigned len, void *data, void *param)
{
Bit32u val = 0, val_hi = 0;
int port;
const Bit32u offset = (Bit32u) (addr - BX_EHCI_THIS pci_bar[0].addr);
if (offset < OPS_REGS_OFFSET) {
switch (offset) {
case 0x00:
val = BX_EHCI_THIS hub.cap_regs.CapLength;
break;
case 0x02:
val = BX_EHCI_THIS hub.cap_regs.HciVersion;
break;
case 0x04:
val = BX_EHCI_THIS hub.cap_regs.HcsParams;
break;
case 0x08:
val = BX_EHCI_THIS hub.cap_regs.HccParams;
break;
}
} else {
switch (offset - OPS_REGS_OFFSET) {
case 0x00:
val = ((BX_EHCI_THIS hub.op_regs.UsbCmd.itc << 16)
| (BX_EHCI_THIS hub.op_regs.UsbCmd.iaad << 6)
| (BX_EHCI_THIS hub.op_regs.UsbCmd.ase << 5)
| (BX_EHCI_THIS hub.op_regs.UsbCmd.pse << 4)
| (BX_EHCI_THIS hub.op_regs.UsbCmd.hcreset << 1)
| BX_EHCI_THIS hub.op_regs.UsbCmd.rs);
break;
case 0x04:
val = ((BX_EHCI_THIS hub.op_regs.UsbSts.ass << 15)
| (BX_EHCI_THIS hub.op_regs.UsbSts.pss << 14)
| (BX_EHCI_THIS hub.op_regs.UsbSts.recl << 13)
| (BX_EHCI_THIS hub.op_regs.UsbSts.hchalted << 12)
| BX_EHCI_THIS hub.op_regs.UsbSts.inti);
break;
case 0x08:
val = BX_EHCI_THIS hub.op_regs.UsbIntr;
break;
case 0x0c:
val = BX_EHCI_THIS hub.op_regs.FrIndex;
break;
case 0x10:
val = BX_EHCI_THIS hub.op_regs.CtrlDsSegment;
break;
case 0x14:
val = BX_EHCI_THIS hub.op_regs.PeriodicListBase;
break;
case 0x18:
val = BX_EHCI_THIS hub.op_regs.AsyncListAddr;
break;
case 0x40:
val = BX_EHCI_THIS hub.op_regs.ConfigFlag;
break;
default:
port = (offset - OPS_REGS_OFFSET - 0x44) / 4;
if (port < USB_EHCI_PORTS) {
val = ((BX_EHCI_THIS hub.usb_port[port].portsc.woe << 22)
| (BX_EHCI_THIS hub.usb_port[port].portsc.wde << 21)
| (BX_EHCI_THIS hub.usb_port[port].portsc.wce << 20)
| (BX_EHCI_THIS hub.usb_port[port].portsc.ptc << 16)
| (BX_EHCI_THIS hub.usb_port[port].portsc.pic << 14)
| (BX_EHCI_THIS hub.usb_port[port].portsc.po << 13)
| (BX_EHCI_THIS hub.usb_port[port].portsc.pp << 12)
| (BX_EHCI_THIS hub.usb_port[port].portsc.ls << 10)
| (BX_EHCI_THIS hub.usb_port[port].portsc.pr << 8)
| (BX_EHCI_THIS hub.usb_port[port].portsc.sus << 7)
| (BX_EHCI_THIS hub.usb_port[port].portsc.fpr << 6)
| (BX_EHCI_THIS hub.usb_port[port].portsc.occ << 5)
| (BX_EHCI_THIS hub.usb_port[port].portsc.oca << 4)
| (BX_EHCI_THIS hub.usb_port[port].portsc.pec << 3)
| (BX_EHCI_THIS hub.usb_port[port].portsc.ped << 2)
| (BX_EHCI_THIS hub.usb_port[port].portsc.csc << 1)
| BX_EHCI_THIS hub.usb_port[port].portsc.ccs);
}
}
}
switch (len) {
case 1:
val &= 0xFF;
*((Bit8u *) data) = (Bit8u) val;
break;
case 2:
val &= 0xFFFF;
*((Bit16u *) data) = (Bit16u) val;
break;
case 8:
*((Bit32u *) ((Bit8u *) data + 4)) = val_hi;
case 4:
*((Bit32u *) data) = val;
break;
}
#if BX_PHY_ADDRESS_LONG
BX_DEBUG(("register read from offset 0x%04X: 0x%08X%08X (len=%i)", offset, (Bit32u) val_hi, (Bit32u) val, len));
#else
BX_DEBUG(("register read from offset 0x%04X: 0x%08X (len=%i)", offset, (Bit32u) val, len));
#endif
return 1;
}
bx_bool bx_usb_ehci_c::write_handler(bx_phy_address addr, unsigned len, void *data, void *param)
{
Bit32u value = *((Bit32u *) data);
Bit32u value_hi = *((Bit32u *) ((Bit8u *) data + 4));
bx_bool oldcfg, oldpo, oldpr, oldfpr;
int i, port;
const Bit32u offset = (Bit32u) (addr - BX_EHCI_THIS pci_bar[0].addr);
// modify val and val_hi per len of data to write
switch (len) {
case 1:
value &= 0xFF;
case 2:
value &= 0xFFFF;
case 4:
value_hi = 0;
break;
}
#if BX_PHY_ADDRESS_LONG
BX_DEBUG(("register write to offset 0x%04X: 0x%08X%08X (len=%i)", offset, value_hi, value, len));
#else
BX_DEBUG(("register write to offset 0x%04X: 0x%08X (len=%i)", offset, value, len));
#endif
if (offset >= OPS_REGS_OFFSET) {
switch (offset - OPS_REGS_OFFSET) {
case 0x00:
BX_EHCI_THIS hub.op_regs.UsbCmd.itc = (value >> 16) & 0x7f;
BX_EHCI_THIS hub.op_regs.UsbCmd.iaad = (value >> 6) & 1;
BX_EHCI_THIS hub.op_regs.UsbCmd.ase = (value >> 5) & 1;
BX_EHCI_THIS hub.op_regs.UsbCmd.pse = (value >> 4) & 1;
BX_EHCI_THIS hub.op_regs.UsbCmd.hcreset = (value >> 1) & 1;
BX_EHCI_THIS hub.op_regs.UsbCmd.rs = (value & 1);
if (BX_EHCI_THIS hub.op_regs.UsbCmd.iaad) {
BX_EHCI_THIS hub.async_stepdown = 0;
// TODO
}
if (BX_EHCI_THIS hub.op_regs.UsbCmd.hcreset) {
BX_EHCI_THIS reset_hc();
BX_EHCI_THIS hub.op_regs.UsbCmd.hcreset = 0;
}
if (BX_EHCI_THIS hub.op_regs.UsbCmd.rs) {
BX_EHCI_THIS hub.op_regs.UsbSts.hchalted = 0;
} else {
BX_EHCI_THIS hub.op_regs.UsbSts.hchalted = 1;
}
break;
case 0x04:
BX_EHCI_THIS hub.op_regs.UsbSts.inti ^= (value & USBINTR_MASK);
BX_EHCI_THIS update_irq();
break;
case 0x08:
BX_EHCI_THIS hub.op_regs.UsbIntr = (Bit8u)(value & USBINTR_MASK);
break;
case 0x0c:
if (!BX_EHCI_THIS hub.op_regs.UsbCmd.rs) {
BX_EHCI_THIS hub.op_regs.FrIndex = (value & 0x1fff);
}
break;
case 0x10:
BX_EHCI_THIS hub.op_regs.CtrlDsSegment = value;
break;
case 0x14:
BX_EHCI_THIS hub.op_regs.PeriodicListBase = (value & 0xfffff000);
break;
case 0x18:
BX_EHCI_THIS hub.op_regs.AsyncListAddr = (value & 0xffffffe0);
break;
case 0x40:
oldcfg = (BX_EHCI_THIS hub.op_regs.ConfigFlag & 1);
BX_EHCI_THIS hub.op_regs.ConfigFlag = (value & 1);
if (!oldcfg && (value & 1)) {
for (i=0; i<USB_EHCI_PORTS; i++) {
BX_EHCI_THIS hub.usb_port[i].owner_change = BX_EHCI_THIS hub.usb_port[i].portsc.po;
}
} else if (!(value & 1)) {
for (i=0; i<USB_EHCI_PORTS; i++) {
BX_EHCI_THIS hub.usb_port[i].owner_change = !BX_EHCI_THIS hub.usb_port[i].portsc.po;
}
}
BX_EHCI_THIS change_port_owner(-1);
break;
default:
port = (offset - OPS_REGS_OFFSET - 0x44) / 4;
if (port < USB_EHCI_PORTS) {
oldpo = BX_EHCI_THIS hub.usb_port[port].portsc.po;
oldpr = BX_EHCI_THIS hub.usb_port[port].portsc.pr;
oldfpr = BX_EHCI_THIS hub.usb_port[port].portsc.fpr;
BX_EHCI_THIS hub.usb_port[port].portsc.woe = (value >> 22) & 1;
BX_EHCI_THIS hub.usb_port[port].portsc.wde = (value >> 21) & 1;
BX_EHCI_THIS hub.usb_port[port].portsc.wce = (value >> 20) & 1;
BX_EHCI_THIS hub.usb_port[port].portsc.ptc = (value >> 16) & 0xf;
BX_EHCI_THIS hub.usb_port[port].portsc.pic = (value >> 14) & 3;
BX_EHCI_THIS hub.usb_port[port].portsc.pr = (value >> 8) & 1;
if ((value >> 7) & 1) BX_EHCI_THIS hub.usb_port[port].portsc.sus = 1;
BX_EHCI_THIS hub.usb_port[port].portsc.fpr = (value >> 6) & 1;
if ((value >> 5) & 1) BX_EHCI_THIS hub.usb_port[port].portsc.occ = 0;
if ((value >> 3) & 1) BX_EHCI_THIS hub.usb_port[port].portsc.pec = 0;
if (!((value >> 2) & 1)) BX_EHCI_THIS hub.usb_port[port].portsc.ped = 0;
if ((value >> 1) & 1) BX_EHCI_THIS hub.usb_port[port].portsc.csc = 0;
if (oldpo != ((value >> 13) & 1)) {
BX_EHCI_THIS hub.usb_port[port].owner_change = 1;
BX_EHCI_THIS change_port_owner(port);
}
if (oldpr && !BX_EHCI_THIS hub.usb_port[port].portsc.pr) {
if (BX_EHCI_THIS hub.usb_port[port].device != NULL) {
BX_EHCI_THIS hub.usb_port[port].device->usb_send_msg(USB_MSG_RESET);
BX_EHCI_THIS hub.usb_port[port].portsc.csc = 0;
if (BX_EHCI_THIS hub.usb_port[port].device->get_speed() == USB_SPEED_HIGH) {
BX_EHCI_THIS hub.usb_port[port].portsc.ped = 1;
}
}
}
if (oldfpr && !BX_EHCI_THIS hub.usb_port[port].portsc.fpr) {
BX_EHCI_THIS hub.usb_port[port].portsc.sus = 0;
}
}
}
}
return 1;
}
// EHCI core methods ported from QEMU 1.2.2
void bx_usb_ehci_c::update_irq(void)
{
bx_bool level = 0;
if ((BX_EHCI_THIS hub.op_regs.UsbSts.inti & BX_EHCI_THIS hub.op_regs.UsbIntr) > 0) {
level = 1;
BX_DEBUG(("Interrupt Fired."));
}
DEV_pci_set_irq(BX_EHCI_THIS devfunc, BX_EHCI_THIS pci_conf[0x3d], level);
}
void bx_usb_ehci_c::raise_irq(Bit8u intr)
{
if (intr & (USBSTS_PCD | USBSTS_FLR | USBSTS_HSE)) {
BX_EHCI_THIS hub.op_regs.UsbSts.inti |= intr;
BX_EHCI_THIS update_irq();
} else {
BX_EHCI_THIS hub.usbsts_pending |= intr;
}
}
void bx_usb_ehci_c::commit_irq(void)
{
Bit32u itc;
if (!BX_EHCI_THIS hub.usbsts_pending) {
return;
}
if (BX_EHCI_THIS hub.usbsts_frindex > BX_EHCI_THIS hub.op_regs.FrIndex) {
return;
}
itc = BX_EHCI_THIS hub.op_regs.UsbCmd.itc;
BX_EHCI_THIS hub.op_regs.UsbSts.inti |= BX_EHCI_THIS hub.usbsts_pending;
BX_EHCI_THIS hub.usbsts_pending = 0;
BX_EHCI_THIS hub.usbsts_frindex = BX_EHCI_THIS hub.op_regs.FrIndex + itc;
BX_EHCI_THIS update_irq();
}
void bx_usb_ehci_c::update_halt(void)
{
if (BX_EHCI_THIS hub.op_regs.UsbCmd.rs) {
BX_EHCI_THIS hub.op_regs.UsbSts.hchalted = 0;
} else {
if (BX_EHCI_THIS hub.astate == EST_INACTIVE && BX_EHCI_THIS hub.pstate == EST_INACTIVE) {
BX_EHCI_THIS hub.op_regs.UsbSts.hchalted = 1;
}
}
}
void bx_usb_ehci_c::set_state(int async, int state)
{
if (async) {
BX_EHCI_THIS hub.astate = state;
if (BX_EHCI_THIS hub.astate == EST_INACTIVE) {
BX_EHCI_THIS hub.op_regs.UsbSts.ass = 0;
BX_EHCI_THIS update_halt();
} else {
BX_EHCI_THIS hub.op_regs.UsbSts.ass = 1;
}
} else {
BX_EHCI_THIS hub.pstate = state;
if (BX_EHCI_THIS hub.pstate == EST_INACTIVE) {
BX_EHCI_THIS hub.op_regs.UsbSts.pss = 0;
BX_EHCI_THIS update_halt();
} else {
BX_EHCI_THIS hub.op_regs.UsbSts.pss = 1;
}
}
}
int bx_usb_ehci_c::get_state(int async)
{
return async ? BX_EHCI_THIS hub.astate : BX_EHCI_THIS hub.pstate;
}
void bx_usb_ehci_c::set_fetch_addr(int async, Bit32u addr)
{
if (async) {
BX_EHCI_THIS hub.a_fetch_addr = addr;
} else {
BX_EHCI_THIS hub.p_fetch_addr = addr;
}
}
Bit32u bx_usb_ehci_c::get_fetch_addr(int async)
{
return async ? BX_EHCI_THIS hub.a_fetch_addr : BX_EHCI_THIS hub.p_fetch_addr;
}
bx_bool bx_usb_ehci_c::async_enabled(void)
{
return (BX_EHCI_THIS hub.op_regs.UsbCmd.rs && BX_EHCI_THIS hub.op_regs.UsbCmd.ase);
}
bx_bool bx_usb_ehci_c::periodic_enabled(void)
{
return (BX_EHCI_THIS hub.op_regs.UsbCmd.rs && BX_EHCI_THIS hub.op_regs.UsbCmd.pse);
}
EHCIPacket *bx_usb_ehci_c::alloc_packet(EHCIQueue *q)
{
EHCIPacket *p = new EHCIPacket;
memset(p, 0, sizeof(EHCIPacket));
p->queue = q;
usb_packet_init(&p->packet, BUFF_SIZE);
QTAILQ_INSERT_TAIL(&q->packets, p, next);
return p;
}
void bx_usb_ehci_c::free_packet(EHCIPacket *p)
{
if (p->async == EHCI_ASYNC_FINISHED) {
int state = BX_EHCI_THIS get_state(p->queue->async);
/* This is a normal, but rare condition (cancel racing completion) */
BX_ERROR(("EHCI: Warning packet completed but not processed"));
BX_EHCI_THIS state_executing(p->queue);
BX_EHCI_THIS state_writeback(p->queue);
BX_EHCI_THIS set_state(p->queue->async, state);
/* state_writeback recurses into us with async == EHCI_ASYNC_NONE!! */
return;
}
if (p->async == EHCI_ASYNC_INFLIGHT) {
usb_cancel_packet(&p->packet);
}
QTAILQ_REMOVE(&p->queue->packets, p, next);
usb_packet_cleanup(&p->packet);
delete p;
}
EHCIQueue *bx_usb_ehci_c::alloc_queue(Bit32u addr, int async)
{
EHCIQueueHead *head = async ? &BX_EHCI_THIS hub.aqueues : &BX_EHCI_THIS hub.pqueues;
EHCIQueue *q;
q = new EHCIQueue;
memset(q, 0, sizeof(EHCIQueue));
q->ehci = &BX_EHCI_THIS hub;
q->qhaddr = addr;
q->async = async;
QTAILQ_INIT(&q->packets);
QTAILQ_INSERT_HEAD(head, q, next);
return q;
}
int bx_usb_ehci_c::cancel_queue(EHCIQueue *q)
{
EHCIPacket *p;
int packets = 0;
p = QTAILQ_FIRST(&q->packets);
if (p == NULL) {
return 0;
}
do {
free_packet(p);
packets++;
} while ((p = QTAILQ_FIRST(&q->packets)) != NULL);
return packets;
}
int bx_usb_ehci_c::reset_queue(EHCIQueue *q)
{
int packets = BX_EHCI_THIS cancel_queue(q);
q->dev = NULL;
q->qtdaddr = 0;
return packets;
}
void bx_usb_ehci_c::free_queue(EHCIQueue *q, const char *warn)
{
EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues;
int cancelled;
cancelled = BX_EHCI_THIS cancel_queue(q);
if (warn && cancelled > 0) {
BX_ERROR((warn));
}
QTAILQ_REMOVE(head, q, next);
free(q);
}
EHCIQueue *bx_usb_ehci_c::find_queue_by_qh(Bit32u addr, int async)
{
EHCIQueueHead *head = async ? &BX_EHCI_THIS hub.aqueues : &BX_EHCI_THIS hub.pqueues;
EHCIQueue *q;
QTAILQ_FOREACH(q, head, next) {
if (addr == q->qhaddr) {
return q;
}
}
return NULL;
}
void bx_usb_ehci_c::queues_rip_unused(int async)
{
EHCIQueueHead *head = async ? &BX_EHCI_THIS hub.aqueues : &BX_EHCI_THIS hub.pqueues;
const char *warn = async ? "guest unlinked busy QH" : NULL;
Bit64u maxage = FRAME_TIMER_USEC * BX_EHCI_THIS maxframes * 4;
EHCIQueue *q, *tmp;
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
if (q->seen) {
q->seen = 0;
q->ts = BX_EHCI_THIS hub.last_run_usec;
continue;
}
if (BX_EHCI_THIS hub.last_run_usec < q->ts + maxage) {
continue;
}
BX_EHCI_THIS free_queue(q, warn);
}
}
void bx_usb_ehci_c::queues_rip_unseen(int async)
{
EHCIQueueHead *head = async ? &BX_EHCI_THIS hub.aqueues : &BX_EHCI_THIS hub.pqueues;
EHCIQueue *q, *tmp;
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
if (!q->seen) {
BX_EHCI_THIS free_queue(q, NULL);
}
}
}
void bx_usb_ehci_c::queues_rip_device(usb_device_c *dev, int async)
{
EHCIQueueHead *head = async ? &BX_EHCI_THIS hub.aqueues : &BX_EHCI_THIS hub.pqueues;
EHCIQueue *q, *tmp;
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
if (q->dev != dev) {
continue;
}
BX_EHCI_THIS free_queue(q, NULL);
}
}
void bx_usb_ehci_c::queues_rip_all(int async)
{
EHCIQueueHead *head = async ? &BX_EHCI_THIS hub.aqueues : &BX_EHCI_THIS hub.pqueues;
const char *warn = async ? "guest stopped busy async schedule" : NULL;
EHCIQueue *q, *tmp;
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
BX_EHCI_THIS free_queue(q, warn);
}
}
usb_device_c *bx_usb_ehci_c::find_device(Bit8u addr)
{
usb_device_c *dev = NULL;
for (int i = 0; i < USB_EHCI_PORTS; i++) {
if (!BX_EHCI_THIS hub.usb_port[i].portsc.ped) {
BX_DEBUG(("Port %d not enabled", i));
continue;
}
if (BX_EHCI_THIS hub.usb_port[i].device != NULL) {
dev = BX_EHCI_THIS hub.usb_port[i].device->find_device(addr);
}
if (dev != NULL) {
return dev;
}
}
return NULL;
}
void bx_usb_ehci_c::flush_qh(EHCIQueue *q)
{
Bit32u *qh = (Bit32u *) &q->qh;
Bit32u dwords = sizeof(EHCIqh) >> 2;
Bit32u addr = NLPTR_GET(q->qhaddr);
put_dwords(addr + 3 * sizeof(Bit32u), qh + 3, dwords - 3);
}
int bx_usb_ehci_c::qh_do_overlay(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
int i;
int dtoggle;
int ping;
int eps;
int reload;
assert(p != NULL);
assert(p->qtdaddr == q->qtdaddr);
// remember values in fields to preserve in qh after overlay
dtoggle = q->qh.token & QTD_TOKEN_DTOGGLE;
ping = q->qh.token & QTD_TOKEN_PING;
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;
eps = get_field(q->qh.epchar, QH_EPCHAR_EPS);
if (eps == EHCI_QH_EPS_HIGH) {
q->qh.token &= ~QTD_TOKEN_PING;
q->qh.token |= ping;
}
reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
set_field(&q->qh.altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
for (i = 0; i < 5; i++) {
q->qh.bufptr[i] = p->qtd.bufptr[i];
}
if (!(q->qh.epchar & QH_EPCHAR_DTC)) {
// preserve QH DT bit
q->qh.token &= ~QTD_TOKEN_DTOGGLE;
q->qh.token |= dtoggle;
}
q->qh.bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
q->qh.bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
BX_EHCI_THIS flush_qh(q);
return 0;
}
// Bochs specific code (no async and scatter/gather support yet)
int bx_usb_ehci_c::transfer(EHCIPacket *p)
{
Bit32u cpage, offset, bytes, plen, blen = 0;
Bit64u page;
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;
while (bytes > 0) {
if (cpage > 4) {
BX_ERROR(("cpage out of range (%d)", cpage));
return USB_RET_PROCERR;
}
page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
page += offset;
plen = bytes;
if (plen > 4096 - offset) {
plen = 4096 - offset;
offset = 0;
cpage++;
}
if (p->pid == USB_TOKEN_IN) {
DEV_MEM_WRITE_PHYSICAL_DMA(page, plen, p->packet.data+blen);
} else {
DEV_MEM_READ_PHYSICAL_DMA(page, plen, p->packet.data+blen);
}
blen += plen;
bytes -= plen;
}
return 0;
}
void bx_usb_ehci_c::finish_transfer(EHCIQueue *q, int status)
{
Bit32u cpage, offset;
if (status > 0) {
/* update cpage & offset */
cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
offset += status;
cpage += offset >> QTD_BUFPTR_SH;
offset &= ~QTD_BUFPTR_MASK;
set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
q->qh.bufptr[0] |= offset;
}
}
void ehci_event_handler(int event, USBPacket *packet, void *dev, int port)
{
((bx_usb_ehci_c*)dev)->event_handler(event, packet, port);
}
void bx_usb_ehci_c::event_handler(int event, USBPacket *packet, int port)
{
EHCIPacket *p;
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();
}
} 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));
}
}
void bx_usb_ehci_c::execute_complete(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
BX_ASSERT(p != NULL);
BX_ASSERT(p->qtdaddr == q->qtdaddr);
BX_ASSERT(p->async == EHCI_ASYNC_INITIALIZED ||
p->async == EHCI_ASYNC_FINISHED);
BX_DEBUG(("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d",
q->qhaddr, q->qh.next, q->qtdaddr, p->usb_status));
if (p->usb_status < 0) {
switch (p->usb_status) {
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);
BX_EHCI_THIS raise_irq(USBSTS_ERRINT);
break;
case USB_RET_STALL:
q->qh.token |= QTD_TOKEN_HALT;
BX_EHCI_THIS raise_irq(USBSTS_ERRINT);
break;
case USB_RET_NAK: /* We're not done yet with this transaction */
set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT);
return;
case USB_RET_BABBLE:
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
BX_EHCI_THIS raise_irq(USBSTS_ERRINT);
break;
default:
/* should not be triggerable */
BX_PANIC(("USB invalid response %d", p->usb_status));
break;
}
} else {
// TODO check 4.12 for splits
if (p->tbytes && p->pid == USB_TOKEN_IN) {
p->tbytes -= p->usb_status;
} else {
p->tbytes = 0;
}
BX_DEBUG(("updating tbytes to %d", p->tbytes));
set_field(&q->qh.token, p->tbytes, QTD_TOKEN_TBYTES);
}
BX_EHCI_THIS finish_transfer(q, p->usb_status);
p->async = EHCI_ASYNC_NONE;
q->qh.token ^= QTD_TOKEN_DTOGGLE;
q->qh.token &= ~QTD_TOKEN_ACTIVE;
if (q->qh.token & QTD_TOKEN_IOC) {
BX_EHCI_THIS raise_irq(USBSTS_INT);
}
}
int bx_usb_ehci_c::execute(EHCIPacket *p)
{
int ret;
int endp;
BX_ASSERT(p->async == EHCI_ASYNC_NONE ||
p->async == EHCI_ASYNC_INITIALIZED);
if (!(p->qtd.token & QTD_TOKEN_ACTIVE)) {
BX_ERROR(("Attempting to execute inactive qtd"));
return USB_RET_PROCERR;
}
p->tbytes = (p->qtd.token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH;
if (p->tbytes > BUFF_SIZE) {
BX_ERROR(("guest requested more bytes than allowed"));
return USB_RET_PROCERR;
}
p->pid = (p->qtd.token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH;
switch (p->pid) {
case 0:
p->pid = USB_TOKEN_OUT;
break;
case 1:
p->pid = USB_TOKEN_IN;
break;
case 2:
p->pid = USB_TOKEN_SETUP;
break;
default:
BX_ERROR(("bad token"));
break;
}
endp = get_field(p->queue->qh.epchar, QH_EPCHAR_EP);
if (p->async == EHCI_ASYNC_NONE) {
p->packet.len = p->tbytes;
if (p->pid != USB_TOKEN_IN) {
if (BX_EHCI_THIS transfer(p) != 0) {
return USB_RET_PROCERR;
}
}
// Bochs specific packet setup
p->packet.pid = p->pid;
p->packet.devaddr = p->queue->dev->get_address();
p->packet.devep = endp;
p->packet.complete_cb = ehci_event_handler;
p->packet.complete_dev = BX_EHCI_THIS_PTR;
p->async = EHCI_ASYNC_INITIALIZED;
}
ret = p->queue->dev->handle_packet(&p->packet);
BX_DEBUG(("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d\n",
p->queue->qhaddr, p->queue->qh.next, p->queue->qtdaddr, p->pid,
p->packet.len, p->tbytes, endp, ret));
if (ret > BUFF_SIZE) {
BX_ERROR(("ret from usb_handle_packet > BUFF_SIZE"));
return USB_RET_PROCERR;
}
// Bochs specific code
if (ret > 0) {
if (p->pid == USB_TOKEN_SETUP) {
// TODO: This is a hack. dev->handle_packet() should return the amount of bytes
// it received, not the amount it anticipates on receiving/sending in the next packet.
ret = 8;
} else if (p->pid == USB_TOKEN_IN) {
if (BX_EHCI_THIS transfer(p) != 0) {
return USB_RET_PROCERR;
}
}
}
return ret;
}
int bx_usb_ehci_c::process_itd(EHCIitd *itd, Bit32u addr)
{
// TODO
BX_PANIC(("process_itd() not implemented yet"));
return 0;
}
int bx_usb_ehci_c::state_waitlisthead(int async)
{
EHCIqh qh;
int i = 0;
int again = 0;
Bit32u entry = BX_EHCI_THIS hub.op_regs.AsyncListAddr;
/* set reclamation flag at start event (4.8.6) */
if (async) {
BX_EHCI_THIS hub.op_regs.UsbSts.recl = 1;
}
BX_EHCI_THIS queues_rip_unused(async);
/* Find the head of the list (4.9.1.1) */
for (i = 0; i < MAX_QH; i++) {
get_dwords(NLPTR_GET(entry), (Bit32u *) &qh, sizeof(EHCIqh) >> 2);
if (qh.epchar & QH_EPCHAR_H) {
if (async) {
entry |= (NLPTR_TYPE_QH << 1);
}
BX_EHCI_THIS set_fetch_addr(async, entry);
BX_EHCI_THIS set_state(async, EST_FETCHENTRY);
again = 1;
goto out;
}
entry = qh.next;
if (entry == BX_EHCI_THIS hub.op_regs.AsyncListAddr) {
break;
}
}
/* no head found for list. */
BX_EHCI_THIS set_state(async, EST_ACTIVE);
out:
return again;
}
int bx_usb_ehci_c::state_fetchentry(int async)
{
int again = 0;
Bit32u entry = BX_EHCI_THIS get_fetch_addr(async);
if (NLPTR_TBIT(entry)) {
BX_EHCI_THIS set_state(async, EST_ACTIVE);
goto out;
}
/* section 4.8, only QH in async schedule */
if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
BX_ERROR(("non queue head request in async schedule"));
return -1;
}
switch (NLPTR_TYPE_GET(entry)) {
case NLPTR_TYPE_QH:
BX_EHCI_THIS set_state(async, EST_FETCHQH);
again = 1;
break;
case NLPTR_TYPE_ITD:
BX_EHCI_THIS set_state(async, EST_FETCHITD);
again = 1;
break;
case NLPTR_TYPE_STITD:
BX_EHCI_THIS set_state(async, EST_FETCHSITD);
again = 1;
break;
default:
/* TODO: handle FSTN type */
BX_ERROR(("FETCHENTRY: entry at %X is of type %d which is not supported yet",
entry, NLPTR_TYPE_GET(entry)));
return -1;
}
out:
return again;
}
EHCIQueue *bx_usb_ehci_c::state_fetchqh(int async)
{
EHCIPacket *p;
Bit32u entry, devaddr, endp;
EHCIQueue *q;
EHCIqh qh;
entry = BX_EHCI_THIS get_fetch_addr(async);
q = BX_EHCI_THIS find_queue_by_qh(entry, async);
if (NULL == q) {
q = BX_EHCI_THIS alloc_queue(entry, async);
}
p = QTAILQ_FIRST(&q->packets);
q->seen++;
if (q->seen > 1) {
/* we are going in circles -- stop processing */
BX_EHCI_THIS set_state(async, EST_ACTIVE);
q = NULL;
goto out;
}
get_dwords(NLPTR_GET(q->qhaddr), (Bit32u*) &qh, sizeof(EHCIqh) >> 2);
/*
* The overlay area of the qh should never be changed by the guest,
* except when idle, in which case the reset is a nop.
*/
devaddr = get_field(qh.epchar, QH_EPCHAR_DEVADDR);
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)) ||
(memcmp(&qh.current_qtd, &q->qh.current_qtd,
9 * sizeof(Bit32u)) != 0) ||
(q->dev != NULL && q->dev->get_address() != devaddr)) {
if (BX_EHCI_THIS reset_queue(q) > 0) {
BX_ERROR(("guest updated active QH"));
}
p = NULL;
}
q->qh = qh;
if (q->dev == NULL) {
q->dev = BX_EHCI_THIS find_device(devaddr);
}
/* I/O finished -- continue processing queue */
if (p && p->async == EHCI_ASYNC_FINISHED) {
BX_EHCI_THIS set_state(async, EST_EXECUTING);
goto out;
}
if (async && (q->qh.epchar & QH_EPCHAR_H)) {
/* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
if (BX_EHCI_THIS hub.op_regs.UsbSts.recl) {
BX_EHCI_THIS hub.op_regs.UsbSts.recl = 0;
} else {
BX_DEBUG(("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset - done processing",
q->qhaddr));
BX_EHCI_THIS set_state(async, EST_ACTIVE);
q = NULL;
goto out;
}
}
#if EHCI_DEBUG
if (q->qhaddr != q->qh.next) {
BX_DEBUG(("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x",
q->qhaddr,
q->qh.epchar & QH_EPCHAR_H,
q->qh.token & QTD_TOKEN_HALT,
q->qh.token & QTD_TOKEN_ACTIVE,
q->qh.next));
}
#endif
if (q->qh.token & QTD_TOKEN_HALT) {
BX_EHCI_THIS set_state(async, EST_HORIZONTALQH);
} else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
(NLPTR_TBIT(q->qh.current_qtd) == 0)) {
q->qtdaddr = q->qh.current_qtd;
BX_EHCI_THIS set_state(async, EST_FETCHQTD);
} else {
/* EHCI spec version 1.0 Section 4.10.2 */
BX_EHCI_THIS set_state(async, EST_ADVANCEQUEUE);
}
out:
return q;
}
int bx_usb_ehci_c::state_fetchitd(int async)
{
Bit32u entry;
EHCIitd itd;
BX_ASSERT(!async);
entry = BX_EHCI_THIS get_fetch_addr(async);
get_dwords(NLPTR_GET(entry), (Bit32u*) &itd, sizeof(EHCIitd) >> 2);
if (BX_EHCI_THIS process_itd(&itd, entry) != 0) {
return -1;
}
put_dwords(NLPTR_GET(entry), (Bit32u*) &itd, sizeof(EHCIitd) >> 2);
BX_EHCI_THIS set_fetch_addr(async, itd.next);
BX_EHCI_THIS set_state(async, EST_FETCHENTRY);
return 1;
}
int bx_usb_ehci_c::state_fetchsitd(int async)
{
Bit32u entry;
EHCIsitd sitd;
BX_ASSERT(!async);
entry = BX_EHCI_THIS get_fetch_addr(async);
get_dwords(NLPTR_GET(entry), (Bit32u*)&sitd, sizeof(EHCIsitd) >> 2);
if (!(sitd.results & SITD_RESULTS_ACTIVE)) {
/* siTD is not active, nothing to do */;
} else {
/* TODO: split transfers are not implemented */
BX_ERROR(("WARNING: Skipping active siTD"));
}
BX_EHCI_THIS set_fetch_addr(async, sitd.next);
BX_EHCI_THIS set_state(async, EST_FETCHENTRY);
return 1;
}
int bx_usb_ehci_c::state_advqueue(EHCIQueue *q)
{
/*
* want data and alt-next qTD is valid
*/
if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
(NLPTR_TBIT(q->qh.altnext_qtd) == 0)) {
q->qtdaddr = q->qh.altnext_qtd;
BX_EHCI_THIS set_state(q->async, EST_FETCHQTD);
/*
* next qTD is valid
*/
} else if (NLPTR_TBIT(q->qh.next_qtd) == 0) {
q->qtdaddr = q->qh.next_qtd;
BX_EHCI_THIS set_state(q->async, EST_FETCHQTD);
/*
* no valid qTD, try next QH
*/
} else {
BX_EHCI_THIS set_state(q->async, EST_HORIZONTALQH);
}
return 1;
}
int bx_usb_ehci_c::state_fetchqtd(EHCIQueue *q)
{
EHCIqtd qtd;
EHCIPacket *p;
int again = 0;
get_dwords(NLPTR_GET(q->qtdaddr), (Bit32u*) &qtd, sizeof(EHCIqtd) >> 2);
p = QTAILQ_FIRST(&q->packets);
if (p != NULL) {
if (p->qtdaddr != q->qtdaddr ||
(!NLPTR_TBIT(p->qtd.next) && (p->qtd.next != qtd.next)) ||
(!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd.altnext)) ||
p->qtd.bufptr[0] != qtd.bufptr[0]) {
BX_EHCI_THIS cancel_queue(q);
BX_ERROR(("guest updated active QH or qTD"));
p = NULL;
} else {
p->qtd = qtd;
BX_EHCI_THIS qh_do_overlay(q);
}
}
if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
if (p != NULL) {
/* transfer canceled by guest (clear active) */
BX_EHCI_THIS cancel_queue(q);
p = NULL;
}
BX_EHCI_THIS set_state(q->async, EST_HORIZONTALQH);
again = 1;
} else if (p != NULL) {
switch (p->async) {
case EHCI_ASYNC_NONE:
/* Should never happen packet should at least be initialized */
BX_PANIC(("Should never happen"));
break;
case EHCI_ASYNC_INITIALIZED:
/* Previously nacked packet (likely interrupt ep) */
BX_EHCI_THIS set_state(q->async, EST_EXECUTE);
break;
case EHCI_ASYNC_INFLIGHT:
/* Unfinished async handled packet, go horizontal */
BX_EHCI_THIS set_state(q->async, EST_HORIZONTALQH);
break;
case EHCI_ASYNC_FINISHED:
/*
* We get here when advqueue moves to a packet which is already
* finished, which can happen with packets queued up by fill_queue
*/
BX_EHCI_THIS set_state(q->async, EST_EXECUTING);
break;
}
again = 1;
} else {
p = BX_EHCI_THIS alloc_packet(q);
p->qtdaddr = q->qtdaddr;
p->qtd = qtd;
BX_EHCI_THIS set_state(q->async, EST_EXECUTE);
again = 1;
}
return again;
}
int bx_usb_ehci_c::state_horizqh(EHCIQueue *q)
{
int again = 0;
if (BX_EHCI_THIS get_fetch_addr(q->async) != q->qh.next) {
BX_EHCI_THIS set_fetch_addr(q->async, q->qh.next);
BX_EHCI_THIS set_state(q->async, EST_FETCHENTRY);
again = 1;
} else {
BX_EHCI_THIS set_state(q->async, EST_ACTIVE);
}
return again;
}
int bx_usb_ehci_c::fill_queue(EHCIPacket *p)
{
EHCIQueue *q = p->queue;
EHCIqtd qtd = p->qtd;
Bit32u qtdaddr;
for (;;) {
if (NLPTR_TBIT(qtd.altnext) == 0) {
break;
}
if (NLPTR_TBIT(qtd.next) != 0) {
break;
}
qtdaddr = qtd.next;
get_dwords(NLPTR_GET(qtdaddr), (Bit32u*) &qtd, sizeof(EHCIqtd) >> 2);
if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
break;
}
p = BX_EHCI_THIS alloc_packet(q);
p->qtdaddr = qtdaddr;
p->qtd = qtd;
p->usb_status = BX_EHCI_THIS execute(p);
if (p->usb_status == USB_RET_PROCERR) {
break;
}
BX_ASSERT(p->usb_status == USB_RET_ASYNC);
p->async = EHCI_ASYNC_INFLIGHT;
}
return p->usb_status;
}
int bx_usb_ehci_c::state_execute(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
int again = 0;
BX_ASSERT(p != NULL);
BX_ASSERT(p->qtdaddr == q->qtdaddr);
if (BX_EHCI_THIS qh_do_overlay(q) != 0) {
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
// TODO Windows does not seem to ever set the MULT field
if (!q->async) {
int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT);
if (!transactCtr) {
BX_EHCI_THIS set_state(q->async, EST_HORIZONTALQH);
again = 1;
goto out;
}
}
if (q->async) {
BX_EHCI_THIS hub.op_regs.UsbSts.recl = 1;
}
p->usb_status = BX_EHCI_THIS execute(p);
if (p->usb_status == USB_RET_PROCERR) {
again = -1;
goto out;
}
if (p->usb_status == USB_RET_ASYNC) {
BX_EHCI_THIS flush_qh(q);
p->async = EHCI_ASYNC_INFLIGHT;
BX_EHCI_THIS set_state(q->async, EST_HORIZONTALQH);
again = (BX_EHCI_THIS fill_queue(p) == USB_RET_PROCERR) ? -1 : 1;
goto out;
}
BX_EHCI_THIS set_state(q->async, EST_EXECUTING);
again = 1;
out:
return again;
}
int bx_usb_ehci_c::state_executing(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
BX_ASSERT(p != NULL);
BX_ASSERT(p->qtdaddr == q->qtdaddr);
BX_EHCI_THIS execute_complete(q);
// 4.10.3
if (!q->async) {
int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT);
transactCtr--;
set_field(&q->qh.epcap, transactCtr, QH_EPCAP_MULT);
// 4.10.3, bottom of page 82, should exit this state when transaction
// counter decrements to 0
}
/* 4.10.5 */
if (p->usb_status == USB_RET_NAK) {
BX_EHCI_THIS set_state(q->async, EST_HORIZONTALQH);
} else {
BX_EHCI_THIS set_state(q->async, EST_WRITEBACK);
}
BX_EHCI_THIS flush_qh(q);
return 1;
}
int bx_usb_ehci_c::state_writeback(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
Bit32u *qtd, addr;
int again = 0;
/* Write back the QTD from the QH area */
BX_ASSERT(p != NULL);
BX_ASSERT(p->qtdaddr == q->qtdaddr);
qtd = (Bit32u*) &q->qh.next_qtd;
addr = NLPTR_GET(p->qtdaddr);
put_dwords(addr + 2 * sizeof(Bit32u), qtd + 2, 2);
BX_EHCI_THIS free_packet(p);
/*
* 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.
*/
if (q->qh.token & QTD_TOKEN_HALT) {
/*
* We should not do any further processing on a halted queue!
* This is esp. important for bulk endpoints with pipelining enabled
* (redirection to a real USB device), where we must cancel all the
* transfers after this one so that:
* 1) If they've completed already, they are not processed further
* causing more stalls, originating from the same failed transfer
* 2) If still in flight, they are cancelled before the guest does
* a clear stall, otherwise the guest and device can loose sync!
*/
while ((p = QTAILQ_FIRST(&q->packets)) != NULL) {
BX_EHCI_THIS free_packet(p);
}
BX_EHCI_THIS set_state(q->async, EST_HORIZONTALQH);
again = 1;
} else {
BX_EHCI_THIS set_state(q->async, EST_ADVANCEQUEUE);
again = 1;
}
return again;
}
void bx_usb_ehci_c::advance_state(int async)
{
EHCIQueue *q = NULL;
int again;
do {
switch (BX_EHCI_THIS get_state(async)) {
case EST_WAITLISTHEAD:
again = BX_EHCI_THIS state_waitlisthead(async);
break;
case EST_FETCHENTRY:
again = BX_EHCI_THIS state_fetchentry(async);
break;
case EST_FETCHQH:
q = BX_EHCI_THIS state_fetchqh(async);
if (q != NULL) {
assert(q->async == async);
again = 1;
} else {
again = 0;
}
break;
case EST_FETCHITD:
again = BX_EHCI_THIS state_fetchitd(async);
break;
case EST_FETCHSITD:
again = BX_EHCI_THIS state_fetchsitd(async);
break;
case EST_ADVANCEQUEUE:
again = BX_EHCI_THIS state_advqueue(q);
break;
case EST_FETCHQTD:
again = BX_EHCI_THIS state_fetchqtd(q);
break;
case EST_HORIZONTALQH:
again = BX_EHCI_THIS state_horizqh(q);
break;
case EST_EXECUTE:
again = BX_EHCI_THIS state_execute(q);
if (async) {
BX_EHCI_THIS hub.async_stepdown = 0;
}
break;
case EST_EXECUTING:
assert(q != NULL);
if (async) {
BX_EHCI_THIS hub.async_stepdown = 0;
}
again = BX_EHCI_THIS state_executing(q);
break;
case EST_WRITEBACK:
assert(q != NULL);
again = BX_EHCI_THIS state_writeback(q);
break;
default:
BX_ERROR(("Bad state!"));
again = -1;
break;
}
if (again < 0) {
BX_ERROR(("processing error - resetting ehci HC"));
BX_EHCI_THIS reset_hc();
again = 0;
}
} while (again);
}
void bx_usb_ehci_c::advance_async_state(void)
{
const int async = 1;
switch (BX_EHCI_THIS get_state(async)) {
case EST_INACTIVE:
if (!BX_EHCI_THIS hub.op_regs.UsbCmd.ase) {
break;
}
BX_EHCI_THIS set_state(async, EST_ACTIVE);
// No break, fall through to ACTIVE
case EST_ACTIVE:
if (!BX_EHCI_THIS hub.op_regs.UsbCmd.ase) {
BX_EHCI_THIS queues_rip_all(async);
BX_EHCI_THIS set_state(async, EST_INACTIVE);
break;
}
/* make sure guest has acknowledged the doorbell interrupt */
/* TO-DO: is this really needed? */
if (BX_EHCI_THIS hub.op_regs.UsbSts.inti & USBSTS_IAA) {
BX_DEBUG(("IAA status bit still set."));
break;
}
/* check that address register has been set */
if (BX_EHCI_THIS hub.op_regs.AsyncListAddr == 0) {
break;
}
BX_EHCI_THIS set_state(async, EST_WAITLISTHEAD);
BX_EHCI_THIS advance_state(async);
/* 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 (BX_EHCI_THIS hub.op_regs.UsbCmd.iaad) {
/* Remove all unseen qhs from the async qhs queue */
BX_EHCI_THIS queues_rip_unseen(async);
BX_EHCI_THIS hub.op_regs.UsbCmd.iaad = 0;
BX_EHCI_THIS raise_irq(USBSTS_IAA);
}
break;
default:
/* this should only be due to a developer mistake */
BX_PANIC(("Bad asynchronous state %d. Resetting to active", BX_EHCI_THIS hub.astate));
BX_EHCI_THIS set_state(async, EST_ACTIVE);
}
}
void bx_usb_ehci_c::advance_periodic_state(void)
{
Bit32u entry;
Bit32u list;
const int async = 0;
// 4.6
switch (BX_EHCI_THIS get_state(async)) {
case EST_INACTIVE:
if (!(BX_EHCI_THIS hub.op_regs.FrIndex & 7) && BX_EHCI_THIS hub.op_regs.UsbCmd.pse) {
BX_EHCI_THIS set_state(async, EST_ACTIVE);
// No break, fall through to ACTIVE
} else
break;
case EST_ACTIVE:
if (!(BX_EHCI_THIS hub.op_regs.FrIndex & 7) && !BX_EHCI_THIS hub.op_regs.UsbCmd.pse) {
BX_EHCI_THIS queues_rip_all(async);
BX_EHCI_THIS set_state(async, EST_INACTIVE);
break;
}
list = BX_EHCI_THIS hub.op_regs.PeriodicListBase & 0xfffff000;
/* check that register has been set */
if (list == 0) {
break;
}
list |= ((BX_EHCI_THIS hub.op_regs.FrIndex & 0x1ff8) >> 1);
DEV_MEM_READ_PHYSICAL(list, 4, (Bit8u*)&entry);
BX_DEBUG(("PERIODIC state adv fr=%d. [%08X] -> %08X",
BX_EHCI_THIS hub.op_regs.FrIndex / 8, list, entry));
BX_EHCI_THIS set_fetch_addr(async, entry);
BX_EHCI_THIS set_state(async, EST_FETCHENTRY);
BX_EHCI_THIS advance_state(async);
BX_EHCI_THIS queues_rip_unused(async);
break;
default:
/* this should only be due to a developer mistake */
BX_PANIC(("Bad periodic state %d. Resetting to active", BX_EHCI_THIS hub.pstate));
}
}
void bx_usb_ehci_c::update_frindex(int frames)
{
int i;
if (!BX_EHCI_THIS hub.op_regs.UsbCmd.rs) {
return;
}
for (i = 0; i < frames; i++) {
BX_EHCI_THIS hub.op_regs.FrIndex += 8;
if (BX_EHCI_THIS hub.op_regs.FrIndex == 0x00002000) {
BX_EHCI_THIS raise_irq(USBSTS_FLR);
}
if (BX_EHCI_THIS hub.op_regs.FrIndex == 0x00004000) {
BX_EHCI_THIS raise_irq(USBSTS_FLR);
BX_EHCI_THIS hub.op_regs.FrIndex = 0;
if (BX_EHCI_THIS hub.usbsts_frindex >= 0x00004000) {
BX_EHCI_THIS hub.usbsts_frindex -= 0x00004000;
} else {
BX_EHCI_THIS hub.usbsts_frindex = 0;
}
}
}
}
void bx_usb_ehci_c::ehci_frame_handler(void *this_ptr)
{
bx_usb_ehci_c *class_ptr = (bx_usb_ehci_c *) this_ptr;
class_ptr->ehci_frame_timer();
}
// Frame timer called once every 1.000 msec
void bx_usb_ehci_c::ehci_frame_timer(void)
{
int need_timer = 0;
Bit64u t_now;
Bit64u usec_elapsed;
int frames, skipped_frames;
int i;
t_now = bx_pc_system.time_usec();
usec_elapsed = t_now - BX_EHCI_THIS hub.last_run_usec;
frames = (int)(usec_elapsed / FRAME_TIMER_USEC);
if (BX_EHCI_THIS periodic_enabled() || (BX_EHCI_THIS hub.pstate != EST_INACTIVE)) {
need_timer++;
BX_EHCI_THIS hub.async_stepdown = 0;
if (frames > (int)BX_EHCI_THIS maxframes) {
skipped_frames = frames - BX_EHCI_THIS maxframes;
BX_EHCI_THIS update_frindex(skipped_frames);
BX_EHCI_THIS hub.last_run_usec += FRAME_TIMER_USEC * skipped_frames;
frames -= skipped_frames;
BX_DEBUG(("WARNING - EHCI skipped %d frames", skipped_frames));
}
for (i = 0; i < frames; i++) {
/*
* If we're running behind schedule, we should not catch up
* too fast, as that will make some guests unhappy:
* 1) We must process a minimum of MIN_FR_PER_TICK frames,
* otherwise we will never catch up
* 2) Process frames until the guest has requested an irq (IOC)
*/
if (i >= MIN_FR_PER_TICK) {
BX_EHCI_THIS commit_irq();
if ((BX_EHCI_THIS hub.op_regs.UsbSts.inti & BX_EHCI_THIS hub.op_regs.UsbIntr) > 0){
break;
}
}
BX_EHCI_THIS update_frindex(1);
BX_EHCI_THIS advance_periodic_state();
BX_EHCI_THIS hub.last_run_usec += FRAME_TIMER_USEC;
}
} else {
if (BX_EHCI_THIS hub.async_stepdown < BX_EHCI_THIS maxframes / 2) {
BX_EHCI_THIS hub.async_stepdown++;
}
BX_EHCI_THIS update_frindex(frames);
BX_EHCI_THIS hub.last_run_usec += FRAME_TIMER_USEC * frames;
}
/* Async is not inside loop since it executes everything it can once
* called
*/
if (BX_EHCI_THIS async_enabled() || (BX_EHCI_THIS hub.astate != EST_INACTIVE)) {
need_timer++;
BX_EHCI_THIS advance_async_state();
}
BX_EHCI_THIS commit_irq();
if (BX_EHCI_THIS hub.usbsts_pending) {
need_timer++;
BX_EHCI_THIS hub.async_stepdown = 0;
}
if (need_timer) {
// TODO: modify timer not implemented
}
}
// runtime configuration handler (called when continuing simulation)
void bx_usb_ehci_c::runtime_config_handler(void *this_ptr)
{
bx_usb_ehci_c *class_ptr = (bx_usb_ehci_c *) this_ptr;
class_ptr->runtime_config();
}
void bx_usb_ehci_c::runtime_config(void)
{
int i;
char pname[6];
usbdev_type type = USB_DEV_TYPE_NONE;
for (i = 0; i < USB_EHCI_PORTS; i++) {
// device change support
if ((BX_EHCI_THIS device_change & (1 << i)) != 0) {
if (BX_EHCI_THIS hub.usb_port[i].device == NULL) {
BX_INFO(("USB port #%d: device connect", i+1));
sprintf(pname, "port%d", i + 1);
init_device(i, (bx_list_c*)SIM->get_param(pname, SIM->get_param(BXPN_USB_EHCI)));
} else {
BX_INFO(("USB port #%d: device disconnect", i+1));
if (BX_EHCI_THIS hub.usb_port[i].device != NULL) {
type = BX_EHCI_THIS hub.usb_port[i].device->get_type();
}
set_connect_status(i, type, 0);
}
BX_EHCI_THIS device_change &= ~(1 << i);
}
// forward to connected device
if (BX_EHCI_THIS hub.usb_port[i].device != NULL) {
BX_EHCI_THIS hub.usb_port[i].device->runtime_config();
}
}
}
// pci configuration space write callback handler
void bx_usb_ehci_c::pci_write_handler(Bit8u address, Bit32u value, unsigned io_len)
{
Bit8u value8, oldval;
if (((address >= 0x14) && (address <= 0x3b)) || (address > 0x80))
return;
for (unsigned i=0; i<io_len; i++) {
value8 = (value >> (i*8)) & 0xFF;
oldval = BX_EHCI_THIS pci_conf[address+i];
switch (address+i) {
case 0x04:
value8 &= 0x06; // (bit 0 is read only for this card) (we don't allow port IO)
BX_EHCI_THIS pci_conf[address+i] = value8;
break;
case 0x05: // disallowing write to command hi-byte
case 0x06: // disallowing write to status lo-byte (is that expected?)
case 0x0d: //
case 0x3d: //
case 0x3e: //
case 0x3f: //
case 0x60: //
break;
case 0x3c:
if (value8 != oldval) {
BX_INFO(("new irq line = %d", value8));
BX_EHCI_THIS pci_conf[address+i] = value8;
}
break;
case 0x2c:
case 0x2d:
case 0x2e:
case 0x2f:
if (BX_EHCI_THIS pci_conf[0x80] & 1) {
BX_EHCI_THIS pci_conf[address+i] = value8;
}
break;
case 0x61:
value8 &= 0x3f;
default:
BX_EHCI_THIS pci_conf[address+i] = value8;
}
}
if (io_len == 1)
BX_DEBUG(("write PCI register 0x%02X value 0x%02X (len=1)", address, value));
else if (io_len == 2)
BX_DEBUG(("write PCI register 0x%02X value 0x%04X (len=2)", address, value));
else if (io_len == 4)
BX_DEBUG(("write PCI register 0x%02X value 0x%08X (len=4)", address, value));
}
// USB runtime parameter handler
const char *bx_usb_ehci_c::usb_param_handler(bx_param_string_c *param, int set,
const char *oldval, const char *val, int maxlen)
{
int portnum;
if (set) {
portnum = atoi((param->get_parent())->get_name()+4) - 1;
bx_bool empty = ((strlen(val) == 0) || (!strcmp(val, "none")));
if ((portnum >= 0) && (portnum < USB_EHCI_PORTS)) {
if (empty && (BX_EHCI_THIS hub.usb_port[portnum].device != NULL)) {
BX_EHCI_THIS device_change |= (1 << portnum);
} else if (!empty && (BX_EHCI_THIS hub.usb_port[portnum].device == NULL)) {
BX_EHCI_THIS device_change |= (1 << portnum);
}
} else {
BX_PANIC(("usb_param_handler called with unexpected parameter '%s'", param->get_name()));
}
}
return val;
}
#endif // BX_SUPPORT_PCI && BX_SUPPORT_USB_EHCI