Bochs/bochs/iodev/usb/ohci_core.cc
Benjamin David Lunt 0d1d6c671a
Ehci companion (#104)
This PR adds the ability to choose which companion controller the EHCI
uses.
`#usb_ehci: enabled=1, companion=uhci`
`#usb_ehci: enabled=1, companion=ohci`

UHCI is enabled by default:
`#usb_ehci: enabled=1`

This breaks up `usb_ohci.cc/h` into `usb_ohci.cc/h` and `ohci_core.cc/h`
to be like the existing `usb_uhci.cc/h` and `uhci_core.cc/h`.
This adds the ability to define a Port Routing string in the
`HcspPortRoute` register, and is optional via the `EHCI_PORT_ROUTE`
define.

Currently `EHCI_N_CC` and `EHCI_N_PCC` must remain as currently set (3
and 2 respectively), however with a little more work, I think these can
be modified and have up to 7 companion controllers with more ports per
companion, as long as the combined total is <= 15 total ports.

Please verify the `Makefile.in` changes. I do not use GCC and can not
verify it is correct.
2023-10-20 21:00:59 +03:00

1399 lines
48 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2009-2023 Benjamin D Lunt (fys [at] fysnet [dot] net)
// 2009-2023 The Bochs Project
//
// 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
/////////////////////////////////////////////////////////////////////////
// USB OHCI adapter
// Notes: See usb_ohci.cc
// 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_OHCI
#include "pci.h"
#include "usb_common.h"
#include "ohci_core.h"
#define LOG_THIS
const char *usb_ohci_port_name[] = {
"HCRevision ",
"HCControl ",
"HCCommandStatus ",
"HCInterruptStatus ",
"HCInterruptEnable ",
"HCInterruptDisable",
"HCHCCA ",
"HCPeriodCurrentED ",
"HCControlHeadED ",
"HCControlCurrentED",
"HCBulkHeadED ",
"HCBulkCurrentED ",
"HCDoneHead ",
"HCFmInterval ",
"HCFmRemaining ",
"HCFmNumber ",
"HCPeriodicStart ",
"HCLSThreshold ",
"HCRhDescriptorA ",
"HCRhDescriptorB ",
"HCRhStatus ",
"HCRhPortStatus0 ",
"HCRhPortStatus1 ",
"HCRhPortStatus2 ",
"HCRhPortStatus3 ",
" **unknown** "
};
// the device object
bx_ohci_core_c::bx_ohci_core_c()
{
put("ohci_core", "OHCIC");
memset((void*)&hub, 0, sizeof(bx_ohci_core_t));
hub.timer_index = BX_NULL_TIMER_HANDLE;
}
bx_ohci_core_c::~bx_ohci_core_c()
{
BX_DEBUG(("Exit"));
}
void bx_ohci_core_c::init_ohci(Bit8u devfunc, Bit16u venid, Bit16u devid, Bit8u rev, Bit8u headt, Bit8u intp)
{
// Call our frame timer routine every 1mS (1,000uS)
// Continuous and active
hub.timer_index =
DEV_register_timer(this, ohci_timer_handler, 1000, 1,1, "ohci.frame_timer");
hub.devfunc = devfunc;
DEV_register_pci_handlers(this, &hub.devfunc, BX_PLUGIN_USB_OHCI, "USB OHCI");
// initialize readonly registers
init_pci_conf(venid, devid, rev, 0x0C0310, headt, intp);
init_bar_mem(0, 4096, read_handler, write_handler);
hub.ohci_done_count = 7;
hub.use_control_head = 0;
hub.use_bulk_head = 0;
hub.sof_time = 0;
packets = NULL;
}
void bx_ohci_core_c::reset_ohci(unsigned type)
{
unsigned i;
if (type == BX_RESET_HARDWARE) {
static const struct reset_vals_t {
unsigned addr;
unsigned char val;
} reset_vals[] = {
{ 0x04, 0x06 }, { 0x05, 0x00 }, // command_io
{ 0x06, 0x10 }, { 0x07, 0x02 }, // status (bit 4 = 1, has capabilities list.)
{ 0x0d, 0x40 }, // bus latency
// address space 0x10 - 0x13
{ 0x10, 0x00 }, { 0x11, 0x50 }, //
{ 0x12, 0x00 }, { 0x13, 0xE1 }, //
{ 0x2C, 0xC1 }, { 0x2D, 0x11 }, // subsystem vendor ID
{ 0x2E, 0x03 }, { 0x2F, 0x58 }, // subsystem ID
{ 0x34, 0x50 }, // offset of capabilities list within configuration space
{ 0x3c, 0x0B }, // IRQ
{ 0x3E, 0x03 }, // minimum time bus master needs PCI bus ownership, in 250ns units
{ 0x3F, 0x56 }, // maximum latency, in 250ns units (bus masters only) (read-only)
// capabilities list:
{ 0x50, 0x01 }, //
{ 0x51, 0x00 }, //
{ 0x52, 0x02 }, //
{ 0x53, 0x76 }, //
{ 0x54, 0x00 }, //
{ 0x55, 0x20 }, //
{ 0x56, 0x00 }, //
{ 0x57, 0x1F }, //
};
for (i = 0; i < sizeof(reset_vals) / sizeof(*reset_vals); ++i) {
pci_conf[reset_vals[i].addr] = reset_vals[i].val;
}
}
reset_hc();
}
void bx_ohci_core_c::reset_hc()
{
// reset locals
hub.ohci_done_count = 7;
// HcRevision
hub.op_regs.HcRevision = 0x0110;
// HcControl
hub.op_regs.HcControl.reserved = 0;
hub.op_regs.HcControl.rwe = 0;
hub.op_regs.HcControl.rwc = 0;
hub.op_regs.HcControl.ir = 0;
hub.op_regs.HcControl.hcfs = OHCI_USB_RESET;
hub.op_regs.HcControl.ble = 0;
hub.op_regs.HcControl.cle = 0;
hub.op_regs.HcControl.ie = 0;
hub.op_regs.HcControl.ple = 0;
hub.op_regs.HcControl.cbsr = 0;
// HcCommandStatus
hub.op_regs.HcCommandStatus.reserved0 = 0x000000;
hub.op_regs.HcCommandStatus.soc = 0;
hub.op_regs.HcCommandStatus.reserved1 = 0x000000;
hub.op_regs.HcCommandStatus.ocr = 0;
hub.op_regs.HcCommandStatus.blf = 0;
hub.op_regs.HcCommandStatus.clf = 0;
hub.op_regs.HcCommandStatus.hcr = 0;
// HcInterruptStatus
hub.op_regs.HcInterruptStatus = 0x00000000;
// HcInterruptEnable
hub.op_regs.HcInterruptEnable = OHCI_INTR_MIE;
// HcHCCA
hub.op_regs.HcHCCA = 0x00000000;
// HcPeriodCurrentED
hub.op_regs.HcPeriodCurrentED = 0x00000000;
// HcControlHeadED
hub.op_regs.HcControlHeadED = 0x00000000;
// HcControlCurrentED
hub.op_regs.HcControlCurrentED = 0x00000000;
// HcBulkHeadED
hub.op_regs.HcBulkHeadED = 0x00000000;
// HcBulkCurrentED
hub.op_regs.HcBulkCurrentED = 0x00000000;
// HcDoneHead
hub.op_regs.HcDoneHead = 0x00000000;
// HcFmInterval
hub.op_regs.HcFmInterval.fit = 0;
hub.op_regs.HcFmInterval.fsmps = 0;
hub.op_regs.HcFmInterval.reserved = 0;
hub.op_regs.HcFmInterval.fi = 0x2EDF;
// HcFmRemaining
hub.op_regs.HcFmRemainingToggle = 0;
// HcFmNumber
hub.op_regs.HcFmNumber = 0x00000000;
// HcPeriodicStart
hub.op_regs.HcPeriodicStart = 0x00000000;
// HcLSThreshold
hub.op_regs.HcLSThreshold = 0x0628;
// HcRhDescriptorA
hub.op_regs.HcRhDescriptorA.potpgt = 0x10;
hub.op_regs.HcRhDescriptorA.reserved = 0;
hub.op_regs.HcRhDescriptorA.nocp = 0;
hub.op_regs.HcRhDescriptorA.ocpm = 1;
hub.op_regs.HcRhDescriptorA.dt = 0;
hub.op_regs.HcRhDescriptorA.nps = 0;
hub.op_regs.HcRhDescriptorA.psm = 1;
hub.op_regs.HcRhDescriptorA.ndp = USB_OHCI_PORTS;
// HcRhDescriptorB
hub.op_regs.HcRhDescriptorB.ppcm = ((1 << USB_OHCI_PORTS) - 1) << 1;
hub.op_regs.HcRhDescriptorB.dr = 0x0000;
// HcRhStatus
hub.op_regs.HcRhStatus.crwe = 0;
hub.op_regs.HcRhStatus.reserved0 = 0;
hub.op_regs.HcRhStatus.ocic = 0;
hub.op_regs.HcRhStatus.lpsc = 0;
hub.op_regs.HcRhStatus.drwe = 0;
hub.op_regs.HcRhStatus.reserved1 = 0;
hub.op_regs.HcRhStatus.oci = 0;
hub.op_regs.HcRhStatus.lps = 0;
// HcRhPortStatus[x]
for (int i=0; i<USB_OHCI_PORTS; i++) {
reset_port(i);
if (hub.usb_port[i].device != NULL) {
set_connect_status(i, 1);
}
}
while (packets != NULL) {
usb_cancel_packet(&packets->packet);
remove_async_packet(&packets, packets);
}
}
void bx_ohci_core_c::reset_port(int p)
{
hub.usb_port[p].HcRhPortStatus.reserved0 = 0;
hub.usb_port[p].HcRhPortStatus.prsc = 0;
hub.usb_port[p].HcRhPortStatus.ocic = 0;
hub.usb_port[p].HcRhPortStatus.pssc = 0;
hub.usb_port[p].HcRhPortStatus.pesc = 0;
hub.usb_port[p].HcRhPortStatus.reserved1 = 0;
hub.usb_port[p].HcRhPortStatus.lsda = 0;
hub.usb_port[p].HcRhPortStatus.pps = 0;
hub.usb_port[p].HcRhPortStatus.reserved2 = 0;
hub.usb_port[p].HcRhPortStatus.prs = 0;
hub.usb_port[p].HcRhPortStatus.poci = 0;
hub.usb_port[p].HcRhPortStatus.pss = 0;
hub.usb_port[p].HcRhPortStatus.pes = 0;
}
void bx_ohci_core_c::ohci_register_state(bx_list_c *parent)
{
unsigned i;
char portnum[8];
bx_list_c *hub1, *port, *reg;
bx_list_c *list = new bx_list_c(parent, "usb_ohci", "USB OHCI State");
hub1 = new bx_list_c(list, "hub");
reg = new bx_list_c(hub1, "HcControl");
BXRS_PARAM_BOOL(reg, rwe, hub.op_regs.HcControl.rwe);
BXRS_PARAM_BOOL(reg, rwc, hub.op_regs.HcControl.rwc);
BXRS_PARAM_BOOL(reg, ir, hub.op_regs.HcControl.ir);
BXRS_HEX_PARAM_FIELD(reg, hcfs, hub.op_regs.HcControl.hcfs);
BXRS_PARAM_BOOL(reg, ble, hub.op_regs.HcControl.ble);
BXRS_PARAM_BOOL(reg, cle, hub.op_regs.HcControl.cle);
BXRS_PARAM_BOOL(reg, ie, hub.op_regs.HcControl.ie);
BXRS_PARAM_BOOL(reg, ple, hub.op_regs.HcControl.ple);
BXRS_HEX_PARAM_FIELD(reg, cbsr, hub.op_regs.HcControl.cbsr);
reg = new bx_list_c(hub1, "HcCommandStatus");
BXRS_HEX_PARAM_FIELD(reg, soc, hub.op_regs.HcCommandStatus.soc);
BXRS_PARAM_BOOL(reg, ocr, hub.op_regs.HcCommandStatus.ocr);
BXRS_PARAM_BOOL(reg, blf, hub.op_regs.HcCommandStatus.blf);
BXRS_PARAM_BOOL(reg, clf, hub.op_regs.HcCommandStatus.clf);
BXRS_PARAM_BOOL(reg, hcr, hub.op_regs.HcCommandStatus.hcr);
BXRS_HEX_PARAM_FIELD(hub1, HcInterruptStatus, hub.op_regs.HcInterruptStatus);
BXRS_HEX_PARAM_FIELD(hub1, HcInterruptEnable, hub.op_regs.HcInterruptEnable);
BXRS_HEX_PARAM_FIELD(hub1, HcHCCA, hub.op_regs.HcHCCA);
BXRS_HEX_PARAM_FIELD(hub1, HcPeriodCurrentED, hub.op_regs.HcPeriodCurrentED);
BXRS_HEX_PARAM_FIELD(hub1, HcControlHeadED, hub.op_regs.HcControlHeadED);
BXRS_HEX_PARAM_FIELD(hub1, HcControlCurrentED, hub.op_regs.HcControlCurrentED);
BXRS_HEX_PARAM_FIELD(hub1, HcBulkHeadED, hub.op_regs.HcBulkHeadED);
BXRS_HEX_PARAM_FIELD(hub1, HcBulkCurrentED, hub.op_regs.HcBulkCurrentED);
BXRS_HEX_PARAM_FIELD(hub1, HcDoneHead, hub.op_regs.HcDoneHead);
reg = new bx_list_c(hub1, "HcFmInterval");
BXRS_PARAM_BOOL(reg, fit, hub.op_regs.HcFmInterval.fit);
BXRS_HEX_PARAM_FIELD(reg, fsmps, hub.op_regs.HcFmInterval.fsmps);
BXRS_HEX_PARAM_FIELD(reg, fi, hub.op_regs.HcFmInterval.fi);
BXRS_PARAM_BOOL(hub1, HcFmRemainingToggle, hub.op_regs.HcFmRemainingToggle);
BXRS_HEX_PARAM_FIELD(hub1, HcFmNumber, hub.op_regs.HcFmNumber);
BXRS_HEX_PARAM_FIELD(hub1, HcPeriodicStart, hub.op_regs.HcPeriodicStart);
reg = new bx_list_c(hub1, "HcRhDescriptorA");
BXRS_HEX_PARAM_FIELD(reg, potpgt, hub.op_regs.HcRhDescriptorA.potpgt);
BXRS_PARAM_BOOL(reg, nocp, hub.op_regs.HcRhDescriptorA.nocp);
BXRS_PARAM_BOOL(reg, ocpm, hub.op_regs.HcRhDescriptorA.ocpm);
BXRS_PARAM_BOOL(reg, nps, hub.op_regs.HcRhDescriptorA.nps);
BXRS_PARAM_BOOL(reg, psm, hub.op_regs.HcRhDescriptorA.psm);
reg = new bx_list_c(hub1, "HcRhDescriptorB");
BXRS_HEX_PARAM_FIELD(reg, ppcm, hub.op_regs.HcRhDescriptorB.ppcm);
BXRS_HEX_PARAM_FIELD(reg, dr, hub.op_regs.HcRhDescriptorB.dr);
reg = new bx_list_c(hub1, "HcRhStatus");
BXRS_PARAM_BOOL(reg, crwe, hub.op_regs.HcRhStatus.crwe);
BXRS_PARAM_BOOL(reg, ocic, hub.op_regs.HcRhStatus.ocic);
BXRS_PARAM_BOOL(reg, lpsc, hub.op_regs.HcRhStatus.lpsc);
BXRS_PARAM_BOOL(reg, drwe, hub.op_regs.HcRhStatus.drwe);
BXRS_PARAM_BOOL(reg, oci, hub.op_regs.HcRhStatus.oci);
BXRS_PARAM_BOOL(reg, lps, hub.op_regs.HcRhStatus.lps);
for (i=0; i<USB_OHCI_PORTS; i++) {
sprintf(portnum, "port%d", i+1);
port = new bx_list_c(hub1, portnum);
reg = new bx_list_c(port, "HcRhPortStatus");
BXRS_PARAM_BOOL(reg, prsc, hub.usb_port[i].HcRhPortStatus.prsc);
BXRS_PARAM_BOOL(reg, ocic, hub.usb_port[i].HcRhPortStatus.ocic);
BXRS_PARAM_BOOL(reg, pssc, hub.usb_port[i].HcRhPortStatus.pssc);
BXRS_PARAM_BOOL(reg, pesc, hub.usb_port[i].HcRhPortStatus.pesc);
BXRS_PARAM_BOOL(reg, csc, hub.usb_port[i].HcRhPortStatus.csc);
BXRS_PARAM_BOOL(reg, lsda, hub.usb_port[i].HcRhPortStatus.lsda);
BXRS_PARAM_BOOL(reg, pps, hub.usb_port[i].HcRhPortStatus.pps);
BXRS_PARAM_BOOL(reg, prs, hub.usb_port[i].HcRhPortStatus.prs);
BXRS_PARAM_BOOL(reg, poci, hub.usb_port[i].HcRhPortStatus.poci);
BXRS_PARAM_BOOL(reg, pss, hub.usb_port[i].HcRhPortStatus.pss);
BXRS_PARAM_BOOL(reg, pes, hub.usb_port[i].HcRhPortStatus.pes);
BXRS_PARAM_BOOL(reg, ccs, hub.usb_port[i].HcRhPortStatus.ccs);
// empty list for USB device state
new bx_list_c(port, "device");
}
BXRS_DEC_PARAM_FIELD(hub1, ohci_done_count, hub.ohci_done_count);
BXRS_PARAM_BOOL(hub1, use_control_head, hub.use_control_head);
BXRS_PARAM_BOOL(hub1, use_bulk_head, hub.use_bulk_head);
BXRS_DEC_PARAM_FIELD(hub1, sof_time, hub.sof_time);
// TODO: handle async packets
register_pci_state(hub1);
}
void bx_ohci_core_c::after_restore_state(void)
{
bx_pci_device_c::after_restore_pci_state(NULL);
for (int j=0; j<USB_OHCI_PORTS; j++) {
if (hub.usb_port[j].device != NULL) {
hub.usb_port[j].device->after_restore_state();
}
}
}
void bx_ohci_core_c::update_irq()
{
bool level = 0;
if ((hub.op_regs.HcInterruptEnable & OHCI_INTR_MIE) &&
(hub.op_regs.HcInterruptStatus & hub.op_regs.HcInterruptEnable)) {
level = 1;
BX_DEBUG(("Interrupt Fired."));
}
DEV_pci_set_irq(hub.devfunc, pci_conf[0x3d], level);
}
void bx_ohci_core_c::set_interrupt(Bit32u value)
{
hub.op_regs.HcInterruptStatus |= value;
update_irq();
}
bool bx_ohci_core_c::read_handler(bx_phy_address addr, unsigned len, void *data, void *param)
{
bx_ohci_core_c *class_ptr = (bx_ohci_core_c *) param;
return class_ptr->mem_read(addr, len, data);
}
bool bx_ohci_core_c::mem_read(bx_phy_address addr, unsigned len, void *data)
{
Bit32u val = 0x0;
int p = 0;
if (len != 4) {
BX_INFO(("Read at 0x%08X with len != 4 (%d)", (Bit32u)addr, len));
return 1;
}
if (addr & 3) {
BX_INFO(("Misaligned read at 0x%08X", (Bit32u)addr));
return 1;
}
Bit32u offset = (Bit32u)(addr - pci_bar[0].addr);
switch (offset) {
case 0x00: // HcRevision
val = hub.op_regs.HcRevision;
break;
case 0x04: // HcControl
val = (hub.op_regs.HcControl.reserved << 11)
| (hub.op_regs.HcControl.rwe ? 1 << 10 : 0)
| (hub.op_regs.HcControl.rwc ? 1 << 9 : 0)
| (hub.op_regs.HcControl.ir ? 1 << 8 : 0)
| (hub.op_regs.HcControl.hcfs << 6)
| (hub.op_regs.HcControl.ble ? 1 << 5 : 0)
| (hub.op_regs.HcControl.cle ? 1 << 4 : 0)
| (hub.op_regs.HcControl.ie ? 1 << 3 : 0)
| (hub.op_regs.HcControl.ple ? 1 << 2 : 0)
| (hub.op_regs.HcControl.cbsr << 0);
break;
case 0x08: // HcCommandStatus
val = (hub.op_regs.HcCommandStatus.reserved0 << 18)
| (hub.op_regs.HcCommandStatus.soc << 16)
| (hub.op_regs.HcCommandStatus.reserved1 << 4)
| (hub.op_regs.HcCommandStatus.ocr ? 1 << 3 : 0)
| (hub.op_regs.HcCommandStatus.blf ? 1 << 2 : 0)
| (hub.op_regs.HcCommandStatus.clf ? 1 << 1 : 0)
| (hub.op_regs.HcCommandStatus.hcr ? 1 << 0 : 0);
break;
case 0x0C: // HcInterruptStatus
val = hub.op_regs.HcInterruptStatus;
break;
case 0x10: // HcInterruptEnable
case 0x14: // HcInterruptDisable (reading this one returns that one)
val = hub.op_regs.HcInterruptEnable;
break;
case 0x18: // HcHCCA
val = hub.op_regs.HcHCCA;
break;
case 0x1C: // HcPeriodCurrentED
val = hub.op_regs.HcPeriodCurrentED;
break;
case 0x20: // HcControlHeadED
val = hub.op_regs.HcControlHeadED;
break;
case 0x24: // HcControlCurrentED
val = hub.op_regs.HcControlCurrentED;
break;
case 0x28: // HcBulkHeadED
val = hub.op_regs.HcBulkHeadED;
break;
case 0x2C: // HcBulkCurrentED
val = hub.op_regs.HcBulkCurrentED;
break;
case 0x30: // HcDoneHead
val = hub.op_regs.HcDoneHead;
break;
case 0x34: // HcFmInterval
val = (hub.op_regs.HcFmInterval.fit ? 1 << 31 : 0)
| (hub.op_regs.HcFmInterval.fsmps << 16)
| (hub.op_regs.HcFmInterval.reserved << 14)
| (hub.op_regs.HcFmInterval.fi << 0);
break;
case 0x38: // HcFmRemaining
val = get_frame_remaining();
break;
case 0x3C: // HcFmNumber
val = hub.op_regs.HcFmNumber;
break;
case 0x40: // HcPeriodicStart
val = hub.op_regs.HcPeriodicStart;
break;
case 0x44: // HcLSThreshold
val = hub.op_regs.HcLSThreshold;
break;
case 0x48: // HcRhDescriptorA
val = (hub.op_regs.HcRhDescriptorA.potpgt << 24)
| (hub.op_regs.HcRhDescriptorA.reserved << 13)
| (hub.op_regs.HcRhDescriptorA.nocp ? 1 << 12 : 0)
| (hub.op_regs.HcRhDescriptorA.ocpm ? 1 << 11 : 0)
| 0 //hub.op_regs.HcRhDescriptorA.dt << 10
| (hub.op_regs.HcRhDescriptorA.nps ? 1 << 9 : 0)
| (hub.op_regs.HcRhDescriptorA.psm ? 1 << 8 : 0)
| (hub.op_regs.HcRhDescriptorA.ndp << 0);
break;
case 0x4C: // HcRhDescriptorB
val = (hub.op_regs.HcRhDescriptorB.ppcm << 16)
| (hub.op_regs.HcRhDescriptorB.dr << 0);
break;
case 0x50: // HcRhStatus
val = (hub.op_regs.HcRhStatus.crwe ? 1 << 31 : 0)
| (hub.op_regs.HcRhStatus.reserved0 << 18)
| (hub.op_regs.HcRhStatus.ocic ? 1 << 17 : 0)
| 0 //hub.op_regs.HcRhStatus.lpsc << 16
| (hub.op_regs.HcRhStatus.drwe ? 1 << 15 : 0)
| (hub.op_regs.HcRhStatus.reserved1 << 2)
| (hub.op_regs.HcRhStatus.oci ? 1 << 1 : 0)
| (hub.op_regs.HcRhStatus.lps ? 1 << 0 : 0);
break;
case 0x60: // HcRhPortStatus[3]
#if (USB_OHCI_PORTS < 4)
val = 0;
break;
#endif
case 0x5C: // HcRhPortStatus[2]
#if (USB_OHCI_PORTS < 3)
val = 0;
break;
#endif
case 0x58: // HcRhPortStatus[1]
#if (USB_OHCI_PORTS < 2)
val = 0;
break;
#endif
case 0x54: // HcRhPortStatus[0]
p = (offset - 0x54) >> 2;
val = (hub.usb_port[p].HcRhPortStatus.reserved0 << 21)
| (hub.usb_port[p].HcRhPortStatus.prsc ? (1 << 20) : 0)
| (hub.usb_port[p].HcRhPortStatus.ocic ? (1 << 19) : 0)
| (hub.usb_port[p].HcRhPortStatus.pssc ? (1 << 18) : 0)
| (hub.usb_port[p].HcRhPortStatus.pesc ? (1 << 17) : 0)
| (hub.usb_port[p].HcRhPortStatus.csc ? (1 << 16) : 0)
| (hub.usb_port[p].HcRhPortStatus.reserved1 << 10)
| (hub.usb_port[p].HcRhPortStatus.lsda ? (1 << 9) : 0)
| (hub.usb_port[p].HcRhPortStatus.pps ? (1 << 8) : 0)
| (hub.usb_port[p].HcRhPortStatus.reserved2 << 5)
| (hub.usb_port[p].HcRhPortStatus.prs ? (1 << 4) : 0)
| (hub.usb_port[p].HcRhPortStatus.poci ? (1 << 3) : 0)
| (hub.usb_port[p].HcRhPortStatus.pss ? (1 << 2) : 0)
| (hub.usb_port[p].HcRhPortStatus.pes ? (1 << 1) : 0)
| (hub.usb_port[p].HcRhPortStatus.ccs ? (1 << 0) : 0);
break;
default:
BX_ERROR(("unsupported read from address=0x%08X!", (Bit32u)addr));
break;
}
int name = offset >> 2;
if (name > (0x60 >> 2))
name = 25;
//BX_INFO(("register read from address 0x%04X (%s): 0x%08X (len=%d)", (unsigned) addr, usb_ohci_port_name[name], (Bit32u) val, len));
*((Bit32u *) data) = val;
return 1;
}
bool bx_ohci_core_c::write_handler(bx_phy_address addr, unsigned len, void *data, void *param)
{
bx_ohci_core_c *class_ptr = (bx_ohci_core_c *) param;
return class_ptr->mem_write(addr, len, data);
}
bool bx_ohci_core_c::mem_write(bx_phy_address addr, unsigned len, void *data)
{
Bit32u value = *((Bit32u *) data);
Bit32u offset = (Bit32u)addr - pci_bar[0].addr;
int p, org_state;
int name = offset >> 2;
if (name > (0x60 >> 2))
name = 25;
//BX_INFO(("register write to address 0x%04X (%s): 0x%08X (len=%d)", (unsigned) addr, usb_ohci_port_name[name], (unsigned) value, len));
if (len != 4) {
BX_INFO(("Write at 0x%08X with len != 4 (%d)", (Bit32u)addr, len));
return 1;
}
if (addr & 3) {
BX_INFO(("Misaligned write at 0x%08X", (Bit32u)addr));
return 1;
}
switch (offset) {
case 0x00: // HcRevision
BX_ERROR(("Write to HcRevision ignored"));
break;
case 0x04: // HcControl
if (value & 0xFFFFF800)
BX_ERROR(("Write to reserved field in HcControl"));
org_state = hub.op_regs.HcControl.hcfs;
hub.op_regs.HcControl.rwe = (value & (1<<10)) ? 1 : 0;
hub.op_regs.HcControl.rwc = (value & (1<< 9)) ? 1 : 0;
hub.op_regs.HcControl.ir = (value & (1<< 8)) ? 1 : 0;
hub.op_regs.HcControl.hcfs = (value & (3<< 6)) >> 6;
hub.op_regs.HcControl.ble = (value & (1<< 5)) ? 1 : 0;
hub.op_regs.HcControl.cle = (value & (1<< 4)) ? 1 : 0;
hub.op_regs.HcControl.ie = (value & (1<< 3)) ? 1 : 0;
hub.op_regs.HcControl.ple = (value & (1<< 2)) ? 1 : 0;
hub.op_regs.HcControl.cbsr = (value & (3<< 0)) >> 0;
if (hub.op_regs.HcControl.hcfs == OHCI_USB_OPERATIONAL) {
hub.op_regs.HcFmRemainingToggle = 0;
if (org_state != OHCI_USB_OPERATIONAL)
hub.use_control_head = hub.use_bulk_head = 1;
}
break;
case 0x08: // HcCommandStatus
if (value & 0xFFFCFFF0)
BX_ERROR(("Write to a reserved field in HcCommandStatus"));
if (value & (3<<16))
BX_ERROR(("Write to R/O field: HcCommandStatus.soc"));
if (value & (1<< 3)) hub.op_regs.HcCommandStatus.ocr = 1;
if (value & (1<< 2)) hub.op_regs.HcCommandStatus.blf = 1;
if (value & (1<< 1)) hub.op_regs.HcCommandStatus.clf = 1;
if (value & (1<< 0)) {
hub.op_regs.HcCommandStatus.hcr = 1;
reset_hc();
hub.op_regs.HcControl.hcfs = OHCI_USB_SUSPEND;
for (unsigned i=0; i<USB_OHCI_PORTS; i++)
if (hub.usb_port[i].HcRhPortStatus.ccs && (hub.usb_port[i].device != NULL))
hub.usb_port[i].device->usb_send_msg(USB_MSG_RESET);
}
break;
case 0x0C: // HcInterruptStatus /// all are WC
if (value & 0xBFFFFF80)
BX_DEBUG(("Write to a reserved field in HcInterruptStatus"));
hub.op_regs.HcInterruptStatus &= ~value;
update_irq();
break;
case 0x10: // HcInterruptEnable
if (value & 0x3FFFFF80)
BX_ERROR(("Write to a reserved field in HcInterruptEnable"));
hub.op_regs.HcInterruptEnable |= (value & 0xC000007F);
update_irq();
break;
case 0x14: // HcInterruptDisable
if (value & 0x3FFFFF80)
BX_ERROR(("Write to a reserved field in HcInterruptDisable"));
hub.op_regs.HcInterruptEnable &= ~value;
update_irq();
break;
case 0x18: // HcHCCA
// the HCD can write 0xFFFFFFFF to this register to see what the alignement is
// by reading back the amount and seeing how many lower bits are clear.
if ((value & 0x000000FF) && (value != 0xFFFFFFFF))
BX_ERROR(("Write to lower byte of HcHCCA non zero."));
hub.op_regs.HcHCCA = (value & 0xFFFFFF00);
break;
case 0x1C: // HcPeriodCurrentED
BX_ERROR(("Write to HcPeriodCurrentED not allowed."));
break;
case 0x20: // HcControlHeadED
if (value & 0x0000000F)
BX_ERROR(("Write to lower nibble of HcControlHeadED non zero."));
hub.op_regs.HcControlHeadED = (value & 0xFFFFFFF0);
break;
case 0x24: // HcControlCurrentED
if (value & 0x0000000F)
BX_ERROR(("Write to lower nibble of HcControlCurrentED non zero."));
hub.op_regs.HcControlCurrentED = (value & 0xFFFFFFF0);
break;
case 0x28: // HcBulkHeadED
if (value & 0x0000000F)
BX_ERROR(("Write to lower nibble of HcBulkHeadED non zero."));
hub.op_regs.HcBulkHeadED = (value & 0xFFFFFFF0);
break;
case 0x2C: // HcBulkCurrentED
if (value & 0x0000000F)
BX_ERROR(("Write to lower nibble of HcBulkCurrentED non zero."));
hub.op_regs.HcBulkCurrentED = (value & 0xFFFFFFF0);
break;
case 0x30: // HcDoneHead
BX_ERROR(("Write to HcDoneHead not allowed."));
break;
case 0x34: // HcFmInterval
if (value & 0x0000C000)
BX_ERROR(("Write to a reserved field in HcFmInterval."));
hub.op_regs.HcFmInterval.fit = (value & (1<<31)) ? 1 : 0;
hub.op_regs.HcFmInterval.fsmps = (value & 0x7FFF0000) >> 16;
hub.op_regs.HcFmInterval.fi = (value & 0x00003FFF) >> 0;
break;
case 0x38: // HcFmRemaining
BX_ERROR(("Write to HcFmRemaining not allowed."));
break;
case 0x3C: // HcFmNumber
BX_ERROR(("Write to HcFmNumber not allowed."));
break;
case 0x40: // HcPeriodicStart
if (value & 0xFFFFC000)
BX_ERROR(("Write to a reserved field in HcPeriodicStart."));
hub.op_regs.HcPeriodicStart = (value & 0x00003FFF);
break;
case 0x44: // HcLSThreshold
hub.op_regs.HcLSThreshold = (value & 0x00000FFF);
break;
case 0x48: // HcRhDescriptorA
if (value & 0x00FFE000)
BX_ERROR(("Write to a reserved field in HcRhDescriptorA."));
if ((value & 0x000000FF) != hub.op_regs.HcRhDescriptorA.ndp)
BX_ERROR(("Write to HcRhDescriptorA.ndp not allowed."));
if (value & (1<<10))
BX_ERROR(("Write to HcRhDescriptorA.dt not allowed."));
hub.op_regs.HcRhDescriptorA.potpgt = (value & 0xFF000000) >> 24;
hub.op_regs.HcRhDescriptorA.nocp = (value & (1<<12)) ? 1 : 0;
hub.op_regs.HcRhDescriptorA.ocpm = (value & (1<<11)) ? 1 : 0;
hub.op_regs.HcRhDescriptorA.nps = (value & (1<< 9)) ? 1 : 0;
hub.op_regs.HcRhDescriptorA.psm = (value & (1<< 8)) ? 1 : 0;
if (hub.op_regs.HcRhDescriptorA.psm == 0) {
BX_INFO(("Ben: hub.op_regs.HcRhDescriptorA.psm == 0"));
// all ports have power, etc.
// hub.usb_port[p].HcRhPortStatus.pps = 1
// Call a routine to set each ports dword (LS, Connected, etc.)
} else {
BX_INFO(("Ben: hub.op_regs.HcRhDescriptorA.psm == 1"));
// only ports with bit set in rhstatus have power, etc.
// Call a routine to set each ports dword (LS, Connected, etc.)
}
break;
case 0x4C: // HcRhDescriptorB
hub.op_regs.HcRhDescriptorB.ppcm = (value & 0xFFFF0000) >> 16;
hub.op_regs.HcRhDescriptorB.dr = (value & 0x0000FFFF) >> 0;
break;
case 0x50: { // HcRhStatus
if (value & 0x7FFC7FFC)
BX_ERROR(("Write to a reserved field in HcRhStatus."));
if (value & (1<<1))
BX_ERROR(("Write to HcRhStatus.oci not allowed."));
// which one of these two takes presidence?
if (value & (1<<31)) hub.op_regs.HcRhStatus.drwe = 0;
if (value & (1<<15)) hub.op_regs.HcRhStatus.drwe = 1;
if (value & (1<<17)) hub.op_regs.HcRhStatus.ocic = 1;
if (value & (1<<16)) {
if (hub.op_regs.HcRhDescriptorA.psm == 0) {
for (p=0; p<USB_OHCI_PORTS; p++)
hub.usb_port[p].HcRhPortStatus.pps = 1;
} else {
for (p=0; p<USB_OHCI_PORTS; p++)
if ((hub.op_regs.HcRhDescriptorB.ppcm & (1<<p)) == 0)
hub.usb_port[p].HcRhPortStatus.pps = 1;
}
}
if (value & (1<<0)) {
if (hub.op_regs.HcRhDescriptorA.psm == 0) {
for (p=0; p<USB_OHCI_PORTS; p++)
hub.usb_port[p].HcRhPortStatus.pps = 0;
} else {
for (p=0; p<USB_OHCI_PORTS; p++)
if (!(hub.op_regs.HcRhDescriptorB.ppcm & (1<<p)))
hub.usb_port[p].HcRhPortStatus.pps = 0;
}
}
break;
}
case 0x60: // HcRhPortStatus[3]
#if (USB_OHCI_PORTS < 4)
break;
#endif
case 0x5C: // HcRhPortStatus[2]
#if (USB_OHCI_PORTS < 3)
break;
#endif
case 0x58: // HcRhPortStatus[1]
#if (USB_OHCI_PORTS < 2)
break;
#endif
case 0x54: { // HcRhPortStatus[0]
p = (offset - 0x54) >> 2;
if (value & 0xFFE0FCE0)
BX_ERROR(("Write to a reserved field in usb_port[%d].HcRhPortStatus", p));
if (value & (1<<0))
hub.usb_port[p].HcRhPortStatus.pes = 0;
if (value & (1<<1)) {
if (hub.usb_port[p].HcRhPortStatus.ccs == 0)
hub.usb_port[p].HcRhPortStatus.csc = 1;
else {
hub.usb_port[p].HcRhPortStatus.pes = 1;
}
}
if (value & (1<<2)) {
if (hub.usb_port[p].HcRhPortStatus.ccs == 0)
hub.usb_port[p].HcRhPortStatus.csc = 1;
else
hub.usb_port[p].HcRhPortStatus.pss = 1;
}
// if (value & (1<<3))
// if (hub.usb_port[p].HcRhPortStatus.pss)
// ; // do a resume (or test this in the timer code and do the resume there)
if (value & (1<<4)) {
if (hub.usb_port[p].HcRhPortStatus.ccs == 0)
hub.usb_port[p].HcRhPortStatus.csc = 1;
else {
reset_port(p);
hub.usb_port[p].HcRhPortStatus.pps = 1;
hub.usb_port[p].HcRhPortStatus.pes = 1;
hub.usb_port[p].HcRhPortStatus.prsc = 1;
// are we are currently connected/disconnected
if (hub.usb_port[p].device != NULL) {
hub.usb_port[p].HcRhPortStatus.lsda =
(hub.usb_port[p].device->get_speed() == USB_SPEED_LOW);
set_connect_status(p, 1);
hub.usb_port[p].device->usb_send_msg(USB_MSG_RESET);
}
set_interrupt(OHCI_INTR_RHSC);
}
}
if (value & (1<<8))
hub.usb_port[p].HcRhPortStatus.pps = 1;
if (value & (1<<9))
hub.usb_port[p].HcRhPortStatus.pps = 0;
if (value & (1<<16))
hub.usb_port[p].HcRhPortStatus.csc = (value & ((1<<4) | (1<<1) | (1<<2))) ? 1 : 0;
if (value & (1<<17))
hub.usb_port[p].HcRhPortStatus.pesc = 0;
if (value & (1<<18))
hub.usb_port[p].HcRhPortStatus.pssc = 0;
if (value & (1<<19))
hub.usb_port[p].HcRhPortStatus.ocic = 0;
if (value & (1<<20))
hub.usb_port[p].HcRhPortStatus.prsc = 0;
break;
}
default:
BX_ERROR(("unsupported write to address=0x%08X, val = 0x%08X!", (Bit32u)addr, value));
break;
}
return 1;
}
Bit32u bx_ohci_core_c::get_frame_remaining(void)
{
Bit16u bit_time, fr;
bit_time = (Bit16u)((bx_pc_system.time_usec() - hub.sof_time) * 12);
if ((hub.op_regs.HcControl.hcfs != OHCI_USB_OPERATIONAL) ||
(bit_time > hub.op_regs.HcFmInterval.fi)) {
fr = 0;
} else {
fr = hub.op_regs.HcFmInterval.fi - bit_time;
}
return (hub.op_regs.HcFmRemainingToggle << 31) | fr;
}
void bx_ohci_core_c::ohci_timer_handler(void *this_ptr)
{
bx_ohci_core_c *class_ptr = (bx_ohci_core_c *) this_ptr;
class_ptr->ohci_timer();
}
// Called once every 1mS
void bx_ohci_core_c::ohci_timer(void)
{
struct OHCI_ED cur_ed;
Bit32u address, ed_address;
Bit16u zero = 0;
if (hub.op_regs.HcControl.hcfs == OHCI_USB_OPERATIONAL) {
// set remaining to the interval amount.
hub.op_regs.HcFmRemainingToggle = hub.op_regs.HcFmInterval.fit;
hub.sof_time = bx_pc_system.time_usec();
// The Frame Number Register is incremented
// every time bit 15 is changed (at 0x8000 or 0x0000), fno is fired.
hub.op_regs.HcFmNumber++;
hub.op_regs.HcFmNumber &= 0xffff;
DEV_MEM_WRITE_PHYSICAL(hub.op_regs.HcHCCA + 0x80, 2, (Bit8u *) &hub.op_regs.HcFmNumber);
DEV_MEM_WRITE_PHYSICAL(hub.op_regs.HcHCCA + 0x82, 2, (Bit8u *) &zero);
if ((hub.op_regs.HcFmNumber == 0x8000) || (hub.op_regs.HcFmNumber == 0x0000)) {
set_interrupt(OHCI_INTR_FNO);
}
//
set_interrupt(OHCI_INTR_SF);
// if interrupt delay (done_count) == 0, and status.wdh == 0, then update the donehead fields.
//BX_DEBUG(("done_count = %d, status.wdh = %d", hub.ohci_done_count,
// ((hub.op_regs.HcInterruptStatus & OHCI_INTR_WD) > 0)));
if ((hub.ohci_done_count == 0) && ((hub.op_regs.HcInterruptStatus & OHCI_INTR_WD) == 0)) {
Bit32u temp = hub.op_regs.HcDoneHead;
if (hub.op_regs.HcInterruptStatus & hub.op_regs.HcInterruptEnable)
temp |= 1;
BX_DEBUG(("Updating the hcca.DoneHead field to 0x%08X and setting the wdh flag", temp));
DEV_MEM_WRITE_PHYSICAL(hub.op_regs.HcHCCA + 0x84, 4, (Bit8u *) &temp);
hub.op_regs.HcDoneHead = 0;
hub.ohci_done_count = 7;
set_interrupt(OHCI_INTR_WD);
}
// if (6 >= done_count > 0) then decrement done_count
if ((hub.ohci_done_count != 7) && (hub.ohci_done_count > 0))
hub.ohci_done_count--;
process_lists();
// do the ED's in the interrupt table
if (hub.op_regs.HcControl.ple) {
address = hub.op_regs.HcHCCA + ((hub.op_regs.HcFmNumber & 0x1F) * 4);
DEV_MEM_READ_PHYSICAL(address, 4, (Bit8u*) &ed_address);
while (ed_address) {
DEV_MEM_READ_PHYSICAL(ed_address, 4, (Bit8u*) &cur_ed.dword0);
DEV_MEM_READ_PHYSICAL(ed_address + 4, 4, (Bit8u*) &cur_ed.dword1);
DEV_MEM_READ_PHYSICAL(ed_address + 8, 4, (Bit8u*) &cur_ed.dword2);
DEV_MEM_READ_PHYSICAL(ed_address + 12, 4, (Bit8u*) &cur_ed.dword3);
process_ed(&cur_ed, ed_address);
ed_address = ED_GET_NEXTED(&cur_ed);
}
}
} // end run schedule
}
void bx_ohci_core_c::process_lists(void)
{
struct OHCI_ED cur_ed;
// TODO: Rather than just comparing .fr to < 8000 here, and < 4000 below, see the statement on
// page 45 of the OHCI specs:
// "When a new frame starts, the Host Controller processes control and bulk Endpoint Descriptors until
// the Remaining field of the HcFmRemaining register is less than or equal to the Start field of the
// HcPeriodicStart register"
// if the control list is enabled *and* the control list filled bit is set, do a control list ED
if (hub.op_regs.HcControl.cle) {
if (hub.use_control_head) {
hub.op_regs.HcControlCurrentED = 0;
hub.use_control_head = 0;
}
if (!hub.op_regs.HcControlCurrentED && hub.op_regs.HcCommandStatus.clf) {
hub.op_regs.HcControlCurrentED = hub.op_regs.HcControlHeadED;
hub.op_regs.HcCommandStatus.clf = 0;
}
while (hub.op_regs.HcControlCurrentED) {
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcControlCurrentED, 4, (Bit8u*) &cur_ed.dword0);
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcControlCurrentED + 4, 4, (Bit8u*) &cur_ed.dword1);
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcControlCurrentED + 8, 4, (Bit8u*) &cur_ed.dword2);
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcControlCurrentED + 12, 4, (Bit8u*) &cur_ed.dword3);
process_ed(&cur_ed, hub.op_regs.HcControlCurrentED);
hub.op_regs.HcControlCurrentED = ED_GET_NEXTED(&cur_ed);
if (get_frame_remaining() < 8000)
break;
}
}
// if the bulk list is enabled *and* the bulk list filled bit is set, do a bulk list ED
if (hub.op_regs.HcControl.ble) {
if (hub.use_bulk_head) {
hub.op_regs.HcBulkCurrentED = 0;
hub.use_bulk_head = 0;
}
if (!hub.op_regs.HcBulkCurrentED && hub.op_regs.HcCommandStatus.blf) {
hub.op_regs.HcBulkCurrentED = hub.op_regs.HcBulkHeadED;
hub.op_regs.HcCommandStatus.blf = 0;
}
while (hub.op_regs.HcBulkCurrentED) {
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcBulkCurrentED, 4, (Bit8u*) &cur_ed.dword0);
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcBulkCurrentED + 4, 4, (Bit8u*) &cur_ed.dword1);
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcBulkCurrentED + 8, 4, (Bit8u*) &cur_ed.dword2);
DEV_MEM_READ_PHYSICAL(hub.op_regs.HcBulkCurrentED + 12, 4, (Bit8u*) &cur_ed.dword3);
if (process_ed(&cur_ed, hub.op_regs.HcBulkCurrentED)) {
hub.op_regs.HcCommandStatus.blf = 1;
}
hub.op_regs.HcBulkCurrentED = ED_GET_NEXTED(&cur_ed);
if (get_frame_remaining() < 4000)
break;
}
}
}
bool bx_ohci_core_c::process_ed(struct OHCI_ED *ed, const Bit32u ed_address)
{
struct OHCI_TD cur_td;
int toggle;
bool ret = 0;
if (!ED_GET_H(ed) && !ED_GET_K(ed) && (ED_GET_HEADP(ed) != ED_GET_TAILP(ed))) {
// if the isochronous is enabled and ed is a isochronous, do TD
if (ED_GET_F(ed)) {
if (hub.op_regs.HcControl.ie) {
// load and do a isochronous TD list
BX_DEBUG(("Found a valid ED that points to an isochronous TD"));
// we currently ignore ISO TD's
}
} else {
BX_DEBUG(("Found a valid ED that points to an control/bulk/int TD"));
ret = 1;
while (ED_GET_HEADP(ed) != ED_GET_TAILP(ed)) {
toggle = ED_GET_C(ed);
DEV_MEM_READ_PHYSICAL(ED_GET_HEADP(ed), 4, (Bit8u*) &cur_td.dword0);
DEV_MEM_READ_PHYSICAL(ED_GET_HEADP(ed) + 4, 4, (Bit8u*) &cur_td.dword1);
DEV_MEM_READ_PHYSICAL(ED_GET_HEADP(ed) + 8, 4, (Bit8u*) &cur_td.dword2);
DEV_MEM_READ_PHYSICAL(ED_GET_HEADP(ed) + 12, 4, (Bit8u*) &cur_td.dword3);
BX_DEBUG(("Head: 0x%08X Tail: 0x%08X Next: 0x%08X", ED_GET_HEADP(ed), ED_GET_TAILP(ed), TD_GET_NEXTTD(&cur_td)));
if (TD_GET_T(&cur_td) & 2)
toggle = TD_GET_T(&cur_td) & 1;
int td_ret = process_td(&cur_td, ed, toggle);
if (td_ret == 0) {
// USB_RET_ASYNC or already processed TD, so done with ED (for now)
break;
} else if (td_ret > 0) {
// Processed TD with no error
const Bit32u temp = ED_GET_HEADP(ed);
if (TD_GET_CC(&cur_td) < NotAccessed) {
ED_SET_HEADP(ed, TD_GET_NEXTTD(&cur_td));
TD_SET_NEXTTD(&cur_td, hub.op_regs.HcDoneHead);
hub.op_regs.HcDoneHead = temp;
if (TD_GET_DI(&cur_td) < hub.ohci_done_count)
hub.ohci_done_count = TD_GET_DI(&cur_td);
}
ED_SET_C(ed, toggle ^ 1);
DEV_MEM_WRITE_PHYSICAL(temp, 4, (Bit8u*) &cur_td.dword0);
DEV_MEM_WRITE_PHYSICAL(temp + 4, 4, (Bit8u*) &cur_td.dword1);
DEV_MEM_WRITE_PHYSICAL(temp + 8, 4, (Bit8u*) &cur_td.dword2);
} else {
// Processed TD with error, advance the toggle anyway
ED_SET_C(ed, toggle ^ 1);
break;
}
}
}
DEV_MEM_WRITE_PHYSICAL(ed_address + 8, 4, (Bit8u*) &ed->dword2);
}
return ret;
}
int ohci_event_handler(int event, void *ptr, void *dev, int port)
{
if (dev != NULL) {
return ((bx_ohci_core_c *) dev)->event_handler(event, ptr, port);
}
return -1;
}
int bx_ohci_core_c::event_handler(int event, void *ptr, int port)
{
Bit32u intr = 0;
int ret = 0;
USBAsync *p;
switch (event) {
// packet events start here
case USB_EVENT_ASYNC:
BX_DEBUG(("Async packet completion"));
p = container_of_usb_packet(ptr);
p->done = 1;
process_lists();
break;
case USB_EVENT_WAKEUP:
if (hub.usb_port[port].HcRhPortStatus.pss) {
hub.usb_port[port].HcRhPortStatus.pss = 0;
hub.usb_port[port].HcRhPortStatus.pssc = 1;
intr = OHCI_INTR_RHSC;
}
if (hub.op_regs.HcControl.hcfs == OHCI_USB_SUSPEND) {
hub.op_regs.HcControl.hcfs = OHCI_USB_RESUME;
intr = OHCI_INTR_RD;
}
set_interrupt(intr);
break;
// host controller events start here
case USB_EVENT_DEFAULT_SPEED:
// return default speed for specified port number
return USB_SPEED_FULL;
case USB_EVENT_CHECK_SPEED:
if (ptr != NULL) {
usb_device_c *usb_device = (usb_device_c *) ptr;
if (usb_device->get_speed() <= USB_SPEED_FULL)
ret = 1;
}
break;
default:
BX_ERROR(("unknown/unsupported event (id=%d) on port #%d", event, port+1));
ret = -1; // unknown event, event not handled
}
return ret;
}
int bx_ohci_core_c::process_td(struct OHCI_TD *td, struct OHCI_ED *ed, int toggle)
{
unsigned pid = 0, len = 0, len1, len2;
int ret2 = 1;
int ilen, ret = 0;
Bit32u addr;
Bit16u maxlen = 0;
USBAsync *p;
bool completion;
addr = ED_GET_HEADP(ed);
p = find_async_packet(&packets, addr);
completion = (p != NULL);
if (completion && !p->done) {
return 0;
}
// The td->cc field should be 111x if it hasn't been processed yet.
if (TD_GET_CC(td) < NotAccessed) {
BX_ERROR(("Found TD with CC value not 111x"));
return 0;
}
if (ED_GET_D(ed) == 1)
pid = USB_TOKEN_OUT;
else if (ED_GET_D(ed) == 2)
pid = USB_TOKEN_IN;
else {
if (TD_GET_DP(td) == 0)
pid = USB_TOKEN_SETUP;
else if (TD_GET_DP(td) == 1)
pid = USB_TOKEN_OUT;
else if (TD_GET_DP(td) == 2)
pid = USB_TOKEN_IN;
}
// calculate the length of the packet
if (TD_GET_CBP(td) && TD_GET_BE(td)) {
if ((TD_GET_CBP(td) & 0xFFFFF000) != (TD_GET_BE(td) & 0xFFFFF000))
len = (TD_GET_BE(td) & 0xFFF) + 0x1001 - (TD_GET_CBP(td) & 0xFFF);
else {
ilen = ((int) TD_GET_BE(td) - TD_GET_CBP(td)) + 1;
if (ilen < 0)
len = 0x1001 + len;
else
len = (unsigned) ilen;
}
} else
len = 0;
if (completion) {
ret = p->packet.len;
} else {
switch (pid) {
case USB_TOKEN_SETUP:
case USB_TOKEN_OUT:
maxlen = (len <= ED_GET_MPS(ed)) ? len : ED_GET_MPS(ed); // limit the data length the the max packet size
break;
case USB_TOKEN_IN:
maxlen = len;
break;
}
p = create_async_packet(&packets, addr, maxlen);
p->packet.pid = pid;
p->packet.devaddr = ED_GET_FA(ed);
p->packet.devep = ED_GET_EN(ed);
p->packet.speed = ED_GET_S(ed) ? USB_SPEED_LOW : USB_SPEED_FULL;
#if HANDLE_TOGGLE_CONTROL
p->packet.toggle = toggle;
#endif
p->packet.complete_cb = ohci_event_handler;
p->packet.complete_dev = this;
BX_DEBUG((" pid = %s addr = %d endpnt = %d len = %d mps = %d s = %d (td->cbp = 0x%08X, td->be = 0x%08X)",
(pid == USB_TOKEN_IN)? "IN" : (pid == USB_TOKEN_OUT) ? "OUT" : (pid == USB_TOKEN_SETUP) ? "SETUP" : "UNKNOWN",
ED_GET_FA(ed), ED_GET_EN(ed), maxlen, ED_GET_MPS(ed), ED_GET_S(ed), TD_GET_CBP(td), TD_GET_BE(td)));
BX_DEBUG((" td->t = %d ed->c = %d td->di = %d td->r = %d", TD_GET_T(td), ED_GET_C(ed), TD_GET_DI(td), TD_GET_R(td)));
switch (pid) {
case USB_TOKEN_SETUP:
if (maxlen > 0)
DEV_MEM_READ_PHYSICAL_DMA(TD_GET_CBP(td), maxlen, p->packet.data);
// 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.
if ((ret = broadcast_packet(&p->packet)) >= 0)
ret = 8;
break;
case USB_TOKEN_OUT:
if (maxlen > 0)
DEV_MEM_READ_PHYSICAL_DMA(TD_GET_CBP(td), maxlen, p->packet.data);
ret = broadcast_packet(&p->packet);
break;
case USB_TOKEN_IN:
ret = broadcast_packet(&p->packet);
break;
default:
TD_SET_CC(td, UnexpectedPID);
TD_SET_EC(td, 3);
return 1;
}
if (ret == USB_RET_ASYNC) {
BX_DEBUG(("Async packet deferred"));
return 0;
}
}
if ((ret > 0) && (pid == USB_TOKEN_IN)) {
if (((TD_GET_CBP(td) & 0xfff) + ret) > 0x1000) {
len1 = 0x1000 - (TD_GET_CBP(td) & 0xfff);
len2 = ret - len1;
DEV_MEM_WRITE_PHYSICAL_DMA(TD_GET_CBP(td), len1, p->packet.data);
DEV_MEM_WRITE_PHYSICAL_DMA((TD_GET_BE(td) & ~0xfff), len2, p->packet.data + len1);
} else {
DEV_MEM_WRITE_PHYSICAL_DMA(TD_GET_CBP(td), ret, p->packet.data);
}
}
if ((ret == (int) len) ||
((pid == USB_TOKEN_IN) && (ret >= 0) && TD_GET_R(td)) ||
((pid == USB_TOKEN_OUT) && (ret >= 0) && (ret <= (int) ED_GET_MPS(ed)))) {
if (ret == (int) len)
TD_SET_CBP(td, 0);
else {
if (((TD_GET_CBP(td) & 0xfff) + ret) >= 0x1000) {
TD_SET_CBP(td, (TD_GET_CBP(td) + ret) & 0x0FFF);
TD_SET_CBP(td, TD_GET_CBP(td) | (TD_GET_BE(td) & ~0x0FFF));
} else {
TD_SET_CBP(td, TD_GET_CBP(td) + ret);
}
}
if ((pid != USB_TOKEN_OUT) || (ret == (int) len)) {
TD_SET_CC(td, NoError);
TD_SET_EC(td, 0);
}
} else {
if (ret >= 0)
TD_SET_CC(td, DataUnderrun);
else {
switch (ret) {
case USB_RET_NODEV: // (-1)
TD_SET_CC(td, DeviceNotResponding);
break;
case USB_RET_NAK: // (-2)
break;
case USB_RET_STALL: // (-3)
TD_SET_CC(td, Stall);
break;
case USB_RET_BABBLE: // (-4)
TD_SET_CC(td, BufferOverrun);
break;
default:
BX_ERROR(("Unknown error returned: %d", ret));
break;
}
ret2 = ret;
}
if (ret != USB_RET_NAK) {
TD_SET_EC(td, 3);
ED_SET_H(ed, 1);
}
}
BX_DEBUG((" td->cbp = 0x%08X ret = %d len = %d td->cc = %d td->ec = %d ed->h = %d", TD_GET_CBP(td), ret, maxlen, TD_GET_CC(td), TD_GET_EC(td), ED_GET_H(ed)));
BX_DEBUG((" td->t = %d ed->c = %d", TD_GET_T(td), ED_GET_C(ed)));
remove_async_packet(&packets, p);
return ret2;
}
int bx_ohci_core_c::broadcast_packet(USBPacket *p)
{
int i, ret;
ret = USB_RET_NODEV;
for (i = 0; i < USB_OHCI_PORTS && ret == USB_RET_NODEV; i++) {
if ((hub.usb_port[i].device != NULL) &&
(hub.usb_port[i].HcRhPortStatus.ccs)) {
ret = hub.usb_port[i].device->handle_packet(p);
}
}
return ret;
}
// pci configuration space write callback handler
void bx_ohci_core_c::pci_write_handler(Bit8u address, Bit32u value, unsigned io_len)
{
Bit8u value8;
if (((address >= 0x14) && (address <= 0x34)))
return;
BX_DEBUG_PCI_WRITE(address, value, io_len);
for (unsigned i=0; i<io_len; i++) {
value8 = (value >> (i*8)) & 0xFF;
// Bit8u oldval = 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)
pci_conf[address+i] = value8;
break;
case 0x3d: //
case 0x3e: //
case 0x3f: //
case 0x05: // disallowing write to command hi-byte
case 0x06: // disallowing write to status lo-byte (is that expected?)
break;
default:
pci_conf[address+i] = value8;
}
}
}
bool bx_ohci_core_c::set_connect_status(Bit8u port, bool connected)
{
const bool ccs_org = hub.usb_port[port].HcRhPortStatus.ccs;
const bool pes_org = hub.usb_port[port].HcRhPortStatus.pes;
usb_device_c *device = hub.usb_port[port].device;
if (device != NULL) {
if (connected) {
switch (device->get_speed()) {
case USB_SPEED_LOW:
hub.usb_port[port].HcRhPortStatus.lsda = 1;
break;
case USB_SPEED_FULL:
hub.usb_port[port].HcRhPortStatus.lsda = 0;
break;
case USB_SPEED_HIGH:
case USB_SPEED_SUPER:
BX_ERROR(("HC ignores device with unsupported speed"));
return 0;
default:
BX_PANIC(("USB device returned invalid speed value"));
return 0;
}
hub.usb_port[port].HcRhPortStatus.ccs = 1;
if (!device->get_connected()) {
if (!device->init()) {
BX_ERROR(("port #%d: connect failed", port+1));
return 0;
} else {
BX_INFO(("port #%d: connect: %s", port+1, device->get_info()));
}
}
} else { // not connected
BX_INFO(("port #%d: device disconnect", port+1));
hub.usb_port[port].HcRhPortStatus.ccs = 0;
hub.usb_port[port].HcRhPortStatus.pes = 0;
hub.usb_port[port].HcRhPortStatus.lsda = 0;
}
hub.usb_port[port].HcRhPortStatus.csc |= (ccs_org != hub.usb_port[port].HcRhPortStatus.ccs);
hub.usb_port[port].HcRhPortStatus.pesc |= (pes_org != hub.usb_port[port].HcRhPortStatus.pes);
// we changed the value of the port, so show it
set_interrupt(OHCI_INTR_RHSC);
}
return connected;
}
void bx_ohci_core_c::set_port_device(int port, usb_device_c *dev)
{
usb_device_c *olddev = hub.usb_port[port].device;
if ((dev != NULL) && (olddev == NULL)) {
// make sure we are calling the correct handler for the device
dev->set_event_handler(this, ohci_event_handler, port);
hub.usb_port[port].device = dev;
set_connect_status(port, 1);
} else if ((dev == NULL) && (olddev != NULL)) {
set_connect_status(port, 0);
hub.usb_port[port].device = dev;
}
}
#endif // BX_SUPPORT_PCI && BX_SUPPORT_USB_OHCI