From 716e20fbc4c2975fc96dece4fbbecec583badf08 Mon Sep 17 00:00:00 2001 From: ergo720 Date: Wed, 30 May 2018 21:53:04 +0200 Subject: [PATCH] HCCA stuff (WIP) --- src/devices/USBController/OHCI.cpp | 117 +++++++++++++++++++++++- src/devices/USBController/OHCI.h | 50 +++++++--- src/devices/USBController/USBDevice.cpp | 4 +- 3 files changed, 156 insertions(+), 15 deletions(-) diff --git a/src/devices/USBController/OHCI.cpp b/src/devices/USBController/OHCI.cpp index 3d933ba4a..78cfeae3f 100644 --- a/src/devices/USBController/OHCI.cpp +++ b/src/devices/USBController/OHCI.cpp @@ -72,6 +72,120 @@ void OHCI::OHCI_FrameBoundaryWrapper(void* pVoid) static_cast(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(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(Paddr + OffsetoOfFrameNumber + CONTIGUOUS_MEMORY_BASE), + reinterpret_cast(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"); diff --git a/src/devices/USBController/OHCI.h b/src/devices/USBController/OHCI.h index 25d45c0be..a9a39688e 100644 --- a/src/devices/USBController/OHCI.h +++ b/src/devices/USBController/OHCI.h @@ -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); diff --git a/src/devices/USBController/USBDevice.cpp b/src/devices/USBController/USBDevice.cpp index 4e8f2a2c9..6990e7fbe 100644 --- a/src/devices/USBController/USBDevice.cpp +++ b/src/devices/USBController/USBDevice.cpp @@ -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;