HCCA stuff (WIP)

This commit is contained in:
ergo720 2018-05-30 21:53:04 +02:00
parent e79c7fa185
commit 716e20fbc4
3 changed files with 156 additions and 15 deletions

View File

@ -72,6 +72,120 @@ void OHCI::OHCI_FrameBoundaryWrapper(void* pVoid)
static_cast<OHCI*>(pVoid)->OHCI_FrameBoundaryWorker();
}
void OHCI::OHCI_FrameBoundaryWorker()
{
OHCI_HCCA hcca;
if (OHCI_ReadHCCA(m_Registers.HcHCCA, &hcca)) {
EmuWarning("Ohci: HCCA read error at physical address 0x%X", m_Registers.HcHCCA);
OHCI_FatalError();
return;
}
// Process all the lists at the end of the frame
if (m_Registers.HcControl & OHCI_CTL_PLE) {
int n = m_Registers.HcFmNumber & 0x1f;
ohci_service_ed_list(ohci, le32_to_cpu(hcca.intr[n]), 0);
}
// Cancel all pending packets if either of the lists has been disabled
if (ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) {
if (ohci->async_td) {
usb_cancel_packet(&ohci->usb_packet);
ohci->async_td = 0;
}
OHCI_StopEndpoints();
}
ohci->old_ctl = ohci->ctl;
ohci_process_lists(ohci, 0);
// Stop if UnrecoverableError happened or OHCI_SOF will crash
if (m_Registers.HcInterruptStatus & OHCI_INTR_UE) {
return;
}
// From the standard: "This bit is loaded from the FrameIntervalToggle field of
// HcFmInterval whenever FrameRemaining reaches 0."
m_Registers.HcFmRemaining = (m_Registers.HcFmRemaining & ~OHCI_FMR_FRT) | (m_Registers.HcFmInterval & OHCI_FMI_FIT);
// Increment frame number
m_Registers.HcFmNumber = (m_Registers.HcFmNumber + 1) & 0xFFFF; // prevent overflow
hcca.HccaFrameNumber = m_Registers.HcFmNumber; // dropped big -> little endian conversion from XQEMU
if (m_DoneCount == 0 && !(m_Registers.HcInterruptStatus & OHCI_INTR_WD)) {
if (!m_Registers.HcDoneHead) {
// From the standard: "This is set to zero whenever HC writes the content of this
// register to HCCA. It also sets the WritebackDoneHead of HcInterruptStatus."
CxbxKrnlCleanup("Ohci: HcDoneHead is zero but WritebackDoneHead interrupt is not set!\n");
}
if (m_Registers.HcInterrupt & m_Registers.HcInterruptStatus) {
// From the standard: "The least significant bit of this entry is set to 1 to indicate whether an
// unmasked HcInterruptStatus was set when HccaDoneHead was written." It's tecnically incorrect to
// do this to HcDoneHead instead of HccaDoneHead however it doesn't matter since HcDoneHead is
// zeroed below
m_Registers.HcDoneHead |= 1;
}
hcca.HccaDoneHead = m_Registers.HcDoneHead; // dropped big -> little endian conversion from XQEMU
m_Registers.HcDoneHead = 0;
m_DoneCount = 7;
OHCI_SetInterrupt(OHCI_INTR_WD);
}
if (m_DoneCount != 7 && m_DoneCount != 0) {
// decrease DelayInterrupt counter
m_DoneCount--;
}
// Do SOF stuff here
OHCI_SOF();
// Writeback HCCA
if (OHCI_WriteHCCA(m_Registers.HcHCCA, &hcca)) {
EmuWarning("Ohci: HCCA write error at physical address 0x%X", m_Registers.HcHCCA);
OHCI_FatalError();
}
}
void OHCI::OHCI_FatalError()
{
// According to the standard, an OHCI will stop operating, and set itself into error state
// (which can be queried by MMIO). Instead of calling directly CxbxKrnlCleanup, we let the
// HCD know the problem so it can try to solve it
OHCI_SetInterrupt(OHCI_INTR_UE);
OHCI_BusStop();
}
bool OHCI::OHCI_ReadHCCA(uint32_t Paddr, OHCI_HCCA* Hcca)
{
// ergo720: I disassembled various xbe's of my games and discovered that the shared memory between
// HCD and HC is allocated with MmAllocateContiguousMemory which means we can access it from
// the contiguous region. Hopefully XDK revisions didn't alter this...
if (Paddr != xbnull) {
std::memcpy(Hcca, reinterpret_cast<void*>(Paddr + CONTIGUOUS_MEMORY_BASE), sizeof(OHCI_HCCA));
return false;
}
return true; // error
}
bool OHCI::OHCI_WriteHCCA(uint32_t Paddr, OHCI_HCCA* Hcca)
{
if (Paddr != xbnull) {
// We need to calculate the offset of the HccaFrameNumber member to avoid overwriting HccaInterrruptTable
size_t OffsetoOfFrameNumber = offsetof(OHCI_HCCA, HccaFrameNumber);
std::memcpy(reinterpret_cast<void*>(Paddr + OffsetoOfFrameNumber + CONTIGUOUS_MEMORY_BASE),
reinterpret_cast<uint8_t*>(Hcca) + OffsetoOfFrameNumber, 8);
return false;
}
return true; // error
}
void OHCI::OHCI_StateReset()
{
// The usb state can be USB_Suspend if it is a software reset, and USB_Reset if it is a hardware
@ -114,6 +228,7 @@ void OHCI::OHCI_StateReset()
USB_PortReset(&Port->UsbPort);
}
}
m_DoneCount = 7;
OHCI_StopEndpoints();
@ -122,7 +237,7 @@ void OHCI::OHCI_StateReset()
void OHCI::OHCI_BusStart()
{
// Create the end-of-frame timer. Let's try a factor of 50 (1 virtual ms -> 50 real ms)
// Create the EOF timer. Let's try a factor of 50 (1 virtual ms -> 50 real ms)
m_pEOFtimer = Timer_Create(OHCI_FrameBoundaryWrapper, this, 50);
DbgPrintf("Ohci: Operational mode event\n");

View File

@ -46,9 +46,12 @@
// OHCI: Open Host Controller Interface; the standard used on the xbox to comunicate with the usb devices
// HC: Host Controller; the hardware which interfaces with the usb device and the usb driver
// HCD: Host Controller Driver; software which talks to the HC, it's linked in the xbe
// SOF: start of frame; the beginning of a USB-defined frame
// EOF: end of frame; the end of a USB-defined frame
// TD: transfer descriptor; a memory structure used by the HC to transfer a block of data to/from a device endpoint
// These macros are used to access the bits in the various registers
// These macros are used to access the bits of the various registers
// HcControl
#define OHCI_CTL_CBSR ((1<<0)|(1<<1)) // ControlBulkServiceRatio
#define OHCI_CTL_PLE (1<<2) // PeriodicListEnable
@ -66,15 +69,14 @@
#define OHCI_STATUS_OCR (1<<3) // OwnershipChangeRequest
#define OHCI_STATUS_SOC ((1<<6)|(1<<7)) // SchedulingOverrunCount
// HcInterruptStatus
#define OHCI_INTR_SO (1<<0) // Scheduling overrun
#define OHCI_INTR_WD (1<<1) // HcDoneHead writeback
#define OHCI_INTR_SF (1<<2) // Start of frame
#define OHCI_INTR_RD (1<<3) // Resume detect
#define OHCI_INTR_UE (1<<4) // Unrecoverable error
#define OHCI_INTR_FNO (1<<5) // Frame number overflow
#define OHCI_INTR_RHSC (1<<6) // Root hub status change
#define OHCI_INTR_OC (1<<30) // Ownership change
#define OHCI_INTR_MIE (1<<31) // Master Interrupt Enable
#define OHCI_INTR_SO (1<<0) // SchedulingOverrun
#define OHCI_INTR_WD (1<<1) // WritebackDoneHead
#define OHCI_INTR_SF (1<<2) // StartofFrame
#define OHCI_INTR_RD (1<<3) // ResumeDetected
#define OHCI_INTR_UE (1<<4) // UnrecoverableError
#define OHCI_INTR_FNO (1<<5) // FrameNumberOverflow
#define OHCI_INTR_RHSC (1<<6) // RootHubStatusChange
#define OHCI_INTR_OC (1<<30) // OwnershipChange
// HcInterruptEnable, HcInterruptDisable
#define OHCI_INTR_MIE (1<<31) // MasterInterruptEnable
// HcHCCA
@ -84,6 +86,9 @@
// HcFmInterval
#define OHCI_FMI_FI 0x00003FFF // FrameInterval
#define OHCI_FMI_FIT 0x80000000 // FrameIntervalToggle
// HcFmRemaining
#define OHCI_FMR_FR 0x00003FFF // FrameRemaining
#define OHCI_FMR_FRT 0x80000000 // FrameRemainingToggle
// HcRhDescriptorA
#define OHCI_RHA_RW_MASK 0x00000000 // Mask of supported features
#define OHCI_RHA_PSM (1<<8) // PowerSwitchingMode
@ -124,6 +129,15 @@ typedef enum _OHCI_State
}
OHCI_State;
// Host Controller Communications Area
typedef struct _OHCI_HCCA
{
uint32_t HccaInterrruptTable[32];
uint16_t HccaFrameNumber, HccaPad1;
uint32_t HccaDoneHead;
}
OHCI_HCCA;
// Small struct used to hold the HcRhPortStatus register and the usb port status
typedef struct _OHCIPort
{
@ -140,7 +154,8 @@ typedef struct _OHCI_Registers
uint32_t HcControl;
uint32_t HcCommandStatus;
uint32_t HcInterruptStatus;
uint32_t HcInterrupt; // HcInterruptEnable/Disable are the same so we can merge them together
// HcInterruptEnable/Disable are the same so we can merge them together
uint32_t HcInterrupt;
// Memory Pointer partition
uint32_t HcHCCA;
@ -162,7 +177,7 @@ typedef struct _OHCI_Registers
uint32_t HcRhDescriptorA;
uint32_t HcRhDescriptorB;
uint32_t HcRhStatus;
// I have some doubts here. Both XQEMU and OpenXbox set 4 ports per HC, for a total of 8 usb ports.
// ergo720: I have some doubts here. Both XQEMU and OpenXbox set 4 ports per HC, for a total of 8 usb ports.
// Could it be becasue each gamepad can host 2 memory units?
OHCIPort RhPort[2]; // 2 ports per HC, for a total of 4 USB ports
}
@ -196,13 +211,20 @@ class OHCI
uint64_t m_TicksPerUsbTick;
// usb packet
USBPacket m_UsbPacket;
// ergo720: I believe it's the value of HcControl in the last frame
uint32_t old_ctl;
// irq number
int m_IrqNum;
// ergo720: I think it's the DelayInterrupt flag in a TD
// -> num of frames to wait before generating an interrupt for this TD
int m_DoneCount;
// EOF callback wrapper
static void OHCI_FrameBoundaryWrapper(void* pVoid);
// EOF callback function
void OHCI_FrameBoundaryWorker();
// inform the HCD that we got a problem here...
void OHCI_FatalError();
// initialize packet struct
void OHCI_PacketInit(USBPacket* packet);
// change usb state mode
@ -234,6 +256,10 @@ class OHCI
// set a flag in a port status register but only set it if the port is connected,
// if not set ConnectStatusChange flag; if flag is enabled return 1
int OHCI_PortSetIfConnected(int i, uint32_t Value);
// read the HCCA structure in memory
bool OHCI_ReadHCCA(uint32_t Paddr, OHCI_HCCA* Hcca);
// write the HCCA structure in memory
bool OHCI_WriteHCCA(uint32_t Paddr, OHCI_HCCA* Hcca);
// register a port with the HC
void USB_RegisterPort(USBPort* Port, int Index, int SpeedMask);

View File

@ -65,7 +65,7 @@ uint32_t USBDevice::MMIORead(int barIndex, uint32_t addr, unsigned size)
assert(barIndex == 0);
// Figure out the correct OHCI object and read the register
if (addr >= USB0_BASE) {
if (addr < USB1_BASE) {
// USB0 queried
return m_pHostController1->OHCI_ReadRegister(addr);
}
@ -80,7 +80,7 @@ void USBDevice::MMIOWrite(int barIndex, uint32_t addr, uint32_t value, unsigned
assert(barIndex == 0);
// Figure out the correct OHCI object and write the value to the register
if (addr >= USB0_BASE) {
if (addr < USB1_BASE) {
// USB0 queried
m_pHostController1->OHCI_WriteRegister(addr, value);
return;