diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/USB/DevOHCI.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/USB/DevOHCI.cpp')
-rw-r--r-- | src/VBox/Devices/USB/DevOHCI.cpp | 6087 |
1 files changed, 6087 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/DevOHCI.cpp b/src/VBox/Devices/USB/DevOHCI.cpp new file mode 100644 index 00000000..aa4e2357 --- /dev/null +++ b/src/VBox/Devices/USB/DevOHCI.cpp @@ -0,0 +1,6087 @@ +/* $Id: DevOHCI.cpp $ */ +/** @file + * DevOHCI - Open Host Controller Interface for USB. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_dev_ohci OHCI - Open Host Controller Interface Emulation. + * + * This component implements an OHCI USB controller. It is split roughly in + * to two main parts, the first part implements the register level + * specification of USB OHCI and the second part maintains the root hub (which + * is an integrated component of the device). + * + * The OHCI registers are used for the usual stuff like enabling and disabling + * interrupts. Since the USB time is divided in to 1ms frames and various + * interrupts may need to be triggered at frame boundary time, a timer-based + * approach was taken. Whenever the bus is enabled ohci->eof_timer will be set. + * + * The actual USB transfers are stored in main memory (along with endpoint and + * transfer descriptors). The ED's for all the control and bulk endpoints are + * found by consulting the HcControlHeadED and HcBulkHeadED registers + * respectively. Interrupt ED's are different, they are found by looking + * in the HCCA (another communication area in main memory). + * + * At the start of every frame (in function ohci_sof) we traverse all enabled + * ED lists and queue up as many transfers as possible. No attention is paid + * to control/bulk service ratios or bandwidth requirements since our USB + * could conceivably contain a dozen high speed busses so this would + * artificially limit the performance. + * + * Once we have a transfer ready to go (in function ohciR3ServiceTd) we + * allocate an URB on the stack, fill in all the relevant fields and submit + * it using the VUSBIRhSubmitUrb function. The roothub device and the virtual + * USB core code (vusb.c) coordinates everything else from this point onwards. + * + * When the URB has been successfully handed to the lower level driver, our + * prepare callback gets called and we can remove the TD from the ED transfer + * list. This stops us queueing it twice while it completes. + * bird: no, we don't remove it because that confuses the guest! (=> crashes) + * + * Completed URBs are reaped at the end of every frame (in function + * ohci_frame_boundary). Our completion routine makes use of the ED and TD + * fields in the URB to store the physical addresses of the descriptors so + * that they may be modified in the roothub callbacks. Our completion + * routine (ohciR3RhXferCompletion) carries out a number of tasks: + * -# Retires the TD associated with the transfer, setting the + * relevant error code etc. + * -# Updates done-queue interrupt timer and potentially causes + * a writeback of the done-queue. + * -# If the transfer was device-to-host, we copy the data in to + * the host memory. + * + * As for error handling OHCI allows for 3 retries before failing a transfer, + * an error count is stored in each transfer descriptor. A halt flag is also + * stored in the transfer descriptor. That allows for ED's to be disabled + * without stopping the bus and de-queuing them. + * + * When the bus is started and stopped we call VUSBIDevPowerOn/Off() on our + * roothub to indicate it's powering up and powering down. Whenever we power + * down, the USB core makes sure to synchronously complete all outstanding + * requests so that the OHCI is never seen in an inconsistent state by the + * guest OS (Transfers are not meant to be unlinked until they've actually + * completed, but we can't do that unless we work synchronously, so we just + * have to fake it). + * bird: we do work synchronously now, anything causes guest crashes. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_OHCI +#include <VBox/pci.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/mm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/AssertGuest.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/asm-math.h> +#include <iprt/semaphore.h> +#include <iprt/critsect.h> +#include <iprt/param.h> +#ifdef IN_RING3 +# include <iprt/alloca.h> +# include <iprt/mem.h> +# include <iprt/thread.h> +# include <iprt/uuid.h> +#endif +#include <VBox/vusb.h> +#include "VBoxDD.h" + + +#define VBOX_WITH_OHCI_PHYS_READ_CACHE +//#define VBOX_WITH_OHCI_PHYS_READ_STATS + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The current saved state version. */ +#define OHCI_SAVED_STATE_VERSION OHCI_SAVED_STATE_VERSION_NO_EOF_TIMER +/** The current saved state version. + * @since 6.1.0beta3/rc1 */ +#define OHCI_SAVED_STATE_VERSION_NO_EOF_TIMER 6 +/** The current saved with the start-of-frame timer. + * @since 4.3.x */ +#define OHCI_SAVED_STATE_VERSION_EOF_TIMER 5 +/** The saved state with support of up to 8 ports. + * @since 3.1 or so */ +#define OHCI_SAVED_STATE_VERSION_8PORTS 4 + + +/** Maximum supported number of Downstream Ports on the root hub. 15 ports + * is the maximum defined by the OHCI spec. Must match the number of status + * register words to the 'opreg' array. + */ +#define OHCI_NDP_MAX 15 + +/** Default NDP, chosen to be compatible with everything. */ +#define OHCI_NDP_DEFAULT 12 + +/* Macro to query the number of currently configured ports. */ +#define OHCI_NDP_CFG(pohci) ((pohci)->RootHub.desc_a & OHCI_RHA_NDP) +/** Macro to convert a EHCI port index (zero based) to a VUSB roothub port ID (one based). */ +#define OHCI_PORT_2_VUSB_PORT(a_uPort) ((a_uPort) + 1) + +/** Pointer to OHCI device data. */ +typedef struct OHCI *POHCI; +/** Read-only pointer to the OHCI device data. */ +typedef struct OHCI const *PCOHCI; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +/** + * Host controller transfer descriptor data. + */ +typedef struct VUSBURBHCITDINT +{ + /** Type of TD. */ + uint32_t TdType; + /** The address of the */ + RTGCPHYS32 TdAddr; + /** A copy of the TD. */ + uint32_t TdCopy[16]; +} VUSBURBHCITDINT; + +/** + * The host controller data associated with each URB. + */ +typedef struct VUSBURBHCIINT +{ + /** The endpoint descriptor address. */ + RTGCPHYS32 EdAddr; + /** Number of Tds in the array. */ + uint32_t cTds; + /** When this URB was created. + * (Used for isochronous frames and for logging.) */ + uint32_t u32FrameNo; + /** Flag indicating that the TDs have been unlinked. */ + bool fUnlinked; +} VUSBURBHCIINT; +#endif + +/** + * An OHCI root hub port. + */ +typedef struct OHCIHUBPORT +{ + /** The port register. */ + uint32_t fReg; + /** Flag whether there is a device attached to the port. */ + bool fAttached; + bool afPadding[3]; +} OHCIHUBPORT; +/** Pointer to an OHCI hub port. */ +typedef OHCIHUBPORT *POHCIHUBPORT; + +/** + * The OHCI root hub, shared. + */ +typedef struct OHCIROOTHUB +{ + uint32_t status; + uint32_t desc_a; + uint32_t desc_b; +#if HC_ARCH_BITS == 64 + uint32_t Alignment0; /**< Align aPorts on a 8 byte boundary. */ +#endif + OHCIHUBPORT aPorts[OHCI_NDP_MAX]; +} OHCIROOTHUB; +/** Pointer to the OHCI root hub. */ +typedef OHCIROOTHUB *POHCIROOTHUB; + + +/** + * The OHCI root hub, ring-3 data. + * + * @implements PDMIBASE + * @implements VUSBIROOTHUBPORT + * @implements PDMILEDPORTS + */ +typedef struct OHCIROOTHUBR3 +{ + /** Pointer to the base interface of the VUSB RootHub. */ + R3PTRTYPE(PPDMIBASE) pIBase; + /** Pointer to the connector interface of the VUSB RootHub. */ + R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn; + /** The base interface exposed to the roothub driver. */ + PDMIBASE IBase; + /** The roothub port interface exposed to the roothub driver. */ + VUSBIROOTHUBPORT IRhPort; + + /** The LED. */ + PDMLED Led; + /** The LED ports. */ + PDMILEDPORTS ILeds; + /** Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; + + OHCIHUBPORT aPorts[OHCI_NDP_MAX]; + R3PTRTYPE(POHCI) pOhci; +} OHCIROOTHUBR3; +/** Pointer to the OHCI ring-3 root hub data. */ +typedef OHCIROOTHUBR3 *POHCIROOTHUBR3; + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE +typedef struct OHCIPAGECACHE +{ + /** Last read physical page address. */ + RTGCPHYS GCPhysReadCacheAddr; + /** Copy of last read physical page. */ + uint8_t abPhysReadCache[GUEST_PAGE_SIZE]; +} OHCIPAGECACHE; +typedef OHCIPAGECACHE *POHCIPAGECACHE; +#endif + +/** + * OHCI device data, shared. + */ +typedef struct OHCI +{ + /** Start of current frame. */ + uint64_t SofTime; + /** done queue interrupt counter */ + uint32_t dqic : 3; + /** frame number overflow. */ + uint32_t fno : 1; + + /** Align roothub structure on a 8-byte boundary. */ + uint32_t u32Alignment0; + /** Root hub device, shared data. */ + OHCIROOTHUB RootHub; + + /* OHCI registers */ + + /** @name Control partition + * @{ */ + /** HcControl. */ + uint32_t ctl; + /** HcCommandStatus. */ + uint32_t status; + /** HcInterruptStatus. */ + uint32_t intr_status; + /** HcInterruptEnabled. */ + uint32_t intr; + /** @} */ + + /** @name Memory pointer partition + * @{ */ + /** HcHCCA. */ + uint32_t hcca; + /** HcPeriodCurrentEd. */ + uint32_t per_cur; + /** HcControlCurrentED. */ + uint32_t ctrl_cur; + /** HcControlHeadED. */ + uint32_t ctrl_head; + /** HcBlockCurrendED. */ + uint32_t bulk_cur; + /** HcBlockHeadED. */ + uint32_t bulk_head; + /** HcDoneHead. */ + uint32_t done; + /** @} */ + + /** @name Frame counter partition + * @{ */ + /** HcFmInterval.FSMPS - FSLargestDataPacket */ + uint32_t fsmps : 15; + /** HcFmInterval.FIT - FrameItervalToggle */ + uint32_t fit : 1; + /** HcFmInterval.FI - FrameInterval */ + uint32_t fi : 14; + /** HcFmRemaining.FRT - toggle bit. */ + uint32_t frt : 1; + /** HcFmNumber. + * @remark The register size is 16-bit, but for debugging and performance + * reasons we maintain a 32-bit counter. */ + uint32_t HcFmNumber; + /** HcPeriodicStart */ + uint32_t pstart; + /** @} */ + + /** This member and all the following are not part of saved state. */ + uint64_t SavedStateEnd; + + /** The number of virtual time ticks per frame. */ + uint64_t cTicksPerFrame; + /** The number of virtual time ticks per USB bus tick. */ + uint64_t cTicksPerUsbTick; + + /** Detected canceled isochronous URBs. */ + STAMCOUNTER StatCanceledIsocUrbs; + /** Detected canceled general URBs. */ + STAMCOUNTER StatCanceledGenUrbs; + /** Dropped URBs (endpoint halted, or URB canceled). */ + STAMCOUNTER StatDroppedUrbs; + + /** VM timer frequency used for frame timer calculations. */ + uint64_t u64TimerHz; + /** Idle detection flag; must be cleared at start of frame */ + bool fIdle; + /** A flag indicating that the bulk list may have in-flight URBs. */ + bool fBulkNeedsCleaning; + + bool afAlignment3[2]; + uint32_t Alignment4; /**< Align size on a 8 byte boundary. */ + + /** Critical section synchronising interrupt handling. */ + PDMCRITSECT CsIrq; + + /** The MMIO region handle. */ + IOMMMIOHANDLE hMmio; +} OHCI; + + +/** + * OHCI device data, ring-3. + */ +typedef struct OHCIR3 +{ + /** The root hub, ring-3 portion. */ + OHCIROOTHUBR3 RootHub; + /** Pointer to the device instance - R3 ptr. */ + PPDMDEVINSR3 pDevInsR3; + + /** Number of in-flight TDs. */ + unsigned cInFlight; + unsigned Alignment0; /**< Align aInFlight on a 8 byte boundary. */ + /** Array of in-flight TDs. */ + struct ohci_td_in_flight + { + /** Address of the transport descriptor. */ + uint32_t GCPhysTD; + /** Flag indicating an inactive (not-linked) URB. */ + bool fInactive; + /** Pointer to the URB. */ + R3PTRTYPE(PVUSBURB) pUrb; + } aInFlight[257]; + +#if HC_ARCH_BITS == 32 + uint32_t Alignment1; +#endif + + /** Number of in-done-queue TDs. */ + unsigned cInDoneQueue; + /** Array of in-done-queue TDs. */ + struct ohci_td_in_done_queue + { + /** Address of the transport descriptor. */ + uint32_t GCPhysTD; + } aInDoneQueue[64]; + /** When the tail of the done queue was added. + * Used to calculate the age of the done queue. */ + uint32_t u32FmDoneQueueTail; +#if R3_ARCH_BITS == 32 + /** Align pLoad, the stats and the struct size correctly. */ + uint32_t Alignment2; +#endif + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /** Last read physical page for caching ED reads in the framer thread. */ + OHCIPAGECACHE CacheED; + /** Last read physical page for caching TD reads in the framer thread. */ + OHCIPAGECACHE CacheTD; +#endif + + /** Critical section to synchronize the framer and URB completion handler. */ + RTCRITSECT CritSect; + + /** The restored periodic frame rate. */ + uint32_t uRestoredPeriodicFrameRate; +} OHCIR3; +/** Pointer to ring-3 OHCI state. */ +typedef OHCIR3 *POHCIR3; + +/** + * OHCI device data, ring-0. + */ +typedef struct OHCIR0 +{ + uint32_t uUnused; +} OHCIR0; +/** Pointer to ring-0 OHCI state. */ +typedef OHCIR0 *POHCIR0; + + +/** + * OHCI device data, raw-mode. + */ +typedef struct OHCIRC +{ + uint32_t uUnused; +} OHCIRC; +/** Pointer to raw-mode OHCI state. */ +typedef OHCIRC *POHCIRC; + + +/** @typedef OHCICC + * The instance data for the current context. */ +typedef CTX_SUFF(OHCI) OHCICC; +/** @typedef POHCICC + * Pointer to the instance data for the current context. */ +typedef CTX_SUFF(POHCI) POHCICC; + + +/** Standard OHCI bus speed */ +#define OHCI_DEFAULT_TIMER_FREQ 1000 + +/** Host Controller Communications Area + * @{ */ +#define OHCI_HCCA_NUM_INTR 32 +#define OHCI_HCCA_OFS (OHCI_HCCA_NUM_INTR * sizeof(uint32_t)) +typedef struct OCHIHCCA +{ + uint16_t frame; + uint16_t pad; + uint32_t done; +} OCHIHCCA; +AssertCompileSize(OCHIHCCA, 8); +/** @} */ + +/** @name OHCI Endpoint Descriptor + * @{ */ + +#define ED_PTR_MASK (~(uint32_t)0xf) +#define ED_HWINFO_MPS 0x07ff0000 +#define ED_HWINFO_ISO RT_BIT(15) +#define ED_HWINFO_SKIP RT_BIT(14) +#define ED_HWINFO_LOWSPEED RT_BIT(13) +#define ED_HWINFO_IN RT_BIT(12) +#define ED_HWINFO_OUT RT_BIT(11) +#define ED_HWINFO_DIR (RT_BIT(11) | RT_BIT(12)) +#define ED_HWINFO_ENDPOINT 0x780 /* 4 bits */ +#define ED_HWINFO_ENDPOINT_SHIFT 7 +#define ED_HWINFO_FUNCTION 0x7f /* 7 bits */ +#define ED_HEAD_CARRY RT_BIT(1) +#define ED_HEAD_HALTED RT_BIT(0) + +/** + * OHCI Endpoint Descriptor. + */ +typedef struct OHCIED +{ + /** Flags and stuff. */ + uint32_t hwinfo; + /** TailP - TD Queue Tail pointer. Bits 0-3 ignored / preserved. */ + uint32_t TailP; + /** HeadP - TD Queue head pointer. Bit 0 - Halted, Bit 1 - toggleCarry. Bit 2&3 - 0. */ + uint32_t HeadP; + /** NextED - Next Endpoint Descriptor. Bits 0-3 ignored / preserved. */ + uint32_t NextED; +} OHCIED, *POHCIED; +typedef const OHCIED *PCOHCIED; +/** @} */ +AssertCompileSize(OHCIED, 16); + + +/** @name Completion Codes + * @{ */ +#define OHCI_CC_NO_ERROR (UINT32_C(0x00) << 28) +#define OHCI_CC_CRC (UINT32_C(0x01) << 28) +#define OHCI_CC_STALL (UINT32_C(0x04) << 28) +#define OHCI_CC_DEVICE_NOT_RESPONDING (UINT32_C(0x05) << 28) +#define OHCI_CC_DNR OHCI_CC_DEVICE_NOT_RESPONDING +#define OHCI_CC_PID_CHECK_FAILURE (UINT32_C(0x06) << 28) +#define OHCI_CC_UNEXPECTED_PID (UINT32_C(0x07) << 28) +#define OHCI_CC_DATA_OVERRUN (UINT32_C(0x08) << 28) +#define OHCI_CC_DATA_UNDERRUN (UINT32_C(0x09) << 28) +/* 0x0a..0x0b - reserved */ +#define OHCI_CC_BUFFER_OVERRUN (UINT32_C(0x0c) << 28) +#define OHCI_CC_BUFFER_UNDERRUN (UINT32_C(0x0d) << 28) +#define OHCI_CC_NOT_ACCESSED_0 (UINT32_C(0x0e) << 28) +#define OHCI_CC_NOT_ACCESSED_1 (UINT32_C(0x0f) << 28) +/** @} */ + + +/** @name OHCI General transfer descriptor + * @{ */ + +/** Error count (EC) shift. */ +#define TD_ERRORS_SHIFT 26 +/** Error count max. (One greater than what the EC field can hold.) */ +#define TD_ERRORS_MAX 4 + +/** CC - Condition code mask. */ +#define TD_HWINFO_CC (UINT32_C(0xf0000000)) +#define TD_HWINFO_CC_SHIFT 28 +/** EC - Error count. */ +#define TD_HWINFO_ERRORS (RT_BIT(26) | RT_BIT(27)) +/** T - Data toggle. */ +#define TD_HWINFO_TOGGLE (RT_BIT(24) | RT_BIT(25)) +#define TD_HWINFO_TOGGLE_HI (RT_BIT(25)) +#define TD_HWINFO_TOGGLE_LO (RT_BIT(24)) +/** DI - Delay interrupt. */ +#define TD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23)) +#define TD_HWINFO_IN (RT_BIT(20)) +#define TD_HWINFO_OUT (RT_BIT(19)) +/** DP - Direction / PID. */ +#define TD_HWINFO_DIR (RT_BIT(19) | RT_BIT(20)) +/** R - Buffer rounding. */ +#define TD_HWINFO_ROUNDING (RT_BIT(18)) +/** Bits that are reserved / unknown. */ +#define TD_HWINFO_UNKNOWN_MASK (UINT32_C(0x0003ffff)) + +/** SETUP - to endpoint. */ +#define OHCI_TD_DIR_SETUP 0x0 +/** OUT - to endpoint. */ +#define OHCI_TD_DIR_OUT 0x1 +/** IN - from endpoint. */ +#define OHCI_TD_DIR_IN 0x2 +/** Reserved. */ +#define OHCI_TD_DIR_RESERVED 0x3 + +/** + * OHCI general transfer descriptor + */ +typedef struct OHCITD +{ + uint32_t hwinfo; + /** CBP - Current Buffer Pointer. (32-bit physical address) */ + uint32_t cbp; + /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */ + uint32_t NextTD; + /** BE - Buffer End (inclusive). (32-bit physical address) */ + uint32_t be; +} OHCITD, *POHCITD; +typedef const OHCITD *PCOHCITD; +/** @} */ +AssertCompileSize(OHCIED, 16); + + +/** @name OHCI isochronous transfer descriptor. + * @{ */ +/** SF - Start frame number. */ +#define ITD_HWINFO_SF 0xffff +/** DI - Delay interrupt. (TD_HWINFO_DI) */ +#define ITD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23)) +#define ITD_HWINFO_DI_SHIFT 21 +/** FC - Frame count. */ +#define ITD_HWINFO_FC (RT_BIT(24) | RT_BIT(25) | RT_BIT(26)) +#define ITD_HWINFO_FC_SHIFT 24 +/** CC - Condition code mask. (=TD_HWINFO_CC) */ +#define ITD_HWINFO_CC UINT32_C(0xf0000000) +#define ITD_HWINFO_CC_SHIFT 28 +/** The buffer page 0 mask (lower 12 bits are ignored). */ +#define ITD_BP0_MASK UINT32_C(0xfffff000) + +#define ITD_NUM_PSW 8 +/** OFFSET - offset of the package into the buffer page. + * (Only valid when CC set to Not Accessed.) + * + * Note that the top bit of the OFFSET field is overlapping with the + * first bit in the CC field. This is ok because both 0xf and 0xe are + * defined as "Not Accessed". + */ +#define ITD_PSW_OFFSET 0x1fff +/** SIZE field mask for IN bound transfers. + * (Only valid when CC isn't Not Accessed.)*/ +#define ITD_PSW_SIZE 0x07ff +/** CC field mask. + * USed to indicate the format of SIZE (Not Accessed -> OFFSET). */ +#define ITD_PSW_CC 0xf000 +#define ITD_PSW_CC_SHIFT 12 + +/** + * OHCI isochronous transfer descriptor. + */ +typedef struct OHCIITD +{ + uint32_t HwInfo; + /** BP0 - Buffer Page 0. The lower 12 bits are ignored. */ + uint32_t BP0; + /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */ + uint32_t NextTD; + /** BE - Buffer End (inclusive). (32-bit physical address) */ + uint32_t BE; + /** (OffsetN/)PSWN - package status word array (0..7). + * The format varies depending on whether the package has been completed or not. */ + uint16_t aPSW[ITD_NUM_PSW]; +} OHCIITD, *POHCIITD; +typedef const OHCIITD *PCOHCIITD; +/** @} */ +AssertCompileSize(OHCIITD, 32); + +/** + * OHCI register operator. + */ +typedef struct OHCIOPREG +{ + const char *pszName; + VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t u32Value); +} OHCIOPREG; + + +/* OHCI Local stuff */ +#define OHCI_CTL_CBSR ((1<<0)|(1<<1)) /* Control/Bulk Service Ratio. */ +#define OHCI_CTL_PLE (1<<2) /* Periodic List Enable. */ +#define OHCI_CTL_IE (1<<3) /* Isochronous Enable. */ +#define OHCI_CTL_CLE (1<<4) /* Control List Enable. */ +#define OHCI_CTL_BLE (1<<5) /* Bulk List Enable. */ +#define OHCI_CTL_HCFS ((1<<6)|(1<<7)) /* Host Controller Functional State. */ +#define OHCI_USB_RESET 0x00 +#define OHCI_USB_RESUME 0x40 +#define OHCI_USB_OPERATIONAL 0x80 +#define OHCI_USB_SUSPEND 0xc0 +#define OHCI_CTL_IR (1<<8) /* Interrupt Routing (host/SMI). */ +#define OHCI_CTL_RWC (1<<9) /* Remote Wakeup Connected. */ +#define OHCI_CTL_RWE (1<<10) /* Remote Wakeup Enabled. */ + +#define OHCI_STATUS_HCR (1<<0) /* Host Controller Reset. */ +#define OHCI_STATUS_CLF (1<<1) /* Control List Filled. */ +#define OHCI_STATUS_BLF (1<<2) /* Bulk List Filled. */ +#define OHCI_STATUS_OCR (1<<3) /* Ownership Change Request. */ +#define OHCI_STATUS_SOC ((1<<6)|(1<<7)) /* Scheduling Overrun Count. */ + +/** @name Interrupt Status and Enabled/Disabled Flags + * @{ */ +/** SO - Scheduling overrun. */ +#define OHCI_INTR_SCHEDULING_OVERRUN RT_BIT(0) +/** WDH - HcDoneHead writeback. */ +#define OHCI_INTR_WRITE_DONE_HEAD RT_BIT(1) +/** SF - Start of frame. */ +#define OHCI_INTR_START_OF_FRAME RT_BIT(2) +/** RD - Resume detect. */ +#define OHCI_INTR_RESUME_DETECT RT_BIT(3) +/** UE - Unrecoverable error. */ +#define OHCI_INTR_UNRECOVERABLE_ERROR RT_BIT(4) +/** FNO - Frame number overflow. */ +#define OHCI_INTR_FRAMENUMBER_OVERFLOW RT_BIT(5) +/** RHSC- Root hub status change. */ +#define OHCI_INTR_ROOT_HUB_STATUS_CHANGE RT_BIT(6) +/** OC - Ownership change. */ +#define OHCI_INTR_OWNERSHIP_CHANGE RT_BIT(30) +/** MIE - Master interrupt enable. */ +#define OHCI_INTR_MASTER_INTERRUPT_ENABLED RT_BIT(31) +/** @} */ + +#define OHCI_HCCA_SIZE 0x100 +#define OHCI_HCCA_MASK UINT32_C(0xffffff00) + +#define OHCI_FMI_FI UINT32_C(0x00003fff) /* Frame Interval. */ +#define OHCI_FMI_FSMPS UINT32_C(0x7fff0000) /* Full-Speed Max Packet Size. */ +#define OHCI_FMI_FSMPS_SHIFT 16 +#define OHCI_FMI_FIT UINT32_C(0x80000000) /* Frame Interval Toggle. */ +#define OHCI_FMI_FIT_SHIFT 31 + +#define OHCI_FR_FRT RT_BIT_32(31) /* Frame Remaining Toggle */ + +#define OHCI_LS_THRESH 0x628 /* Low-Speed Threshold. */ + +#define OHCI_RHA_NDP (0xff) /* Number of Downstream Ports. */ +#define OHCI_RHA_PSM RT_BIT_32(8) /* Power Switching Mode. */ +#define OHCI_RHA_NPS RT_BIT_32(9) /* No Power Switching. */ +#define OHCI_RHA_DT RT_BIT_32(10) /* Device Type. */ +#define OHCI_RHA_OCPM RT_BIT_32(11) /* Over-Current Protection Mode. */ +#define OHCI_RHA_NOCP RT_BIT_32(12) /* No Over-Current Protection. */ +#define OHCI_RHA_POTPGP UINT32_C(0xff000000) /* Power On To Power Good Time. */ + +#define OHCI_RHS_LPS RT_BIT_32(0) /* Local Power Status. */ +#define OHCI_RHS_OCI RT_BIT_32(1) /* Over-Current Indicator. */ +#define OHCI_RHS_DRWE RT_BIT_32(15) /* Device Remote Wakeup Enable. */ +#define OHCI_RHS_LPSC RT_BIT_32(16) /* Local Power Status Change. */ +#define OHCI_RHS_OCIC RT_BIT_32(17) /* Over-Current Indicator Change. */ +#define OHCI_RHS_CRWE RT_BIT_32(31) /* Clear Remote Wakeup Enable. */ + +/** @name HcRhPortStatus[n] - RH Port Status register (read). + * @{ */ +/** CCS - CurrentConnectionStatus - 0 = no device, 1 = device. */ +#define OHCI_PORT_CCS RT_BIT(0) +/** ClearPortEnable (when writing CCS). */ +#define OHCI_PORT_CLRPE OHCI_PORT_CCS +/** PES - PortEnableStatus. */ +#define OHCI_PORT_PES RT_BIT(1) +/** PSS - PortSuspendStatus */ +#define OHCI_PORT_PSS RT_BIT(2) +/** POCI- PortOverCurrentIndicator. */ +#define OHCI_PORT_POCI RT_BIT(3) +/** ClearSuspendStatus (when writing POCI). */ +#define OHCI_PORT_CLRSS OHCI_PORT_POCI +/** PRS - PortResetStatus */ +#define OHCI_PORT_PRS RT_BIT(4) +/** PPS - PortPowerStatus */ +#define OHCI_PORT_PPS RT_BIT(8) +/** LSDA - LowSpeedDeviceAttached */ +#define OHCI_PORT_LSDA RT_BIT(9) +/** ClearPortPower (when writing LSDA). */ +#define OHCI_PORT_CLRPP OHCI_PORT_LSDA +/** CSC - ConnectStatusChange */ +#define OHCI_PORT_CSC RT_BIT(16) +/** PESC - PortEnableStatusChange */ +#define OHCI_PORT_PESC RT_BIT(17) +/** PSSC - PortSuspendStatusChange */ +#define OHCI_PORT_PSSC RT_BIT(18) +/** OCIC - OverCurrentIndicatorChange */ +#define OHCI_PORT_OCIC RT_BIT(19) +/** PRSC - PortResetStatusChange */ +#define OHCI_PORT_PRSC RT_BIT(20) +/** The mask of RW1C bits. */ +#define OHCI_PORT_CLEAR_CHANGE_MASK (OHCI_PORT_CSC | OHCI_PORT_PESC | OHCI_PORT_PSSC | OHCI_PORT_OCIC | OHCI_PORT_PRSC) +/** @} */ + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS +/* + * Explain + */ +typedef struct OHCIDESCREADSTATS +{ + uint32_t cReads; + uint32_t cPageChange; + uint32_t cMinReadsPerPage; + uint32_t cMaxReadsPerPage; + + uint32_t cReadsLastPage; + uint32_t u32LastPageAddr; +} OHCIDESCREADSTATS; +typedef OHCIDESCREADSTATS *POHCIDESCREADSTATS; + +typedef struct OHCIPHYSREADSTATS +{ + OHCIDESCREADSTATS ed; + OHCIDESCREADSTATS td; + OHCIDESCREADSTATS all; + + uint32_t cCrossReads; + uint32_t cCacheReads; + uint32_t cPageReads; +} OHCIPHYSREADSTATS; +typedef OHCIPHYSREADSTATS *POHCIPHYSREADSTATS; +typedef OHCIPHYSREADSTATS const *PCOHCIPHYSREADSTATS; +#endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if defined(VBOX_WITH_OHCI_PHYS_READ_STATS) && defined(IN_RING3) +static OHCIPHYSREADSTATS g_PhysReadState; +#endif + +#if defined(LOG_ENABLED) && defined(IN_RING3) +static bool g_fLogBulkEPs = false; +static bool g_fLogControlEPs = false; +static bool g_fLogInterruptEPs = false; +#endif +#ifdef IN_RING3 +/** + * SSM descriptor table for the OHCI structure. + */ +static SSMFIELD const g_aOhciFields[] = +{ + SSMFIELD_ENTRY( OHCI, SofTime), + SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4), + SSMFIELD_ENTRY( OHCI, RootHub.status), + SSMFIELD_ENTRY( OHCI, RootHub.desc_a), + SSMFIELD_ENTRY( OHCI, RootHub.desc_b), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[8].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[9].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[10].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[11].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[12].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[13].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[14].fReg), + SSMFIELD_ENTRY( OHCI, ctl), + SSMFIELD_ENTRY( OHCI, status), + SSMFIELD_ENTRY( OHCI, intr_status), + SSMFIELD_ENTRY( OHCI, intr), + SSMFIELD_ENTRY( OHCI, hcca), + SSMFIELD_ENTRY( OHCI, per_cur), + SSMFIELD_ENTRY( OHCI, ctrl_cur), + SSMFIELD_ENTRY( OHCI, ctrl_head), + SSMFIELD_ENTRY( OHCI, bulk_cur), + SSMFIELD_ENTRY( OHCI, bulk_head), + SSMFIELD_ENTRY( OHCI, done), + SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4), + SSMFIELD_ENTRY( OHCI, HcFmNumber), + SSMFIELD_ENTRY( OHCI, pstart), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the older 8-port OHCI structure. + */ +static SSMFIELD const g_aOhciFields8Ports[] = +{ + SSMFIELD_ENTRY( OHCI, SofTime), + SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4), + SSMFIELD_ENTRY( OHCI, RootHub.status), + SSMFIELD_ENTRY( OHCI, RootHub.desc_a), + SSMFIELD_ENTRY( OHCI, RootHub.desc_b), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( OHCI, ctl), + SSMFIELD_ENTRY( OHCI, status), + SSMFIELD_ENTRY( OHCI, intr_status), + SSMFIELD_ENTRY( OHCI, intr), + SSMFIELD_ENTRY( OHCI, hcca), + SSMFIELD_ENTRY( OHCI, per_cur), + SSMFIELD_ENTRY( OHCI, ctrl_cur), + SSMFIELD_ENTRY( OHCI, ctrl_head), + SSMFIELD_ENTRY( OHCI, bulk_cur), + SSMFIELD_ENTRY( OHCI, bulk_head), + SSMFIELD_ENTRY( OHCI, done), + SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4), + SSMFIELD_ENTRY( OHCI, HcFmNumber), + SSMFIELD_ENTRY( OHCI, pstart), + SSMFIELD_ENTRY_TERM() +}; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +#ifdef IN_RING3 +/* Update host controller state to reflect a device attach */ +static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp); +static void ohciR3BusResume(PPDMDEVINS pDevIns, POHCI pOhci, POHCICC pThisCC, bool fHardware); +static void ohciR3BusStop(POHCICC pThisCC); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE +static void ohciR3PhysReadCacheInvalidate(POHCIPAGECACHE pPageCache); +#endif + +static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb); +static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb); + +static int ohciR3InFlightFind(POHCICC pThisCC, uint32_t GCPhysTD); +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) +static int ohciR3InDoneQueueFind(POHCICC pThisCC, uint32_t GCPhysTD); +# endif +#endif /* IN_RING3 */ +RT_C_DECLS_END + + +/** + * Update PCI IRQ levels + */ +static void ohciUpdateInterruptLocked(PPDMDEVINS pDevIns, POHCI ohci, const char *msg) +{ + int level = 0; + + if ( (ohci->intr & OHCI_INTR_MASTER_INTERRUPT_ENABLED) + && (ohci->intr_status & ohci->intr) + && !(ohci->ctl & OHCI_CTL_IR)) + level = 1; + + PDMDevHlpPCISetIrq(pDevIns, 0, level); + if (level) + { + uint32_t val = ohci->intr_status & ohci->intr; + Log2(("ohci: Fired off interrupt %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d - %s\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, msg)); NOREF(val); NOREF(msg); + } +} + +#ifdef IN_RING3 + +/** + * Set an interrupt, use the wrapper ohciSetInterrupt. + */ +DECLINLINE(int) ohciR3SetInterruptInt(PPDMDEVINS pDevIns, POHCI ohci, int rcBusy, uint32_t intr, const char *msg) +{ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ohci->CsIrq, rcBusy); + if (rc != VINF_SUCCESS) + return rc; + + if ( (ohci->intr_status & intr) != intr ) + { + ohci->intr_status |= intr; + ohciUpdateInterruptLocked(pDevIns, ohci, msg); + } + + PDMDevHlpCritSectLeave(pDevIns, &ohci->CsIrq); + return rc; +} + +/** + * Set an interrupt wrapper macro for logging purposes. + */ +# define ohciR3SetInterrupt(a_pDevIns, a_pOhci, a_fIntr) \ + ohciR3SetInterruptInt(a_pDevIns, a_pOhci, VERR_IGNORED, a_fIntr, #a_fIntr) + + +/** + * Sets the HC in the unrecoverable error state and raises the appropriate interrupt. + * + * @returns nothing. + * @param pDevIns The device instance. + * @param pThis The OHCI instance. + * @param iCode Diagnostic code. + */ +DECLINLINE(void) ohciR3RaiseUnrecoverableError(PPDMDEVINS pDevIns, POHCI pThis, int iCode) +{ + LogRelMax(10, ("OHCI#%d: Raising unrecoverable error (%d)\n", pDevIns->iInstance, iCode)); + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_UNRECOVERABLE_ERROR); +} + + +/* Carry out a hardware remote wakeup */ +static void ohciR3RemoteWakeup(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + if ((pThis->ctl & OHCI_CTL_HCFS) != OHCI_USB_SUSPEND) + return; + if (!(pThis->RootHub.status & OHCI_RHS_DRWE)) + return; + ohciR3BusResume(pDevIns, pThis, pThisCC, true /* hardware */); +} + + +/** + * Query interface method for the roothub LUN. + */ +static DECLCALLBACK(void *) ohciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + POHCICC pThisCC = RT_FROM_MEMBER(pInterface, OHCICC, RootHub.IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->RootHub.IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pThisCC->RootHub.IRhPort); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->RootHub.ILeds); + return NULL; +} + +/** + * Gets the pointer to the status LED of a unit. + * + * @returns VBox status code. + * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param iLUN The unit which status LED we desire. + * @param ppLed Where to store the LED pointer. + */ +static DECLCALLBACK(int) ohciR3RhQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + POHCICC pThisCC = RT_FROM_MEMBER(pInterface, OHCICC, RootHub.ILeds); + if (iLUN == 0) + { + *ppLed = &pThisCC->RootHub.Led; + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/** Converts a OHCI.roothub.IRhPort pointer to a OHCICC one. */ +#define VUSBIROOTHUBPORT_2_OHCI(a_pInterface) RT_FROM_MEMBER(a_pInterface, OHCICC, RootHub.IRhPort) + +/** + * Get the number of available ports in the hub. + * + * @returns The number of ports available. + * @param pInterface Pointer to this structure. + * @param pAvailable Bitmap indicating the available ports. Set bit == available port. + */ +static DECLCALLBACK(unsigned) ohciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + unsigned cPorts = 0; + + memset(pAvailable, 0, sizeof(*pAvailable)); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + + for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) + if (!pThis->RootHub.aPorts[iPort].fAttached) + { + cPorts++; + ASMBitSet(pAvailable, iPort + 1); + } + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return cPorts; +} + + +/** + * Gets the supported USB versions. + * + * @returns The mask of supported USB versions. + * @param pInterface Pointer to this structure. + */ +static DECLCALLBACK(uint32_t) ohciR3RhGetUSBVersions(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_11; +} + + +/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */ +static DECLCALLBACK(int) ohciR3RhAttach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3RhAttach: uPort=%u\n", uPort)); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis)); + uPort--; + Assert(!pThis->RootHub.aPorts[uPort].fAttached); + /* Only LS/FS devices should end up here. */ + Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL); + + /* + * Attach it. + */ + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC; + if (enmSpeed == VUSB_SPEED_LOW) + pThis->RootHub.aPorts[uPort].fReg |= OHCI_PORT_LSDA; + pThis->RootHub.aPorts[uPort].fAttached = true; + ohciR3RhPortPower(&pThisCC->RootHub, uPort, 1 /* power on */); + + ohciR3RemoteWakeup(pDevIns, pThis, pThisCC); + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + + +/** + * A device is being detached from a port in the roothub. + * + * @param pInterface Pointer to this structure. + * @param uPort The port number assigned to the device. + */ +static DECLCALLBACK(void) ohciR3RhDetach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3RhDetach: uPort=%u\n", uPort)); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis)); + uPort--; + Assert(pThis->RootHub.aPorts[uPort].fAttached); + + /* + * Detach it. + */ + pThis->RootHub.aPorts[uPort].fAttached = false; + if (pThis->RootHub.aPorts[uPort].fReg & OHCI_PORT_PES) + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC | OHCI_PORT_PESC; + else + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC; + + ohciR3RemoteWakeup(pDevIns, pThis, pThisCC); + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); +} + + +/** + * One of the roothub devices has completed its reset operation. + * + * Currently, we don't think anything is required to be done here + * so it's just a stub for forcing async resetting of the devices + * during a root hub reset. + * + * @param pDev The root hub device. + * @param uPort The port of the device completing the reset. + * @param rc The result of the operation. + * @param pvUser Pointer to the controller. + */ +static DECLCALLBACK(void) ohciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) +{ + LogRel(("OHCI: root hub reset completed with %Rrc\n", rc)); + RT_NOREF(pDev, uPort, rc, pvUser); +} + + +/** + * Reset the root hub. + * + * @returns VBox status code. + * @param pInterface Pointer to this structure. + * @param fResetOnLinux This is used to indicate whether we're at VM reset time and + * can do real resets or if we're at any other time where that + * isn't such a good idea. + * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion! + * @thread EMT + */ +static DECLCALLBACK(int) ohciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + Log(("ohci: root hub reset%s\n", fResetOnLinux ? " (reset on linux)" : "")); + + pThis->RootHub.status = 0; + pThis->RootHub.desc_a = OHCI_RHA_NPS | OHCI_NDP_CFG(pThis); /* Preserve NDP value. */ + pThis->RootHub.desc_b = 0x0; /* Impl. specific */ + + /* + * We're pending to _reattach_ the device without resetting them. + * Except, during VM reset where we use the opportunity to do a proper + * reset before the guest comes along and expect things. + * + * However, it's very very likely that we're not doing the right thing + * here if coming from the guest (USB Reset state). The docs talks about + * root hub resetting, however what exact behaviour in terms of root hub + * status and changed bits, and HC interrupts aren't stated clearly. IF we + * get trouble and see the guest doing "USB Resets" we will have to look + * into this. For the time being we stick with simple. + */ + for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) + { + if (pThis->RootHub.aPorts[iPort].fAttached) + { + pThis->RootHub.aPorts[iPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC | OHCI_PORT_PPS; + if (fResetOnLinux) + { + PVM pVM = PDMDevHlpGetVM(pDevIns); + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort), fResetOnLinux, + ohciR3RhResetDoneOneDev, pThis, pVM); + } + } + else + pThis->RootHub.aPorts[iPort].fReg = 0; + } + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + + +/** + * Does a software or hardware reset of the controller. + * + * This is called in response to setting HcCommandStatus.HCR, hardware reset, + * and device construction. + * + * @param pDevIns The device instance. + * @param pThis The ohci instance data. + * @param pThisCC The ohci instance data, current context. + * @param fNewMode The new mode of operation. This is UsbSuspend if it's a + * software reset, and UsbReset if it's a hardware reset / cold boot. + * @param fResetOnLinux Set if we can do a real reset of the devices attached to the root hub. + * This is really a just a hack for the non-working linux device reset. + * Linux has this feature called 'logical disconnect' if device reset fails + * which prevents us from doing resets when the guest asks for it - the guest + * will get confused when the device seems to be reconnected everytime it tries + * to reset it. But if we're at hardware reset time, we can allow a device to + * be 'reconnected' without upsetting the guest. + * + * @remark This hasn't got anything to do with software setting the mode to UsbReset. + */ +static void ohciR3DoReset(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, uint32_t fNewMode, bool fResetOnLinux) +{ + Log(("ohci: %s reset%s\n", fNewMode == OHCI_USB_RESET ? "hardware" : "software", + fResetOnLinux ? " (reset on linux)" : "")); + + /* Clear list enable bits first, so that any processing currently in progress terminates quickly. */ + pThis->ctl &= ~(OHCI_CTL_BLE | OHCI_CTL_CLE | OHCI_CTL_PLE); + + /* Stop the bus in any case, disabling walking the lists. */ + ohciR3BusStop(pThisCC); + + /* + * Cancel all outstanding URBs. + * + * We can't, and won't, deal with URBs until we're moved out of the + * suspend/reset state. Also, a real HC isn't going to send anything + * any more when a reset has been signaled. + */ + pThisCC->RootHub.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub.pIRhConn); + Assert(pThisCC->cInFlight == 0); + + /* + * Reset the hardware registers. + */ + if (fNewMode == OHCI_USB_RESET) + pThis->ctl = OHCI_CTL_RWC; /* We're the firmware, set RemoteWakeupConnected. */ + else + pThis->ctl &= OHCI_CTL_IR | OHCI_CTL_RWC; /* IR and RWC are preserved on software reset. */ + + /* Clear the HCFS bits first to make setting the new state work. */ + pThis->ctl &= ~OHCI_CTL_HCFS; + pThis->ctl |= fNewMode; + pThis->status = 0; + pThis->intr_status = 0; + pThis->intr = 0; + PDMDevHlpPCISetIrq(pDevIns, 0, 0); + + pThis->hcca = 0; + pThis->per_cur = 0; + pThis->ctrl_head = pThis->ctrl_cur = 0; + pThis->bulk_head = pThis->bulk_cur = 0; + pThis->done = 0; + + pThis->fsmps = 0x2778; /* To-Be-Defined, use the value linux sets...*/ + pThis->fit = 0; + pThis->fi = 11999; /* (12MHz ticks, one frame is 1ms) */ + pThis->frt = 0; + pThis->HcFmNumber = 0; + pThis->pstart = 0; + + pThis->dqic = 0x7; + pThis->fno = 0; + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED); + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +#endif + + /* + * If this is a hardware reset, we will initialize the root hub too. + * Software resets doesn't do this according to the specs. + * (It's not possible to have device connected at the time of the + * device construction, so nothing to worry about there.) + */ + if (fNewMode == OHCI_USB_RESET) + pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, fResetOnLinux); +} + + +/** + * Reads physical memory. + */ +DECLINLINE(void) ohciR3PhysRead(PPDMDEVINS pDevIns, uint32_t Addr, void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysReadUser(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Reads physical memory - metadata. + */ +DECLINLINE(void) ohciR3PhysReadMeta(PPDMDEVINS pDevIns, uint32_t Addr, void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysReadMeta(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Writes physical memory. + */ +DECLINLINE(void) ohciR3PhysWrite(PPDMDEVINS pDevIns, uint32_t Addr, const void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysWriteUser(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Writes physical memory - metadata. + */ +DECLINLINE(void) ohciR3PhysWriteMeta(PPDMDEVINS pDevIns, uint32_t Addr, const void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysWriteMeta(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Read an array of dwords from physical memory and correct endianness. + */ +DECLINLINE(void) ohciR3GetDWords(PPDMDEVINS pDevIns, uint32_t Addr, uint32_t *pau32s, int c32s) +{ + ohciR3PhysReadMeta(pDevIns, Addr, pau32s, c32s * sizeof(uint32_t)); +# ifndef RT_LITTLE_ENDIAN + for(int i = 0; i < c32s; i++) + pau32s[i] = RT_H2LE_U32(pau32s[i]); +# endif +} + +/** + * Write an array of dwords from physical memory and correct endianness. + */ +DECLINLINE(void) ohciR3PutDWords(PPDMDEVINS pDevIns, uint32_t Addr, const uint32_t *pau32s, int cu32s) +{ +# ifdef RT_LITTLE_ENDIAN + ohciR3PhysWriteMeta(pDevIns, Addr, pau32s, cu32s << 2); +# else + for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s)) + { + uint32_t u32Tmp = RT_H2LE_U32(*pau32s); + ohciR3PhysWriteMeta(pDevIns, Addr, (uint8_t *)&u32Tmp, sizeof(u32Tmp)); + } +# endif +} + + + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + +static void descReadStatsReset(POHCIDESCREADSTATS p) +{ + p->cReads = 0; + p->cPageChange = 0; + p->cMinReadsPerPage = UINT32_MAX; + p->cMaxReadsPerPage = 0; + + p->cReadsLastPage = 0; + p->u32LastPageAddr = 0; +} + +static void physReadStatsReset(POHCIPHYSREADSTATS p) +{ + descReadStatsReset(&p->ed); + descReadStatsReset(&p->td); + descReadStatsReset(&p->all); + + p->cCrossReads = 0; + p->cCacheReads = 0; + p->cPageReads = 0; +} + +static void physReadStatsUpdateDesc(POHCIDESCREADSTATS p, uint32_t u32Addr) +{ + const uint32_t u32PageAddr = u32Addr & ~UINT32_C(0xFFF); + + ++p->cReads; + + if (p->u32LastPageAddr == 0) + { + /* First call. */ + ++p->cReadsLastPage; + p->u32LastPageAddr = u32PageAddr; + } + else if (u32PageAddr != p->u32LastPageAddr) + { + /* New page. */ + ++p->cPageChange; + + p->cMinReadsPerPage = RT_MIN(p->cMinReadsPerPage, p->cReadsLastPage); + p->cMaxReadsPerPage = RT_MAX(p->cMaxReadsPerPage, p->cReadsLastPage);; + + p->cReadsLastPage = 1; + p->u32LastPageAddr = u32PageAddr; + } + else + { + /* Read on the same page. */ + ++p->cReadsLastPage; + } +} + +static void physReadStatsPrint(POHCIPHYSREADSTATS p) +{ + p->ed.cMinReadsPerPage = RT_MIN(p->ed.cMinReadsPerPage, p->ed.cReadsLastPage); + p->ed.cMaxReadsPerPage = RT_MAX(p->ed.cMaxReadsPerPage, p->ed.cReadsLastPage);; + + p->td.cMinReadsPerPage = RT_MIN(p->td.cMinReadsPerPage, p->td.cReadsLastPage); + p->td.cMaxReadsPerPage = RT_MAX(p->td.cMaxReadsPerPage, p->td.cReadsLastPage);; + + p->all.cMinReadsPerPage = RT_MIN(p->all.cMinReadsPerPage, p->all.cReadsLastPage); + p->all.cMaxReadsPerPage = RT_MAX(p->all.cMaxReadsPerPage, p->all.cReadsLastPage);; + + LogRel(("PHYSREAD:\n" + " ED: %d, %d, %d/%d\n" + " TD: %d, %d, %d/%d\n" + " ALL: %d, %d, %d/%d\n" + " C: %d, %d, %d\n" + "", + p->ed.cReads, p->ed.cPageChange, p->ed.cMinReadsPerPage, p->ed.cMaxReadsPerPage, + p->td.cReads, p->td.cPageChange, p->td.cMinReadsPerPage, p->td.cMaxReadsPerPage, + p->all.cReads, p->all.cPageChange, p->all.cMinReadsPerPage, p->all.cMaxReadsPerPage, + p->cCrossReads, p->cCacheReads, p->cPageReads + )); + + physReadStatsReset(p); +} + +# endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */ +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + +static void ohciR3PhysReadCacheInvalidate(POHCIPAGECACHE pPageCache) +{ + pPageCache->GCPhysReadCacheAddr = NIL_RTGCPHYS; +} + +static void ohciR3PhysReadCacheRead(PPDMDEVINS pDevIns, POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, void *pvBuf, size_t cbBuf) +{ + const RTGCPHYS PageAddr = GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK; + + if (PageAddr == ((GCPhys + cbBuf) & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK)) + { + if (PageAddr != pPageCache->GCPhysReadCacheAddr) + { + PDMDevHlpPCIPhysRead(pDevIns, PageAddr, pPageCache->abPhysReadCache, sizeof(pPageCache->abPhysReadCache)); + pPageCache->GCPhysReadCacheAddr = PageAddr; +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cPageReads; +# endif + } + + memcpy(pvBuf, &pPageCache->abPhysReadCache[GCPhys & GUEST_PAGE_OFFSET_MASK], cbBuf); +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cCacheReads; +# endif + } + else + { + PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pvBuf, cbBuf); +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cCrossReads; +# endif + } +} + + +/** + * Updates the data in the given page cache if the given guest physical address is currently contained + * in the cache. + * + * @returns nothing. + * @param pPageCache The page cache to update. + * @param GCPhys The guest physical address needing the update. + * @param pvBuf Pointer to the buffer to update the page cache with. + * @param cbBuf Number of bytes to update. + */ +static void ohciR3PhysCacheUpdate(POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, const void *pvBuf, size_t cbBuf) +{ + const RTGCPHYS GCPhysPage = GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK; + + if (GCPhysPage == pPageCache->GCPhysReadCacheAddr) + { + uint32_t offPage = GCPhys & GUEST_PAGE_OFFSET_MASK; + memcpy(&pPageCache->abPhysReadCache[offPage], pvBuf, RT_MIN(GUEST_PAGE_SIZE - offPage, cbBuf)); + } +} + +/** + * Update any cached ED data with the given endpoint descriptor at the given address. + * + * @returns nothing. + * @param pThisCC The OHCI instance data for the current context. + * @param EdAddr Endpoint descriptor address. + * @param pEd The endpoint descriptor which got updated. + */ +DECLINLINE(void) ohciR3CacheEdUpdate(POHCICC pThisCC, RTGCPHYS32 EdAddr, PCOHCIED pEd) +{ + ohciR3PhysCacheUpdate(&pThisCC->CacheED, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, sizeof(uint32_t)); +} + + +/** + * Update any cached TD data with the given transfer descriptor at the given address. + * + * @returns nothing. + * @param pThisCC The OHCI instance data, current context. + * @param TdAddr Transfer descriptor address. + * @param pTd The transfer descriptor which got updated. + */ +DECLINLINE(void) ohciR3CacheTdUpdate(POHCICC pThisCC, RTGCPHYS32 TdAddr, PCOHCITD pTd) +{ + ohciR3PhysCacheUpdate(&pThisCC->CacheTD, TdAddr, pTd, sizeof(*pTd)); +} + +# endif /* VBOX_WITH_OHCI_PHYS_READ_CACHE */ + +/** + * Reads an OHCIED. + */ +DECLINLINE(void) ohciR3ReadEd(PPDMDEVINS pDevIns, uint32_t EdAddr, POHCIED pEd) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsUpdateDesc(&g_PhysReadState.ed, EdAddr); + physReadStatsUpdateDesc(&g_PhysReadState.all, EdAddr); +# endif +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3PhysReadCacheRead(pDevIns, &pThisCC->CacheED, EdAddr, pEd, sizeof(*pEd)); +#else + ohciR3GetDWords(pDevIns, EdAddr, (uint32_t *)pEd, sizeof(*pEd) >> 2); +#endif +} + +/** + * Reads an OHCITD. + */ +DECLINLINE(void) ohciR3ReadTd(PPDMDEVINS pDevIns, uint32_t TdAddr, POHCITD pTd) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsUpdateDesc(&g_PhysReadState.td, TdAddr); + physReadStatsUpdateDesc(&g_PhysReadState.all, TdAddr); +# endif +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3PhysReadCacheRead(pDevIns, &pThisCC->CacheTD, TdAddr, pTd, sizeof(*pTd)); +#else + ohciR3GetDWords(pDevIns, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2); +#endif +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + uint32_t hichg; + hichg = pTd->hwinfo; + Log3(("ohciR3ReadTd(,%#010x,): R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x UNK=%#x\n", + TdAddr, + (pTd->hwinfo >> 18) & 1, + (pTd->hwinfo >> 19) & 3, + (pTd->hwinfo >> 21) & 7, + (pTd->hwinfo >> 24) & 3, + (pTd->hwinfo >> 26) & 3, + (pTd->hwinfo >> 28) &15, + pTd->cbp, + pTd->NextTD, + pTd->be, + pTd->hwinfo & TD_HWINFO_UNKNOWN_MASK)); +# if 0 + if (LogIs3Enabled()) + { + /* + * usbohci.sys (32-bit XP) allocates 0x80 bytes per TD: + * 0x00-0x0f is the OHCI TD. + * 0x10-0x1f for isochronous TDs + * 0x20 is the physical address of this TD. + * 0x24 is initialized with 0x64745948, probably a magic. + * 0x28 is some kind of flags. the first bit begin the allocated / not allocated indicator. + * 0x30 is a pointer to something. endpoint? interface? device? + * 0x38 is initialized to 0xdeadface. but is changed into a pointer or something. + * 0x40 looks like a pointer. + * The rest is unknown and initialized with zeros. + */ + uint8_t abXpTd[0x80]; + ohciR3PhysRead(pDevIns, TdAddr, abXpTd, sizeof(abXpTd)); + Log3(("WinXpTd: alloc=%d PhysSelf=%RX32 s2=%RX32 magic=%RX32 s4=%RX32 s5=%RX32\n" + "%.*Rhxd\n", + abXpTd[28] & RT_BIT(0), + *((uint32_t *)&abXpTd[0x20]), *((uint32_t *)&abXpTd[0x30]), + *((uint32_t *)&abXpTd[0x24]), *((uint32_t *)&abXpTd[0x38]), + *((uint32_t *)&abXpTd[0x40]), + sizeof(abXpTd), &abXpTd[0])); + } +# endif + } +# endif +} + +/** + * Reads an OHCIITD. + */ +DECLINLINE(void) ohciR3ReadITd(PPDMDEVINS pDevIns, POHCI pThis, uint32_t ITdAddr, POHCIITD pITd) +{ + ohciR3GetDWords(pDevIns, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t)); +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + Log3(("ohciR3ReadITd(,%#010x,): SF=%#06x (%#RX32) DI=%#x FC=%d CC=%#x BP0=%#010x NextTD=%#010x BE=%#010x\n", + ITdAddr, + pITd->HwInfo & 0xffff, pThis->HcFmNumber, + (pITd->HwInfo >> 21) & 7, + (pITd->HwInfo >> 24) & 7, + (pITd->HwInfo >> 28) &15, + pITd->BP0, + pITd->NextTD, + pITd->BE)); + Log3(("psw0=%x:%03x psw1=%x:%03x psw2=%x:%03x psw3=%x:%03x psw4=%x:%03x psw5=%x:%03x psw6=%x:%03x psw7=%x:%03x\n", + pITd->aPSW[0] >> 12, pITd->aPSW[0] & 0xfff, + pITd->aPSW[1] >> 12, pITd->aPSW[1] & 0xfff, + pITd->aPSW[2] >> 12, pITd->aPSW[2] & 0xfff, + pITd->aPSW[3] >> 12, pITd->aPSW[3] & 0xfff, + pITd->aPSW[4] >> 12, pITd->aPSW[4] & 0xfff, + pITd->aPSW[5] >> 12, pITd->aPSW[5] & 0xfff, + pITd->aPSW[6] >> 12, pITd->aPSW[6] & 0xfff, + pITd->aPSW[7] >> 12, pITd->aPSW[7] & 0xfff)); + } +# else + RT_NOREF(pThis); +# endif +} + + +/** + * Writes an OHCIED. + */ +DECLINLINE(void) ohciR3WriteEd(PPDMDEVINS pDevIns, uint32_t EdAddr, PCOHCIED pEd) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCIED EdOld; + uint32_t hichg; + + ohciR3GetDWords(pDevIns, EdAddr, (uint32_t *)&EdOld, sizeof(EdOld) >> 2); + hichg = EdOld.hwinfo ^ pEd->hwinfo; + Log3(("ohciR3WriteEd(,%#010x,): %sFA=%#x %sEN=%#x %sD=%#x %sS=%d %sK=%d %sF=%d %sMPS=%#x %sTailP=%#010x %sHeadP=%#010x %sH=%d %sC=%d %sNextED=%#010x\n", + EdAddr, + (hichg >> 0) & 0x7f ? "*" : "", (pEd->hwinfo >> 0) & 0x7f, + (hichg >> 7) & 0xf ? "*" : "", (pEd->hwinfo >> 7) & 0xf, + (hichg >> 11) & 3 ? "*" : "", (pEd->hwinfo >> 11) & 3, + (hichg >> 13) & 1 ? "*" : "", (pEd->hwinfo >> 13) & 1, + (hichg >> 14) & 1 ? "*" : "", (pEd->hwinfo >> 14) & 1, + (hichg >> 15) & 1 ? "*" : "", (pEd->hwinfo >> 15) & 1, + (hichg >> 24) &0x3ff ? "*" : "", (pEd->hwinfo >> 16) &0x3ff, + EdOld.TailP != pEd->TailP ? "*" : "", pEd->TailP, + (EdOld.HeadP & ~3) != (pEd->HeadP & ~3) ? "*" : "", pEd->HeadP & ~3, + (EdOld.HeadP ^ pEd->HeadP) & 1 ? "*" : "", pEd->HeadP & 1, + (EdOld.HeadP ^ pEd->HeadP) & 2 ? "*" : "", (pEd->HeadP >> 1) & 1, + EdOld.NextED != pEd->NextED ? "*" : "", pEd->NextED)); + } +# endif + + ohciR3PutDWords(pDevIns, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, 1); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3CacheEdUpdate(pThisCC, EdAddr, pEd); +#endif +} + + +/** + * Writes an OHCITD. + */ +DECLINLINE(void) ohciR3WriteTd(PPDMDEVINS pDevIns, uint32_t TdAddr, PCOHCITD pTd, const char *pszLogMsg) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCITD TdOld; + ohciR3GetDWords(pDevIns, TdAddr, (uint32_t *)&TdOld, sizeof(TdOld) >> 2); + uint32_t hichg = TdOld.hwinfo ^ pTd->hwinfo; + Log3(("ohciR3WriteTd(,%#010x,): %sR=%d %sDP=%d %sDI=%#x %sT=%d %sEC=%d %sCC=%#x %sCBP=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n", + TdAddr, + (hichg >> 18) & 1 ? "*" : "", (pTd->hwinfo >> 18) & 1, + (hichg >> 19) & 3 ? "*" : "", (pTd->hwinfo >> 19) & 3, + (hichg >> 21) & 7 ? "*" : "", (pTd->hwinfo >> 21) & 7, + (hichg >> 24) & 3 ? "*" : "", (pTd->hwinfo >> 24) & 3, + (hichg >> 26) & 3 ? "*" : "", (pTd->hwinfo >> 26) & 3, + (hichg >> 28) &15 ? "*" : "", (pTd->hwinfo >> 28) &15, + TdOld.cbp != pTd->cbp ? "*" : "", pTd->cbp, + TdOld.NextTD != pTd->NextTD ? "*" : "", pTd->NextTD, + TdOld.be != pTd->be ? "*" : "", pTd->be, + pszLogMsg)); + } +# else + RT_NOREF(pszLogMsg); +# endif + ohciR3PutDWords(pDevIns, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3CacheTdUpdate(pThisCC, TdAddr, pTd); +#endif +} + +/** + * Writes an OHCIITD. + */ +DECLINLINE(void) ohciR3WriteITd(PPDMDEVINS pDevIns, POHCI pThis, uint32_t ITdAddr, PCOHCIITD pITd, const char *pszLogMsg) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCIITD ITdOld; + ohciR3GetDWords(pDevIns, ITdAddr, (uint32_t *)&ITdOld, sizeof(ITdOld) / sizeof(uint32_t)); + uint32_t HIChg = ITdOld.HwInfo ^ pITd->HwInfo; + Log3(("ohciR3WriteITd(,%#010x,): %sSF=%#x (now=%#RX32) %sDI=%#x %sFC=%d %sCC=%#x %sBP0=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n", + ITdAddr, + (HIChg & 0xffff) & 1 ? "*" : "", pITd->HwInfo & 0xffff, pThis->HcFmNumber, + (HIChg >> 21) & 7 ? "*" : "", (pITd->HwInfo >> 21) & 7, + (HIChg >> 24) & 7 ? "*" : "", (pITd->HwInfo >> 24) & 7, + (HIChg >> 28) &15 ? "*" : "", (pITd->HwInfo >> 28) &15, + ITdOld.BP0 != pITd->BP0 ? "*" : "", pITd->BP0, + ITdOld.NextTD != pITd->NextTD ? "*" : "", pITd->NextTD, + ITdOld.BE != pITd->BE ? "*" : "", pITd->BE, + pszLogMsg)); + Log3(("psw0=%s%x:%s%03x psw1=%s%x:%s%03x psw2=%s%x:%s%03x psw3=%s%x:%s%03x psw4=%s%x:%s%03x psw5=%s%x:%s%03x psw6=%s%x:%s%03x psw7=%s%x:%s%03x\n", + (ITdOld.aPSW[0] >> 12) != (pITd->aPSW[0] >> 12) ? "*" : "", pITd->aPSW[0] >> 12, (ITdOld.aPSW[0] & 0xfff) != (pITd->aPSW[0] & 0xfff) ? "*" : "", pITd->aPSW[0] & 0xfff, + (ITdOld.aPSW[1] >> 12) != (pITd->aPSW[1] >> 12) ? "*" : "", pITd->aPSW[1] >> 12, (ITdOld.aPSW[1] & 0xfff) != (pITd->aPSW[1] & 0xfff) ? "*" : "", pITd->aPSW[1] & 0xfff, + (ITdOld.aPSW[2] >> 12) != (pITd->aPSW[2] >> 12) ? "*" : "", pITd->aPSW[2] >> 12, (ITdOld.aPSW[2] & 0xfff) != (pITd->aPSW[2] & 0xfff) ? "*" : "", pITd->aPSW[2] & 0xfff, + (ITdOld.aPSW[3] >> 12) != (pITd->aPSW[3] >> 12) ? "*" : "", pITd->aPSW[3] >> 12, (ITdOld.aPSW[3] & 0xfff) != (pITd->aPSW[3] & 0xfff) ? "*" : "", pITd->aPSW[3] & 0xfff, + (ITdOld.aPSW[4] >> 12) != (pITd->aPSW[4] >> 12) ? "*" : "", pITd->aPSW[4] >> 12, (ITdOld.aPSW[4] & 0xfff) != (pITd->aPSW[4] & 0xfff) ? "*" : "", pITd->aPSW[4] & 0xfff, + (ITdOld.aPSW[5] >> 12) != (pITd->aPSW[5] >> 12) ? "*" : "", pITd->aPSW[5] >> 12, (ITdOld.aPSW[5] & 0xfff) != (pITd->aPSW[5] & 0xfff) ? "*" : "", pITd->aPSW[5] & 0xfff, + (ITdOld.aPSW[6] >> 12) != (pITd->aPSW[6] >> 12) ? "*" : "", pITd->aPSW[6] >> 12, (ITdOld.aPSW[6] & 0xfff) != (pITd->aPSW[6] & 0xfff) ? "*" : "", pITd->aPSW[6] & 0xfff, + (ITdOld.aPSW[7] >> 12) != (pITd->aPSW[7] >> 12) ? "*" : "", pITd->aPSW[7] >> 12, (ITdOld.aPSW[7] & 0xfff) != (pITd->aPSW[7] & 0xfff) ? "*" : "", pITd->aPSW[7] & 0xfff)); + } +# else + RT_NOREF(pThis, pszLogMsg); +# endif + ohciR3PutDWords(pDevIns, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t)); +} + + +# ifdef LOG_ENABLED + +/** + * Core TD queue dumper. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpTdQueueCore(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull) +{ + uint32_t GCPhys = GCPhysHead; + int cIterations = 128; + for (;;) + { + OHCITD Td; + Log4(("%#010x%s%s", GCPhys, + GCPhys && ohciR3InFlightFind(pThisCC, GCPhys) >= 0 ? "~" : "", + GCPhys && ohciR3InDoneQueueFind(pThisCC, GCPhys) >= 0 ? "^" : "")); + if (GCPhys == 0 || GCPhys == GCPhysTail) + break; + + /* can't use ohciR3ReadTd() because of Log4. */ + ohciR3GetDWords(pDevIns, GCPhys, (uint32_t *)&Td, sizeof(Td) >> 2); + if (fFull) + Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ", + (Td.hwinfo >> 18) & 1, + (Td.hwinfo >> 19) & 3, + (Td.hwinfo >> 21) & 7, + (Td.hwinfo >> 24) & 3, + (Td.hwinfo >> 26) & 3, + (Td.hwinfo >> 28) &15, + Td.cbp, + Td.NextTD, + Td.be)); + else + Log4((" -> ")); + GCPhys = Td.NextTD & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + if (!--cIterations) + break; + } +} + +/** + * Dumps a TD queue. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpTdQueue(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, const char *pszMsg) +{ + if (pszMsg) + Log4(("%s: ", pszMsg)); + ohciR3DumpTdQueueCore(pDevIns, pThisCC, GCPhysHead, 0, true); + Log4(("\n")); +} + +/** + * Core ITD queue dumper. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpITdQueueCore(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull) +{ + RT_NOREF(fFull); + uint32_t GCPhys = GCPhysHead; + int cIterations = 100; + for (;;) + { + OHCIITD ITd; + Log4(("%#010x%s%s", GCPhys, + GCPhys && ohciR3InFlightFind(pThisCC, GCPhys) >= 0 ? "~" : "", + GCPhys && ohciR3InDoneQueueFind(pThisCC, GCPhys) >= 0 ? "^" : "")); + if (GCPhys == 0 || GCPhys == GCPhysTail) + break; + + /* can't use ohciR3ReadTd() because of Log4. */ + ohciR3GetDWords(pDevIns, GCPhys, (uint32_t *)&ITd, sizeof(ITd) / sizeof(uint32_t)); + /*if (fFull) + Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ", + (Td.hwinfo >> 18) & 1, + (Td.hwinfo >> 19) & 3, + (Td.hwinfo >> 21) & 7, + (Td.hwinfo >> 24) & 3, + (Td.hwinfo >> 26) & 3, + (Td.hwinfo >> 28) &15, + Td.cbp, + Td.NextTD, + Td.be)); + else*/ + Log4((" -> ")); + GCPhys = ITd.NextTD & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + if (!--cIterations) + break; + } +} + +/** + * Dumps a ED list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpEdList(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, const char *pszMsg, bool fTDs) +{ + RT_NOREF(fTDs); + uint32_t GCPhys = GCPhysHead; + if (pszMsg) + Log4(("%s:", pszMsg)); + for (;;) + { + OHCIED Ed; + + /* ED */ + Log4((" %#010x={", GCPhys)); + if (!GCPhys) + { + Log4(("END}\n")); + return; + } + + /* TDs */ + ohciR3ReadEd(pDevIns, GCPhys, &Ed); + if (Ed.hwinfo & ED_HWINFO_ISO) + Log4(("[I]")); + if ((Ed.HeadP & ED_HEAD_HALTED) || (Ed.hwinfo & ED_HWINFO_SKIP)) + { + if ((Ed.HeadP & ED_HEAD_HALTED) && (Ed.hwinfo & ED_HWINFO_SKIP)) + Log4(("SH}")); + else if (Ed.hwinfo & ED_HWINFO_SKIP) + Log4(("S-}")); + else + Log4(("-H}")); + } + else + { + if (Ed.hwinfo & ED_HWINFO_ISO) + ohciR3DumpITdQueueCore(pDevIns, pThisCC, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false); + else + ohciR3DumpTdQueueCore(pDevIns, pThisCC, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false); + Log4(("}")); + } + + /* next */ + GCPhys = Ed.NextED & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + } + /* not reached */ +} + +# endif /* LOG_ENABLED */ + + +DECLINLINE(int) ohciR3InFlightFindFree(POHCICC pThisCC, const int iStart) +{ + unsigned i = iStart; + while (i < RT_ELEMENTS(pThisCC->aInFlight)) + { + if (pThisCC->aInFlight[i].pUrb == NULL) + return i; + i++; + } + i = iStart; + while (i-- > 0) + { + if (pThisCC->aInFlight[i].pUrb == NULL) + return i; + } + return -1; +} + + +/** + * Record an in-flight TD. + * + * @param pThis OHCI instance data, shared edition. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + * @param pUrb The URB. + */ +static void ohciR3InFlightAdd(POHCI pThis, POHCICC pThisCC, uint32_t GCPhysTD, PVUSBURB pUrb) +{ + int i = ohciR3InFlightFindFree(pThisCC, (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight)); + if (i >= 0) + { +# ifdef LOG_ENABLED + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; +# endif + pThisCC->aInFlight[i].GCPhysTD = GCPhysTD; + pThisCC->aInFlight[i].pUrb = pUrb; + pThisCC->cInFlight++; + return; + } + AssertMsgFailed(("Out of space cInFlight=%d!\n", pThisCC->cInFlight)); + RT_NOREF(pThis); +} + + +/** + * Record in-flight TDs for an URB. + * + * @param pThis OHCI instance data, shared edition. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param pUrb The URB. + */ +static void ohciR3InFlightAddUrb(POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb) +{ + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + ohciR3InFlightAdd(pThis, pThisCC, pUrb->paTds[iTd].TdAddr, pUrb); +} + + +/** + * Finds a in-flight TD. + * + * @returns Index of the record. + * @returns -1 if not found. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + * @remark This has to be fast. + */ +static int ohciR3InFlightFind(POHCICC pThisCC, uint32_t GCPhysTD) +{ + unsigned cLeft = pThisCC->cInFlight; + unsigned i = (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight); + const int iLast = i; + while (i < RT_ELEMENTS(pThisCC->aInFlight)) + { + if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb) + return i; + if (pThisCC->aInFlight[i].pUrb) + if (cLeft-- <= 1) + return -1; + i++; + } + i = iLast; + while (i-- > 0) + { + if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb) + return i; + if (pThisCC->aInFlight[i].pUrb) + if (cLeft-- <= 1) + return -1; + } + return -1; +} + + +/** + * Checks if a TD is in-flight. + * + * @returns true if in flight, false if not. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static bool ohciR3IsTdInFlight(POHCICC pThisCC, uint32_t GCPhysTD) +{ + return ohciR3InFlightFind(pThisCC, GCPhysTD) >= 0; +} + +/** + * Returns a URB associated with an in-flight TD, if any. + * + * @returns pointer to URB if TD is in flight. + * @returns NULL if not in flight. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static PVUSBURB ohciR3TdInFlightUrb(POHCICC pThisCC, uint32_t GCPhysTD) +{ + int i; + + i = ohciR3InFlightFind(pThisCC, GCPhysTD); + if ( i >= 0 ) + return pThisCC->aInFlight[i].pUrb; + return NULL; +} + +/** + * Removes a in-flight TD. + * + * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight. + * @returns -1 if not found. + * @param pThis OHCI instance data, shared edition (for logging). + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static int ohciR3InFlightRemove(POHCI pThis, POHCICC pThisCC, uint32_t GCPhysTD) +{ + int i = ohciR3InFlightFind(pThisCC, GCPhysTD); + if (i >= 0) + { +# ifdef LOG_ENABLED + const int cFramesInFlight = pThis->HcFmNumber - pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo; +# else + const int cFramesInFlight = 0; RT_NOREF(pThis); +# endif + Log2(("ohciR3InFlightRemove: reaping TD=%#010x %d frames (%#010x-%#010x)\n", + GCPhysTD, cFramesInFlight, pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo, pThis->HcFmNumber)); + pThisCC->aInFlight[i].GCPhysTD = 0; + pThisCC->aInFlight[i].pUrb = NULL; + pThisCC->cInFlight--; + return cFramesInFlight; + } + AssertMsgFailed(("TD %#010x is not in flight\n", GCPhysTD)); + return -1; +} + + +/** + * Removes all TDs associated with a URB from the in-flight tracking. + * + * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight. + * @returns -1 if not found. + * @param pThis OHCI instance data, shared edition (for logging). + * @param pThisCC OHCI instance data, ring-3 edition. + * @param pUrb The URB. + */ +static int ohciR3InFlightRemoveUrb(POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb) +{ + int cFramesInFlight = ohciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[0].TdAddr); + if (pUrb->pHci->cTds > 1) + { + for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++) + if (ohciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[iTd].TdAddr) < 0) + cFramesInFlight = -1; + } + return cFramesInFlight; +} + + +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + +/** + * Empties the in-done-queue. + * @param pThisCC OHCI instance data, ring-3 edition. + */ +static void ohciR3InDoneQueueZap(POHCICC pThisCC) +{ + pThisCC->cInDoneQueue = 0; +} + +/** + * Finds a TD in the in-done-queue. + * @returns >= 0 on success. + * @returns -1 if not found. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static int ohciR3InDoneQueueFind(POHCICC pThisCC, uint32_t GCPhysTD) +{ + unsigned i = pThisCC->cInDoneQueue; + while (i-- > 0) + if (pThisCC->aInDoneQueue[i].GCPhysTD == GCPhysTD) + return i; + return -1; +} + +/** + * Checks that the specified TD is not in the done queue. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static bool ohciR3InDoneQueueCheck(POHCICC pThisCC, uint32_t GCPhysTD) +{ + int i = ohciR3InDoneQueueFind(pThisCC, GCPhysTD); +# if 0 + /* This condition has been observed with the USB tablet emulation or with + * a real USB mouse and an SMP XP guest. I am also not sure if this is + * really a problem for us. The assertion checks that the guest doesn't + * re-submit a TD which is still in the done queue. It seems to me that + * this should only be a problem if we either keep track of TDs in the done + * queue somewhere else as well (in which case we should also free those + * references in time, and I can't see any code doing that) or if we + * manipulate TDs in the done queue in some way that might fail if they are + * re-submitted (can't see anything like that either). + */ + AssertMsg(i < 0, ("TD %#010x (i=%d)\n", GCPhysTD, i)); +# endif + return i < 0; +} + + +# if defined(VBOX_STRICT) && defined(LOG_ENABLED) +/** + * Adds a TD to the in-done-queue tracking, checking that it's not there already. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static void ohciR3InDoneQueueAdd(POHCICC pThisCC, uint32_t GCPhysTD) +{ + Assert(pThisCC->cInDoneQueue + 1 <= RT_ELEMENTS(pThisCC->aInDoneQueue)); + if (ohciR3InDoneQueueCheck(pThisCC, GCPhysTD)) + pThisCC->aInDoneQueue[pThisCC->cInDoneQueue++].GCPhysTD = GCPhysTD; +} +# endif /* VBOX_STRICT */ +# endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */ + + +/** + * OHCI Transport Buffer - represents a OHCI Transport Descriptor (TD). + * A TD may be split over max 2 pages. + */ +typedef struct OHCIBUF +{ + /** Pages involved. */ + struct OHCIBUFVEC + { + /** The 32-bit physical address of this part. */ + uint32_t Addr; + /** The length. */ + uint32_t cb; + } aVecs[2]; + /** Number of valid entries in aVecs. */ + uint32_t cVecs; + /** The total length. */ + uint32_t cbTotal; +} OHCIBUF, *POHCIBUF; + + +/** + * Sets up a OHCI transport buffer. + * + * @param pBuf OHCI buffer. + * @param cbp Current buffer pointer. 32-bit physical address. + * @param be Last byte in buffer (BufferEnd). 32-bit physical address. + */ +static void ohciR3BufInit(POHCIBUF pBuf, uint32_t cbp, uint32_t be) +{ + if (!cbp || !be) + { + pBuf->cVecs = 0; + pBuf->cbTotal = 0; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=0 EMPTY\n", cbp, be)); + } + else if ((cbp & ~0xfff) == (be & ~0xfff) && (cbp <= be)) + { + pBuf->aVecs[0].Addr = cbp; + pBuf->aVecs[0].cb = (be - cbp) + 1; + pBuf->cVecs = 1; + pBuf->cbTotal = pBuf->aVecs[0].cb; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u\n", cbp, be, pBuf->cbTotal)); + } + else + { + pBuf->aVecs[0].Addr = cbp; + pBuf->aVecs[0].cb = 0x1000 - (cbp & 0xfff); + pBuf->aVecs[1].Addr = be & ~0xfff; + pBuf->aVecs[1].cb = (be & 0xfff) + 1; + pBuf->cVecs = 2; + pBuf->cbTotal = pBuf->aVecs[0].cb + pBuf->aVecs[1].cb; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u PAGE FLIP\n", cbp, be, pBuf->cbTotal)); + } +} + +/** + * Updates a OHCI transport buffer. + * + * This is called upon completion to adjust the sector lengths if + * the total length has changed. (received less then we had space for + * or a partial transfer.) + * + * @param pBuf The buffer to update. cbTotal contains the new total on input. + * While the aVecs[*].cb members is updated upon return. + */ +static void ohciR3BufUpdate(POHCIBUF pBuf) +{ + for (uint32_t i = 0, cbCur = 0; i < pBuf->cVecs; i++) + { + if (cbCur + pBuf->aVecs[i].cb > pBuf->cbTotal) + { + pBuf->aVecs[i].cb = pBuf->cbTotal - cbCur; + pBuf->cVecs = i + 1; + return; + } + cbCur += pBuf->aVecs[i].cb; + } +} + + +/** A worker for ohciR3UnlinkTds(). */ +static bool ohciR3UnlinkIsochronousTdInList(PPDMDEVINS pDevIns, POHCI pThis, uint32_t TdAddr, POHCIITD pITd, POHCIED pEd) +{ + const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK; + Log(("ohciUnlinkIsocTdInList: Unlinking non-head ITD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n", + TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); + AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); + + uint32_t cIterations = 256; + uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; + while ( CurTdAddr != LastTdAddr + && cIterations-- > 0) + { + OHCIITD ITd; + ohciR3ReadITd(pDevIns, pThis, CurTdAddr, &ITd); + if ((ITd.NextTD & ED_PTR_MASK) == TdAddr) + { + ITd.NextTD = (pITd->NextTD & ED_PTR_MASK) | (ITd.NextTD & ~ED_PTR_MASK); + ohciR3WriteITd(pDevIns, pThis, CurTdAddr, &ITd, "ohciUnlinkIsocTdInList"); + pITd->NextTD &= ~ED_PTR_MASK; + return true; + } + + /* next */ + CurTdAddr = ITd.NextTD & ED_PTR_MASK; + } + + Log(("ohciUnlinkIsocTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations)); + return false; +} + + +/** A worker for ohciR3UnlinkTds(). */ +static bool ohciR3UnlinkGeneralTdInList(PPDMDEVINS pDevIns, uint32_t TdAddr, POHCITD pTd, POHCIED pEd) +{ + const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK; + Log(("ohciR3UnlinkGeneralTdInList: Unlinking non-head TD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n", + TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); + AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); + + uint32_t cIterations = 256; + uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; + while ( CurTdAddr != LastTdAddr + && cIterations-- > 0) + { + OHCITD Td; + ohciR3ReadTd(pDevIns, CurTdAddr, &Td); + if ((Td.NextTD & ED_PTR_MASK) == TdAddr) + { + Td.NextTD = (pTd->NextTD & ED_PTR_MASK) | (Td.NextTD & ~ED_PTR_MASK); + ohciR3WriteTd(pDevIns, CurTdAddr, &Td, "ohciR3UnlinkGeneralTdInList"); + pTd->NextTD &= ~ED_PTR_MASK; + return true; + } + + /* next */ + CurTdAddr = Td.NextTD & ED_PTR_MASK; + } + + Log(("ohciR3UnlinkGeneralTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations)); + return false; +} + + +/** + * Unlinks the TDs that makes up the URB from the ED. + * + * @returns success indicator. true if successfully unlinked. + * @returns false if the TD was not found in the list. + */ +static bool ohciR3UnlinkTds(PPDMDEVINS pDevIns, POHCI pThis, PVUSBURB pUrb, POHCIED pEd) +{ + /* + * Don't unlink more than once. + */ + if (pUrb->pHci->fUnlinked) + return true; + pUrb->pHci->fUnlinked = true; + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr; + + /* + * Unlink the TD from the ED list. + * The normal case is that it's at the head of the list. + */ + Assert((ITdAddr & ED_PTR_MASK) == ITdAddr); + if ((pEd->HeadP & ED_PTR_MASK) == ITdAddr) + { + pEd->HeadP = (pITd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK); + pITd->NextTD &= ~ED_PTR_MASK; + } + else + { + /* + * It's probably somewhere in the list, not a unlikely situation with + * the current isochronous code. + */ + if (!ohciR3UnlinkIsochronousTdInList(pDevIns, pThis, ITdAddr, pITd, pEd)) + return false; + } + } + } + else + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr; + + /** @todo r=bird: Messing with the toggle flag in prepare is probably not correct + * when we encounter a STALL error, 4.3.1.3.7.2: ''If an endpoint returns a STALL + * PID, the Host Controller retires the General TD with the ConditionCode set + * to STALL and halts the endpoint. The CurrentBufferPointer, ErrorCount, and + * dataToggle fields retain the values that they had at the start of the + * transaction.'' */ + + /* update toggle and set data toggle carry */ + pTd->hwinfo &= ~TD_HWINFO_TOGGLE; + if ( pTd->hwinfo & TD_HWINFO_TOGGLE_HI ) + { + if ( !!(pTd->hwinfo & TD_HWINFO_TOGGLE_LO) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */ + pTd->hwinfo |= TD_HWINFO_TOGGLE_LO; + else + pTd->hwinfo &= ~TD_HWINFO_TOGGLE_LO; + } + else + { + if ( !!(pEd->HeadP & ED_HEAD_CARRY) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */ + pEd->HeadP |= ED_HEAD_CARRY; + else + pEd->HeadP &= ~ED_HEAD_CARRY; + } + + /* + * Unlink the TD from the ED list. + * The normal case is that it's at the head of the list. + */ + Assert((TdAddr & ED_PTR_MASK) == TdAddr); + if ((pEd->HeadP & ED_PTR_MASK) == TdAddr) + { + pEd->HeadP = (pTd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK); + pTd->NextTD &= ~ED_PTR_MASK; + } + else + { + /* + * The TD is probably somewhere in the list. + * + * This shouldn't ever happen unless there was a failure! Even on failure, + * we can screw up the HCD state by picking out a TD from within the list + * like this! If this turns out to be a problem, we have to find a better + * solution. For now we'll hope the HCD handles it... + */ + if (!ohciR3UnlinkGeneralTdInList(pDevIns, TdAddr, pTd, pEd)) + return false; + } + + /* + * Only unlink the first TD on error. + * See comment in ohciR3RhXferCompleteGeneralURB(). + */ + if (pUrb->enmStatus != VUSBSTATUS_OK) + break; + } + } + + return true; +} + + +/** + * Checks that the transport descriptors associated with the URB + * hasn't been changed in any way indicating that they may have been canceled. + * + * This rountine also updates the TD copies contained within the URB. + * + * @returns true if the URB has been canceled, otherwise false. + * @param pDevIns The device instance. + * @param pThis The OHCI instance. + * @param pUrb The URB in question. + * @param pEd The ED pointer (optional). + */ +static bool ohciR3HasUrbBeenCanceled(PPDMDEVINS pDevIns, POHCI pThis, PVUSBURB pUrb, PCOHCIED pEd) +{ + if (!pUrb) + return true; + + /* + * Make sure we've got an endpoint descriptor so we can + * check for tail TDs. + */ + OHCIED Ed; + if (!pEd) + { + ohciR3ReadEd(pDevIns, pUrb->pHci->EdAddr, &Ed); + pEd = &Ed; + } + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + union + { + OHCIITD ITd; + uint32_t au32[8]; + } u; + if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK) + == (pEd->TailP & ED_PTR_MASK)) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)! [iso]\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs); + return true; + } + ohciR3ReadITd(pDevIns, pThis, pUrb->paTds[iTd].TdAddr, &u.ITd); + if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */ + || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* bp0 */ + || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */ + || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */ + && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */) + || u.au32[4] != pUrb->paTds[iTd].TdCopy[4] /* psw0&1 */ + || u.au32[5] != pUrb->paTds[iTd].TdCopy[5] /* psw2&3 */ + || u.au32[6] != pUrb->paTds[iTd].TdCopy[6] /* psw4&5 */ + || u.au32[7] != pUrb->paTds[iTd].TdCopy[7] /* psw6&7 */ + ) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled! [iso]\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(u.ITd), &u.ITd, sizeof(u.ITd), &pUrb->paTds[iTd].TdCopy[0])); + STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs); + return true; + } + pUrb->paTds[iTd].TdCopy[2] = u.au32[2]; + } + } + else + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + union + { + OHCITD Td; + uint32_t au32[4]; + } u; + if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK) + == (pEd->TailP & ED_PTR_MASK)) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)!\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs); + return true; + } + ohciR3ReadTd(pDevIns, pUrb->paTds[iTd].TdAddr, &u.Td); + if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */ + || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* cbp */ + || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */ + || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */ + && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */) + ) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled!\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(u.Td), &u.Td, sizeof(u.Td), &pUrb->paTds[iTd].TdCopy[0])); + STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs); + return true; + } + pUrb->paTds[iTd].TdCopy[2] = u.au32[2]; + } + } + return false; +} + + +/** + * Returns the OHCI_CC_* corresponding to the VUSB status code. + * + * @returns OHCI_CC_* value. + * @param enmStatus The VUSB status code. + */ +static uint32_t ohciR3VUsbStatus2OhciStatus(VUSBSTATUS enmStatus) +{ + switch (enmStatus) + { + case VUSBSTATUS_OK: return OHCI_CC_NO_ERROR; + case VUSBSTATUS_STALL: return OHCI_CC_STALL; + case VUSBSTATUS_CRC: return OHCI_CC_CRC; + case VUSBSTATUS_DATA_UNDERRUN: return OHCI_CC_DATA_UNDERRUN; + case VUSBSTATUS_DATA_OVERRUN: return OHCI_CC_DATA_OVERRUN; + case VUSBSTATUS_DNR: return OHCI_CC_DNR; + case VUSBSTATUS_NOT_ACCESSED: return OHCI_CC_NOT_ACCESSED_1; + default: + Log(("pUrb->enmStatus=%#x!!!\n", enmStatus)); + return OHCI_CC_DNR; + } +} + + +/** + * Lock the given OHCI controller instance. + * + * @returns nothing. + * @param pThisCC The OHCI controller instance to lock, ring-3 edition. + */ +DECLINLINE(void) ohciR3Lock(POHCICC pThisCC) +{ + RTCritSectEnter(&pThisCC->CritSect); + +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* Clear all caches here to avoid reading stale data from previous lock holders. */ + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED); + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif +} + + +/** + * Unlocks the given OHCI controller instance. + * + * @returns nothing. + * @param pThisCC The OHCI controller instance to unlock, ring-3 edition. + */ +DECLINLINE(void) ohciR3Unlock(POHCICC pThisCC) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* + * Clear all caches here to avoid leaving stale data behind (paranoia^2, + * already done in ohciR3Lock). + */ + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED); + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif + + RTCritSectLeave(&pThisCC->CritSect); +} + + +/** + * Worker for ohciR3RhXferCompletion that handles the completion of + * a URB made up of isochronous TDs. + * + * In general, all URBs should have status OK. + */ +static void ohciR3RhXferCompleteIsochronousURB(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb + /*, POHCIED pEd , int cFmAge*/) +{ + /* + * Copy the data back (if IN operation) and update the TDs. + */ + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr; + const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + unsigned R = (pUrb->pHci->u32FrameNo & ITD_HWINFO_SF) - (pITd->HwInfo & ITD_HWINFO_SF); + if (R >= 8) + R = 0; /* submitted ahead of time. */ + + /* + * Only one case of TD level condition code is document, so + * just set NO_ERROR here to reduce number duplicate code. + */ + pITd->HwInfo &= ~TD_HWINFO_CC; + AssertCompile(OHCI_CC_NO_ERROR == 0); + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + /* + * Update the frames and copy back the data. + * We assume that we don't get incorrect lengths here. + */ + for (unsigned i = 0; i < cFrames; i++) + { + if ( i < R + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_NOT_ACCESSED) + { + /* It should already be NotAccessed. */ + pITd->aPSW[i] |= 0xe000; /* (Don't touch the 12th bit.) */ + continue; + } + + /* Update the PSW (save the offset first in case of a IN). */ + uint32_t off = pITd->aPSW[i] & ITD_PSW_OFFSET; + pITd->aPSW[i] = ohciR3VUsbStatus2OhciStatus(pUrb->aIsocPkts[i - R].enmStatus) + >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT); + + if ( pUrb->enmDir == VUSBDIRECTION_IN + && ( pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_OK + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_UNDERRUN + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_OVERRUN)) + { + /* Set the size. */ + const unsigned cb = pUrb->aIsocPkts[i - R].cb; + pITd->aPSW[i] |= cb & ITD_PSW_SIZE; + /* Copy data. */ + if (cb) + { + uint8_t *pb = &pUrb->abData[pUrb->aIsocPkts[i - R].off]; + if (off + cb > 0x1000) + { + if (off < 0x1000) + { + /* both */ + const unsigned cb0 = 0x1000 - off; + ohciR3PhysWrite(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb0); + ohciR3PhysWrite(pDevIns, pITd->BE & ITD_BP0_MASK, pb + cb0, cb - cb0); + } + else /* only in the 2nd page */ + ohciR3PhysWrite(pDevIns, (pITd->BE & ITD_BP0_MASK) + (off & ITD_BP0_MASK), pb, cb); + } + else /* only in the 1st page */ + ohciR3PhysWrite(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb); + Log5(("packet %d: off=%#x cb=%#x pb=%p (%#x)\n" + "%.*Rhxd\n", + i + R, off, cb, pb, pb - &pUrb->abData[0], cb, pb)); + //off += cb; + } + } + } + + /* + * If the last package ended with a NotAccessed status, set ITD CC + * to DataOverrun to indicate scheduling overrun. + */ + if (pUrb->aIsocPkts[pUrb->cIsocPkts - 1].enmStatus == VUSBSTATUS_NOT_ACCESSED) + pITd->HwInfo |= OHCI_CC_DATA_OVERRUN; + } + else + { + Log(("DevOHCI: Taking untested code path at line %d...\n", __LINE__)); + /* + * Most status codes only applies to the individual packets. + * + * If we get a URB level error code of this kind, we'll distribute + * it to all the packages unless some other status is available for + * a package. This is a bit fuzzy, and we will get rid of this code + * before long! + */ + //if (pUrb->enmStatus != VUSBSTATUS_DATA_OVERRUN) + { + const unsigned uCC = ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus) + >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT); + for (unsigned i = 0; i < cFrames; i++) + pITd->aPSW[i] = uCC; + } + //else + // pITd->HwInfo |= ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus); + } + + /* + * Update the done queue interrupt timer. + */ + uint32_t DoneInt = (pITd->HwInfo & ITD_HWINFO_DI) >> ITD_HWINFO_DI_SHIFT; + if ((pITd->HwInfo & TD_HWINFO_CC) != OHCI_CC_NO_ERROR) + DoneInt = 0; /* It's cleared on error. */ + if ( DoneInt != 0x7 + && DoneInt < pThis->dqic) + pThis->dqic = DoneInt; + + /* + * Move on to the done list and write back the modified TD. + */ +# ifdef LOG_ENABLED + if (!pThis->done) + pThisCC->u32FmDoneQueueTail = pThis->HcFmNumber; +# ifdef VBOX_STRICT + ohciR3InDoneQueueAdd(pThisCC, ITdAddr); +# endif +# endif + pITd->NextTD = pThis->done; + pThis->done = ITdAddr; + + Log(("%s: ohciR3RhXferCompleteIsochronousURB: ITdAddr=%#010x EdAddr=%#010x SF=%#x (%#x) CC=%#x FC=%d " + "psw0=%x:%x psw1=%x:%x psw2=%x:%x psw3=%x:%x psw4=%x:%x psw5=%x:%x psw6=%x:%x psw7=%x:%x R=%d\n", + pUrb->pszDesc, ITdAddr, + pUrb->pHci->EdAddr, + pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber, + (pITd->HwInfo & ITD_HWINFO_CC) >> ITD_HWINFO_CC_SHIFT, + (pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT, + pITd->aPSW[0] >> ITD_PSW_CC_SHIFT, pITd->aPSW[0] & ITD_PSW_SIZE, + pITd->aPSW[1] >> ITD_PSW_CC_SHIFT, pITd->aPSW[1] & ITD_PSW_SIZE, + pITd->aPSW[2] >> ITD_PSW_CC_SHIFT, pITd->aPSW[2] & ITD_PSW_SIZE, + pITd->aPSW[3] >> ITD_PSW_CC_SHIFT, pITd->aPSW[3] & ITD_PSW_SIZE, + pITd->aPSW[4] >> ITD_PSW_CC_SHIFT, pITd->aPSW[4] & ITD_PSW_SIZE, + pITd->aPSW[5] >> ITD_PSW_CC_SHIFT, pITd->aPSW[5] & ITD_PSW_SIZE, + pITd->aPSW[6] >> ITD_PSW_CC_SHIFT, pITd->aPSW[6] & ITD_PSW_SIZE, + pITd->aPSW[7] >> ITD_PSW_CC_SHIFT, pITd->aPSW[7] & ITD_PSW_SIZE, + R)); + ohciR3WriteITd(pDevIns, pThis, ITdAddr, pITd, "retired"); + } + RT_NOREF(pThisCC); +} + + +/** + * Worker for ohciR3RhXferCompletion that handles the completion of + * a URB made up of general TDs. + */ +static void ohciR3RhXferCompleteGeneralURB(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb, + POHCIED pEd, int cFmAge) +{ + RT_NOREF(cFmAge); + + /* + * Copy the data back (if IN operation) and update the TDs. + */ + unsigned cbLeft = pUrb->cbData; + uint8_t *pb = &pUrb->abData[0]; + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr; + + /* + * Setup a ohci transfer buffer and calc the new cbp value. + */ + OHCIBUF Buf; + ohciR3BufInit(&Buf, pTd->cbp, pTd->be); + uint32_t NewCbp; + if (cbLeft >= Buf.cbTotal) + NewCbp = 0; + else + { + /* (len may have changed for short transfers) */ + Buf.cbTotal = cbLeft; + ohciR3BufUpdate(&Buf); + Assert(Buf.cVecs >= 1); + NewCbp = Buf.aVecs[Buf.cVecs-1].Addr + Buf.aVecs[Buf.cVecs-1].cb; + } + + /* + * Write back IN buffers. + */ + if ( pUrb->enmDir == VUSBDIRECTION_IN + && ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN) + && Buf.cbTotal > 0) + { + Assert(Buf.cVecs > 0); + + /* Be paranoid */ + if ( Buf.aVecs[0].cb > cbLeft + || ( Buf.cVecs > 1 + && Buf.aVecs[1].cb > (cbLeft - Buf.aVecs[0].cb))) + { + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 1); + return; + } + + ohciR3PhysWrite(pDevIns, Buf.aVecs[0].Addr, pb, Buf.aVecs[0].cb); + if (Buf.cVecs > 1) + ohciR3PhysWrite(pDevIns, Buf.aVecs[1].Addr, pb + Buf.aVecs[0].cb, Buf.aVecs[1].cb); + } + + /* advance the data buffer. */ + cbLeft -= Buf.cbTotal; + pb += Buf.cbTotal; + + /* + * Set writeback field. + */ + /* zero out writeback fields for retirement */ + pTd->hwinfo &= ~TD_HWINFO_CC; + /* always update the CurrentBufferPointer; essential for underrun/overrun errors */ + pTd->cbp = NewCbp; + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + pTd->hwinfo &= ~TD_HWINFO_ERRORS; + + /* update done queue interrupt timer */ + uint32_t DoneInt = (pTd->hwinfo & TD_HWINFO_DI) >> 21; + if ( DoneInt != 0x7 + && DoneInt < pThis->dqic) + pThis->dqic = DoneInt; + Log(("%s: ohciR3RhXferCompleteGeneralURB: ED=%#010x TD=%#010x Age=%d enmStatus=%d cbTotal=%#x NewCbp=%#010RX32 dqic=%d\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus, Buf.cbTotal, NewCbp, pThis->dqic)); + } + else + { + Log(("%s: ohciR3RhXferCompleteGeneralURB: HALTED ED=%#010x TD=%#010x (age %d) pUrb->enmStatus=%d\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus)); + pEd->HeadP |= ED_HEAD_HALTED; + pThis->dqic = 0; /* "If the Transfer Descriptor is being retired with an error, + * then the Done Queue Interrupt Counter is cleared as if the + * InterruptDelay field were zero." + */ + switch (pUrb->enmStatus) + { + case VUSBSTATUS_STALL: + pTd->hwinfo |= OHCI_CC_STALL; + break; + case VUSBSTATUS_CRC: + pTd->hwinfo |= OHCI_CC_CRC; + break; + case VUSBSTATUS_DATA_UNDERRUN: + pTd->hwinfo |= OHCI_CC_DATA_UNDERRUN; + break; + case VUSBSTATUS_DATA_OVERRUN: + pTd->hwinfo |= OHCI_CC_DATA_OVERRUN; + break; + default: /* what the hell */ + Log(("pUrb->enmStatus=%#x!!!\n", pUrb->enmStatus)); + RT_FALL_THRU(); + case VUSBSTATUS_DNR: + pTd->hwinfo |= OHCI_CC_DNR; + break; + } + } + + /* + * Move on to the done list and write back the modified TD. + */ +# ifdef LOG_ENABLED + if (!pThis->done) + pThisCC->u32FmDoneQueueTail = pThis->HcFmNumber; +# ifdef VBOX_STRICT + ohciR3InDoneQueueAdd(pThisCC, TdAddr); +# endif +# endif + pTd->NextTD = pThis->done; + pThis->done = TdAddr; + + ohciR3WriteTd(pDevIns, TdAddr, pTd, "retired"); + + /* + * If we've halted the endpoint, we stop here. + * ohciR3UnlinkTds() will make sure we've only unliked the first TD. + * + * The reason for this is that while we can have more than one TD in a URB, real + * OHCI hardware will only deal with one TD at the time and it's therefore incorrect + * to retire TDs after the endpoint has been halted. Win2k will crash or enter infinite + * kernel loop if we don't behave correctly. (See @bugref{1646}.) + */ + if (pEd->HeadP & ED_HEAD_HALTED) + break; + } + RT_NOREF(pThisCC); +} + + +/** + * Transfer completion callback routine. + * + * VUSB will call this when a transfer have been completed + * in a one or another way. + * + * @param pInterface Pointer to OHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + LogFlow(("%s: ohciR3RhXferCompletion: EdAddr=%#010RX32 cTds=%d TdAddr0=%#010RX32\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr)); + + ohciR3Lock(pThisCC); + + int cFmAge = ohciR3InFlightRemoveUrb(pThis, pThisCC, pUrb); + + /* Do nothing requiring memory access if the HC encountered an unrecoverable error. */ + if (!(pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR)) + { + pThis->fIdle = false; /* Mark as active */ + + /* get the current end point descriptor. */ + OHCIED Ed; + ohciR3ReadEd(pDevIns, pUrb->pHci->EdAddr, &Ed); + + /* + * Check that the URB hasn't been canceled and then try unlink the TDs. + * + * We drop the URB if the ED is marked halted/skip ASSUMING that this + * means the HCD has canceled the URB. + * + * If we succeed here (i.e. not dropping the URB), the TdCopy members will + * be updated but not yet written. We will delay the writing till we're done + * with the data copying, buffer pointer advancing and error handling. + */ + if (pUrb->enmStatus == VUSBSTATUS_UNDO) + { + /* Leave the TD alone - the HCD doesn't want us talking to the device. */ + Log(("%s: ohciR3RhXferCompletion: CANCELED {ED=%#010x cTds=%d TD0=%#010x age %d}\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge)); + STAM_COUNTER_INC(&pThis->StatDroppedUrbs); + ohciR3Unlock(pThisCC); + return; + } + bool fHasBeenCanceled = false; + if ( (Ed.HeadP & ED_HEAD_HALTED) + || (Ed.hwinfo & ED_HWINFO_SKIP) + || cFmAge < 0 + || (fHasBeenCanceled = ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, &Ed)) + || !ohciR3UnlinkTds(pDevIns, pThis, pUrb, &Ed) + ) + { + Log(("%s: ohciR3RhXferCompletion: DROPPED {ED=%#010x cTds=%d TD0=%#010x age %d} because:%s%s%s%s%s!!!\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge, + (Ed.HeadP & ED_HEAD_HALTED) ? " ep halted" : "", + (Ed.hwinfo & ED_HWINFO_SKIP) ? " ep skip" : "", + (Ed.HeadP & ED_PTR_MASK) != pUrb->paTds[0].TdAddr ? " ep head-changed" : "", + cFmAge < 0 ? " td not-in-flight" : "", + fHasBeenCanceled ? " td canceled" : "")); + NOREF(fHasBeenCanceled); + STAM_COUNTER_INC(&pThis->StatDroppedUrbs); + ohciR3Unlock(pThisCC); + return; + } + + /* + * Complete the TD updating and write the back. + * When appropriate also copy data back to the guest memory. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + ohciR3RhXferCompleteIsochronousURB(pDevIns, pThis, pThisCC, pUrb /*, &Ed , cFmAge*/); + else + ohciR3RhXferCompleteGeneralURB(pDevIns, pThis, pThisCC, pUrb, &Ed, cFmAge); + + /* finally write back the endpoint descriptor. */ + ohciR3WriteEd(pDevIns, pUrb->pHci->EdAddr, &Ed); + } + + ohciR3Unlock(pThisCC); +} + + +/** + * Handle transfer errors. + * + * VUSB calls this when a transfer attempt failed. This function will respond + * indicating whether to retry or complete the URB with failure. + * + * @returns true if the URB should be retired. + * @returns false if the URB should be retried. + * @param pInterface Pointer to OHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + /* + * Isochronous URBs can't be retried. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + return true; + + /* + * Don't retry on stall. + */ + if (pUrb->enmStatus == VUSBSTATUS_STALL) + { + Log2(("%s: ohciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc)); + return true; + } + + ohciR3Lock(pThisCC); + bool fRetire = false; + /* + * Check if the TDs still are valid. + * This will make sure the TdCopy is up to date. + */ + const uint32_t TdAddr = pUrb->paTds[0].TdAddr; +/** @todo IMPORTANT! we must check if the ED is still valid at this point!!! */ + if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, NULL)) + { + Log(("%s: ohciR3RhXferError: TdAddr0=%#x canceled!\n", pUrb->pszDesc, TdAddr)); + fRetire = true; + } + else + { + /* + * Get and update the error counter. + */ + POHCITD pTd = (POHCITD)&pUrb->paTds[0].TdCopy[0]; + unsigned cErrs = (pTd->hwinfo & TD_HWINFO_ERRORS) >> TD_ERRORS_SHIFT; + pTd->hwinfo &= ~TD_HWINFO_ERRORS; + cErrs++; + pTd->hwinfo |= (cErrs % TD_ERRORS_MAX) << TD_ERRORS_SHIFT; + ohciR3WriteTd(pDevIns, TdAddr, pTd, "ohciR3RhXferError"); + + if (cErrs >= TD_ERRORS_MAX - 1) + { + Log2(("%s: ohciR3RhXferError: too many errors, giving up!\n", pUrb->pszDesc)); + fRetire = true; + } + else + Log2(("%s: ohciR3RhXferError: cErrs=%d: retrying...\n", pUrb->pszDesc, cErrs)); + } + + ohciR3Unlock(pThisCC); + return fRetire; +} + + +/** + * Service a general transport descriptor. + */ +static bool ohciR3ServiceTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType, + PCOHCIED pEd, uint32_t EdAddr, uint32_t TdAddr, uint32_t *pNextTdAddr, const char *pszListName) +{ + RT_NOREF(pszListName); + + /* + * Read the TD and setup the buffer data. + */ + OHCITD Td; + ohciR3ReadTd(pDevIns, TdAddr, &Td); + OHCIBUF Buf; + ohciR3BufInit(&Buf, Td.cbp, Td.be); + + *pNextTdAddr = Td.NextTD & ED_PTR_MASK; + + /* + * Determine the direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + switch (Td.hwinfo & TD_HWINFO_DIR) + { + case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + case 0: enmDir = VUSBDIRECTION_SETUP; break; + default: + Log(("ohciR3ServiceTd: Invalid direction!!!! Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Td.hwinfo, pEd->hwinfo)); + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 2); + return false; + } + break; + } + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, + enmType, enmDir, Buf.cbTotal, 1, NULL); + if (!pUrb) + return false; /* retry later... */ + + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->fShortNotOk = !(Td.hwinfo & TD_HWINFO_ROUNDING); + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->cTds = 1; + pUrb->paTds[0].TdAddr = TdAddr; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(Td)); + memcpy(pUrb->paTds[0].TdCopy, &Td, sizeof(Td)); + + /* copy data if out bound transfer. */ + pUrb->cbData = Buf.cbTotal; + if ( Buf.cbTotal + && Buf.cVecs > 0 + && enmDir != VUSBDIRECTION_IN) + { + /* Be paranoid. */ + if ( Buf.aVecs[0].cb > pUrb->cbData + || ( Buf.cVecs > 1 + && Buf.aVecs[1].cb > (pUrb->cbData - Buf.aVecs[0].cb))) + { + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 3); + VUSBIRhFreeUrb(pThisCC->RootHub.pIRhConn, pUrb); + return false; + } + + ohciR3PhysRead(pDevIns, Buf.aVecs[0].Addr, pUrb->abData, Buf.aVecs[0].cb); + if (Buf.cVecs > 1) + ohciR3PhysRead(pDevIns, Buf.aVecs[1].Addr, &pUrb->abData[Buf.aVecs[0].cb], Buf.aVecs[1].cb); + } + + /* + * Submit the URB. + */ + ohciR3InFlightAdd(pThis, pThisCC, TdAddr, pUrb); + Log(("%s: ohciR3ServiceTd: submitting TdAddr=%#010x EdAddr=%#010x cbData=%#x\n", + pUrb->pszDesc, TdAddr, EdAddr, pUrb->cbData)); + + ohciR3Unlock(pThisCC); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + ohciR3Lock(pThisCC); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceTd: failed submitting TdAddr=%#010x EdAddr=%#010x pUrb=%p!!\n", + TdAddr, EdAddr, pUrb)); + ohciR3InFlightRemove(pThis, pThisCC, TdAddr); + return false; +} + + +/** + * Service a the head TD of an endpoint. + */ +static bool ohciR3ServiceHeadTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType, + PCOHCIED pEd, uint32_t EdAddr, const char *pszListName) +{ + /* + * Read the TD, after first checking if it's already in-flight. + */ + uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThisCC, TdAddr)) + return false; +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueCheck(pThisCC, TdAddr); +# endif + return ohciR3ServiceTd(pDevIns, pThis, pThisCC, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName); +} + + +/** + * Service one or more general transport descriptors (bulk or interrupt). + */ +static bool ohciR3ServiceTdMultiple(PPDMDEVINS pDevIns, POHCI pThis, VUSBXFERTYPE enmType, PCOHCIED pEd, uint32_t EdAddr, + uint32_t TdAddr, uint32_t *pNextTdAddr, const char *pszListName) +{ + RT_NOREF(pszListName); + + /* + * Read the TDs involved in this URB. + */ + struct OHCITDENTRY + { + /** The TD. */ + OHCITD Td; + /** The associated OHCI buffer tracker. */ + OHCIBUF Buf; + /** The TD address. */ + uint32_t TdAddr; + /** Pointer to the next element in the chain (stack). */ + struct OHCITDENTRY *pNext; + } Head; + + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif + + /* read the head */ + ohciR3ReadTd(pDevIns, TdAddr, &Head.Td); + ohciR3BufInit(&Head.Buf, Head.Td.cbp, Head.Td.be); + Head.TdAddr = TdAddr; + Head.pNext = NULL; + + /* combine with more TDs. */ + struct OHCITDENTRY *pTail = &Head; + unsigned cbTotal = pTail->Buf.cbTotal; + unsigned cTds = 1; + while ( (pTail->Buf.cbTotal == 0x1000 || pTail->Buf.cbTotal == 0x2000) + && !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING) /* This isn't right for *BSD, but let's not . */ + && (pTail->Td.NextTD & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && cTds < 128) + { + struct OHCITDENTRY *pCur = (struct OHCITDENTRY *)alloca(sizeof(*pCur)); + + pCur->pNext = NULL; + pCur->TdAddr = pTail->Td.NextTD & ED_PTR_MASK; + ohciR3ReadTd(pDevIns, pCur->TdAddr, &pCur->Td); + ohciR3BufInit(&pCur->Buf, pCur->Td.cbp, pCur->Td.be); + + /* Don't combine if the direction doesn't match up. There can't actually be + * a mismatch for bulk/interrupt EPs unless the guest is buggy. + */ + if ( (pCur->Td.hwinfo & (TD_HWINFO_DIR)) + != (Head.Td.hwinfo & (TD_HWINFO_DIR))) + break; + + pTail->pNext = pCur; + pTail = pCur; + cbTotal += pCur->Buf.cbTotal; + cTds++; + } + + /* calc next TD address */ + *pNextTdAddr = pTail->Td.NextTD & ED_PTR_MASK; + + /* + * Determine the direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceTdMultiple: WARNING! Ed.hwdinfo=%#x bulk or interrupt EP shouldn't rely on the TD for direction...\n", pEd->hwinfo)); + switch (Head.Td.hwinfo & TD_HWINFO_DIR) + { + case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceTdMultiple: Invalid direction!!!! Head.Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Head.Td.hwinfo, pEd->hwinfo)); + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 4); + return false; + } + break; + } + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, + enmType, enmDir, cbTotal, cTds, "ohciR3ServiceTdMultiple"); + if (!pUrb) + /* retry later... */ + return false; + Assert(pUrb->cbData == cbTotal); + + pUrb->enmType = enmType; + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->enmDir = enmDir; + pUrb->fShortNotOk = !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING); + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->cTds = cTds; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + + /* Copy data and TD information. */ + unsigned iTd = 0; + uint8_t *pb = &pUrb->abData[0]; + for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++) + { + /* data */ + if ( cbTotal + && enmDir != VUSBDIRECTION_IN + && pCur->Buf.cVecs > 0) + { + ohciR3PhysRead(pDevIns, pCur->Buf.aVecs[0].Addr, pb, pCur->Buf.aVecs[0].cb); + if (pCur->Buf.cVecs > 1) + ohciR3PhysRead(pDevIns, pCur->Buf.aVecs[1].Addr, pb + pCur->Buf.aVecs[0].cb, pCur->Buf.aVecs[1].cb); + } + pb += pCur->Buf.cbTotal; + + /* TD info */ + pUrb->paTds[iTd].TdAddr = pCur->TdAddr; + AssertCompile(sizeof(pUrb->paTds[iTd].TdCopy) >= sizeof(pCur->Td)); + memcpy(pUrb->paTds[iTd].TdCopy, &pCur->Td, sizeof(pCur->Td)); + } + + /* + * Submit the URB. + */ + ohciR3InFlightAddUrb(pThis, pThisCC, pUrb); + Log(("%s: ohciR3ServiceTdMultiple: submitting cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x\n", + pUrb->pszDesc, pUrb->cbData, EdAddr, cTds, TdAddr)); + ohciR3Unlock(pThisCC); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + ohciR3Lock(pThisCC); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceTdMultiple: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x - rc=%Rrc\n", + pUrb, cbTotal, EdAddr, cTds, TdAddr, rc)); + /* NB: We cannot call ohciR3InFlightRemoveUrb() because the URB is already gone! */ + for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++) + ohciR3InFlightRemove(pThis, pThisCC, pCur->TdAddr); + return false; +} + + +/** + * Service the head TD of an endpoint. + */ +static bool ohciR3ServiceHeadTdMultiple(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType, + PCOHCIED pEd, uint32_t EdAddr, const char *pszListName) +{ + /* + * First, check that it's not already in-flight. + */ + uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThisCC, TdAddr)) + return false; +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueCheck(pThisCC, TdAddr); +# endif + return ohciR3ServiceTdMultiple(pDevIns, pThis, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName); +} + + +/** + * A worker for ohciR3ServiceIsochronousEndpoint which unlinks a ITD + * that belongs to the past. + */ +static bool ohciR3ServiceIsochronousTdUnlink(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, POHCIITD pITd, uint32_t ITdAddr, + uint32_t ITdAddrPrev, PVUSBURB pUrb, POHCIED pEd, uint32_t EdAddr) +{ + LogFlow(("%s%sohciR3ServiceIsochronousTdUnlink: Unlinking ITD: ITdAddr=%#010x EdAddr=%#010x ITdAddrPrev=%#010x\n", + pUrb ? pUrb->pszDesc : "", pUrb ? ": " : "", ITdAddr, EdAddr, ITdAddrPrev)); + + /* + * Do the unlinking. + */ + const uint32_t ITdAddrNext = pITd->NextTD & ED_PTR_MASK; + if (ITdAddrPrev) + { + /* Get validate the previous TD */ + int iInFlightPrev = ohciR3InFlightFind(pThisCC, ITdAddrPrev); + AssertMsgReturn(iInFlightPrev >= 0, ("ITdAddr=%#RX32\n", ITdAddrPrev), false); + PVUSBURB pUrbPrev = pThisCC->aInFlight[iInFlightPrev].pUrb; + if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrbPrev, pEd)) /* ensures the copy is correct. */ + return false; + + /* Update the copy and write it back. */ + POHCIITD pITdPrev = ((POHCIITD)pUrbPrev->paTds[0].TdCopy); + pITdPrev->NextTD = (pITdPrev->NextTD & ~ED_PTR_MASK) | ITdAddrNext; + ohciR3WriteITd(pDevIns, pThis, ITdAddrPrev, pITdPrev, "ohciR3ServiceIsochronousEndpoint"); + } + else + { + /* It's the head node. update the copy from the caller and write it back. */ + pEd->HeadP = (pEd->HeadP & ~ED_PTR_MASK) | ITdAddrNext; + ohciR3WriteEd(pDevIns, EdAddr, pEd); + } + + /* + * If it's in flight, just mark the URB as unlinked (there is only one ITD per URB atm). + * Otherwise, retire it to the done queue with an error and cause a done line interrupt (?). + */ + if (pUrb) + { + pUrb->pHci->fUnlinked = true; + if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, pEd)) /* ensures the copy is correct (paranoia). */ + return false; + + POHCIITD pITdCopy = ((POHCIITD)pUrb->paTds[0].TdCopy); + pITd->NextTD = pITdCopy->NextTD &= ~ED_PTR_MASK; + } + else + { + pITd->HwInfo &= ~ITD_HWINFO_CC; + pITd->HwInfo |= OHCI_CC_DATA_OVERRUN; + + pITd->NextTD = pThis->done; + pThis->done = ITdAddr; + + pThis->dqic = 0; + } + + ohciR3WriteITd(pDevIns, pThis, ITdAddr, pITd, "ohciR3ServiceIsochronousTdUnlink"); + return true; +} + + +/** + * A worker for ohciR3ServiceIsochronousEndpoint which submits the specified TD. + * + * @returns true on success. + * @returns false on failure to submit. + * @param pDevIns The device instance. + * @param pThis The OHCI controller instance data, shared edition. + * @param pThisCC The OHCI controller instance data, ring-3 edition. + * @param pITd The transfer descriptor to service. + * @param ITdAddr The address of the transfer descriptor in gues memory. + * @param R The start packet (frame) relative to the start of frame in HwInfo. + * @param pEd The OHCI endpoint descriptor. + * @param EdAddr The endpoint descriptor address in guest memory. + */ +static bool ohciR3ServiceIsochronousTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, + POHCIITD pITd, uint32_t ITdAddr, const unsigned R, PCOHCIED pEd, uint32_t EdAddr) +{ + /* + * Determine the endpoint direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceIsochronousTd: Invalid direction!!!! Ed.hwdinfo=%#x\n", pEd->hwinfo)); + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 5); + return false; + } + + /* + * Extract the packet sizes and calc the total URB size. + */ + struct + { + uint16_t cb; + uint16_t off; + } aPkts[ITD_NUM_PSW]; + + /* first entry (R) */ + uint32_t cbTotal = 0; + if (((uint32_t)pITd->aPSW[R] >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT)) + { + Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, R, pITd->aPSW[R] >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/ + pThis->intr_status |= OHCI_INTR_UNRECOVERABLE_ERROR; + return false; + } + uint16_t offPrev = aPkts[0].off = (pITd->aPSW[R] & ITD_PSW_OFFSET); + + /* R+1..cFrames */ + const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + for (unsigned iR = R + 1; iR < cFrames; iR++) + { + const uint16_t PSW = pITd->aPSW[iR]; + const uint16_t off = aPkts[iR - R].off = (PSW & ITD_PSW_OFFSET); + cbTotal += aPkts[iR - R - 1].cb = off - offPrev; + if (off < offPrev) + { + Log(("ITdAddr=%RX32 PSW%d.offset=%#x < offPrev=%#x!\n", ITdAddr, iR, off, offPrev)); /* => Unrecoverable Error*/ + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 6); + return false; + } + if (((uint32_t)PSW >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT)) + { + Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, iR, PSW >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/ + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 7); + return false; + } + offPrev = off; + } + + /* calc offEnd and figure out the size of the last packet. */ + const uint32_t offEnd = (pITd->BE & 0xfff) + + (((pITd->BE & ITD_BP0_MASK) != (pITd->BP0 & ITD_BP0_MASK)) << 12) + + 1 /* BE is inclusive */; + if (offEnd < offPrev) + { + Log(("ITdAddr=%RX32 offEnd=%#x < offPrev=%#x!\n", ITdAddr, offEnd, offPrev)); /* => Unrecoverable Error*/ + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 8); + return false; + } + cbTotal += aPkts[cFrames - 1 - R].cb = offEnd - offPrev; + Assert(cbTotal <= 0x2000); + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, + VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL); + if (!pUrb) + /* retry later... */ + return false; + + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->fShortNotOk = false; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->cTds = 1; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + pUrb->paTds[0].TdAddr = ITdAddr; + AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pITd)); + memcpy(pUrb->paTds[0].TdCopy, pITd, sizeof(*pITd)); +# if 0 /* color the data */ + memset(pUrb->abData, 0xfe, cbTotal); +# endif + + /* copy the data */ + if ( cbTotal + && enmDir != VUSBDIRECTION_IN) + { + const uint32_t off0 = pITd->aPSW[R] & ITD_PSW_OFFSET; + if (off0 < 0x1000) + { + if (offEnd > 0x1000) + { + /* both pages. */ + const unsigned cb0 = 0x1000 - off0; + ohciR3PhysRead(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off0, &pUrb->abData[0], cb0); + ohciR3PhysRead(pDevIns, pITd->BE & ITD_BP0_MASK, &pUrb->abData[cb0], offEnd & 0xfff); + } + else /* a portion of the 1st page. */ + ohciR3PhysRead(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off0, pUrb->abData, offEnd - off0); + } + else /* a portion of the 2nd page. */ + ohciR3PhysRead(pDevIns, (pITd->BE & UINT32_C(0xfffff000)) + (off0 & 0xfff), pUrb->abData, cbTotal); + } + + /* setup the packets */ + pUrb->cIsocPkts = cFrames - R; + unsigned off = 0; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED; + pUrb->aIsocPkts[i].off = off; + off += pUrb->aIsocPkts[i].cb = aPkts[i].cb; + } + Assert(off == cbTotal); + + /* + * Submit the URB. + */ + ohciR3InFlightAdd(pThis, pThisCC, ITdAddr, pUrb); + Log(("%s: ohciR3ServiceIsochronousTd: submitting cbData=%#x cIsocPkts=%d EdAddr=%#010x TdAddr=%#010x SF=%#x (%#x)\n", + pUrb->pszDesc, pUrb->cbData, pUrb->cIsocPkts, EdAddr, ITdAddr, pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber)); + ohciR3Unlock(pThisCC); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + ohciR3Lock(pThisCC); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceIsochronousTd: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d ITdAddr0=%#010x - rc=%Rrc\n", + pUrb, cbTotal, EdAddr, 1, ITdAddr, rc)); + ohciR3InFlightRemove(pThis, pThisCC, ITdAddr); + return false; +} + + +/** + * Service an isochronous endpoint. + */ +static void ohciR3ServiceIsochronousEndpoint(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, POHCIED pEd, uint32_t EdAddr) +{ + /* + * We currently process this as if the guest follows the interrupt end point chaining + * hierarchy described in the documenation. This means that for an isochronous endpoint + * with a 1 ms interval we expect to find in-flight TDs at the head of the list. We will + * skip over all in-flight TDs which timeframe has been exceed. Those which aren't in + * flight but which are too late will be retired (possibly out of order, but, we don't + * care right now). + * + * When we reach a TD which still has a buffer which is due for take off, we will + * stop iterating TDs. If it's in-flight, there isn't anything to be done. Otherwise + * we will push it onto the runway for immediate take off. In this process we + * might have to complete buffers which didn't make it on time, something which + * complicates the kind of status info we need to keep around for the TD. + * + * Note: We're currently not making any attempt at reassembling ITDs into URBs. + * However, this will become necessary because of EMT scheduling and guest + * like linux using one TD for each frame (simple but inefficient for us). + */ + OHCIITD ITd; + uint32_t ITdAddr = pEd->HeadP & ED_PTR_MASK; + uint32_t ITdAddrPrev = 0; + uint32_t u32NextFrame = UINT32_MAX; + const uint16_t u16CurFrame = pThis->HcFmNumber; + for (;;) + { + /* check for end-of-chain. */ + if ( ITdAddr == (pEd->TailP & ED_PTR_MASK) + || !ITdAddr) + break; + + /* + * If isochronous endpoints are around, don't slow down the timer. Getting the timing right + * is difficult enough as it is. + */ + pThis->fIdle = false; + + /* + * Read the current ITD and check what we're supposed to do about it. + */ + ohciR3ReadITd(pDevIns, pThis, ITdAddr, &ITd); + const uint32_t ITdAddrNext = ITd.NextTD & ED_PTR_MASK; + const int16_t R = u16CurFrame - (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF); /* 4.3.2.3 */ + const int16_t cFrames = ((ITd.HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + + if (R < cFrames) + { + /* + * It's inside the current or a future launch window. + * + * We will try maximize the TD in flight here to deal with EMT scheduling + * issues and similar stuff which will screw up the time. So, we will only + * stop submitting TD when we reach a gap (in time) or end of the list. + */ + if ( R < 0 /* (a future frame) */ + && (uint16_t)u32NextFrame != (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF)) + break; + if (ohciR3InFlightFind(pThisCC, ITdAddr) < 0) + if (!ohciR3ServiceIsochronousTd(pDevIns, pThis, pThisCC, &ITd, ITdAddr, R < 0 ? 0 : R, pEd, EdAddr)) + break; + + ITdAddrPrev = ITdAddr; + } + else + { +# if 1 + /* + * Ok, the launch window for this TD has passed. + * If it's not in flight it should be retired with a DataOverrun status (TD). + * + * Don't remove in-flight TDs before they complete. + * Windows will, upon the completion of another ITD it seems, check for if + * any other TDs has been unlinked. If we unlink them before they really + * complete all the packet status codes will be NotAccessed and Windows + * will fail the URB with status USBD_STATUS_ISOCH_REQUEST_FAILED. + * + * I don't know if unlinking TDs out of order could cause similar problems, + * time will show. + */ + int iInFlight = ohciR3InFlightFind(pThisCC, ITdAddr); + if (iInFlight >= 0) + ITdAddrPrev = ITdAddr; + else if (!ohciR3ServiceIsochronousTdUnlink(pDevIns, pThis, pThisCC, &ITd, ITdAddr, ITdAddrPrev, NULL, pEd, EdAddr)) + { + Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n")); + break; + } +# else /* BAD IDEA: */ + /* + * Ok, the launch window for this TD has passed. + * If it's not in flight it should be retired with a DataOverrun status (TD). + * + * If it's in flight we will try unlink it from the list prematurely to + * help the guest to move on and shorten the list we have to walk. We currently + * are successful with the first URB but then it goes too slowly... + */ + int iInFlight = ohciR3InFlightFind(pThis, ITdAddr); + if (!ohciR3ServiceIsochronousTdUnlink(pThis, &ITd, ITdAddr, ITdAddrPrev, + iInFlight < 0 ? NULL : pThis->aInFlight[iInFlight].pUrb, + pEd, EdAddr)) + { + Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n")); + break; + } +# endif + } + + /* advance to the next ITD */ + ITdAddr = ITdAddrNext; + u32NextFrame = (ITd.HwInfo & ITD_HWINFO_SF) + cFrames; + } +} + + +/** + * Checks if a endpoints has TDs queued and is ready to have them processed. + * + * @returns true if it's ok to process TDs. + * @param pEd The endpoint data. + */ +DECLINLINE(bool) ohciR3IsEdReady(PCOHCIED pEd) +{ + return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && !(pEd->HeadP & ED_HEAD_HALTED) + && !(pEd->hwinfo & ED_HWINFO_SKIP); +} + + +/** + * Checks if an endpoint has TDs queued (not necessarily ready to have them processed). + * + * @returns true if endpoint may have TDs queued. + * @param pEd The endpoint data. + */ +DECLINLINE(bool) ohciR3IsEdPresent(PCOHCIED pEd) +{ + return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && !(pEd->HeadP & ED_HEAD_HALTED); +} + + +/** + * Services the bulk list. + * + * On the bulk list we must reassemble URBs from multiple TDs using heuristics + * derived from USB tracing done in the guests and guest source code (when available). + */ +static void ohciR3ServiceBulkList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk before", true); + if (pThis->bulk_cur) + Log(("ohciR3ServiceBulkList: bulk_cur=%#010x before listprocessing!!! HCD have positioned us!!!\n", pThis->bulk_cur)); +# endif + + /* + * ", HC will start processing the Bulk list and will set BF [BulkListFilled] to 0" + * - We've simplified and are always starting at the head of the list and working + * our way thru to the end each time. + */ + pThis->status &= ~OHCI_STATUS_BLF; + pThis->fBulkNeedsCleaning = false; + pThis->bulk_cur = 0; + + uint32_t EdAddr = pThis->bulk_head; + uint32_t cIterations = 256; + while (EdAddr + && (pThis->ctl & OHCI_CTL_BLE) + && (cIterations-- > 0)) + { + OHCIED Ed; + + /* Bail if previous processing ended up in the unrecoverable error state. */ + if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR) + break; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdReady(&Ed)) + { + pThis->status |= OHCI_STATUS_BLF; + pThis->fBulkNeedsCleaning = true; + +# if 1 + /* + + * After we figured out that all the TDs submitted for dealing with MSD + * read/write data really makes up on single URB, and that we must + * reassemble these TDs into an URB before submitting it, there is no + * longer any need for servicing anything other than the head *URB* + * on a bulk endpoint. + */ + ohciR3ServiceHeadTdMultiple(pDevIns, pThis, pThisCC, VUSBXFERTYPE_BULK, &Ed, EdAddr, "Bulk"); +# else + /* + * This alternative code was used before we started reassembling URBs from + * multiple TDs. We keep it handy for debugging. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + if (!ohciR3IsTdInFlight(pThis, TdAddr)) + { + do + { + if (!ohciR3ServiceTdMultiple(pThis, VUSBXFERTYPE_BULK, &Ed, EdAddr, TdAddr, &TdAddr, "Bulk")) + { + LogFlow(("ohciR3ServiceBulkList: ohciR3ServiceTdMultiple -> false\n")); + break; + } + if ( (TdAddr & ED_PTR_MASK) == (Ed.TailP & ED_PTR_MASK) + || !TdAddr /* paranoia */) + { + LogFlow(("ohciR3ServiceBulkList: TdAddr=%#010RX32 Ed.TailP=%#010RX32\n", TdAddr, Ed.TailP)); + break; + } + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); /* It might have been updated on URB completion. */ + } while (ohciR3IsEdReady(&Ed)); + } +# endif + } + else + { + if (Ed.hwinfo & ED_HWINFO_SKIP) + { + LogFlow(("ohciR3ServiceBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP)); + /* If the ED is in 'skip' state, no transactions on it are allowed and we must + * cancel outstanding URBs, if any. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr); + if (pUrb) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk after ", true); +# endif +} + + +/** + * Abort outstanding transfers on the bulk list. + * + * If the guest disabled bulk list processing, we must abort any outstanding transfers + * (that is, cancel in-flight URBs associated with the list). This is required because + * there may be outstanding read URBs that will never get a response from the device + * and would block further communication. + */ +static void ohciR3UndoBulkList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk before", true); + if (pThis->bulk_cur) + Log(("ohciR3UndoBulkList: bulk_cur=%#010x before list processing!!! HCD has positioned us!!!\n", pThis->bulk_cur)); +# endif + + /* This flag follows OHCI_STATUS_BLF, but BLF doesn't change when list processing is disabled. */ + pThis->fBulkNeedsCleaning = false; + + uint32_t EdAddr = pThis->bulk_head; + uint32_t cIterations = 256; + while (EdAddr + && (cIterations-- > 0)) + { + OHCIED Ed; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdPresent(&Ed)) + { + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThisCC, TdAddr)) + { + LogFlow(("ohciR3UndoBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 UNDO\n", EdAddr, Ed.TailP)); + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr); + if (pUrb) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } +} + + +/** + * Services the control list. + * + * The control list has complex URB assembling, but that's taken + * care of at VUSB level (unlike the other transfer types). + */ +static void ohciR3ServiceCtrlList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + if (g_fLogControlEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->ctrl_head, "Ctrl before", true); + if (pThis->ctrl_cur) + Log(("ohciR3ServiceCtrlList: ctrl_cur=%010x before list processing!!! HCD have positioned us!!!\n", pThis->ctrl_cur)); +# endif + + /* + * ", HC will start processing the list and will set ControlListFilled to 0" + * - We've simplified and are always starting at the head of the list and working + * our way thru to the end each time. + */ + pThis->status &= ~OHCI_STATUS_CLF; + pThis->ctrl_cur = 0; + + uint32_t EdAddr = pThis->ctrl_head; + uint32_t cIterations = 256; + while ( EdAddr + && (pThis->ctl & OHCI_CTL_CLE) + && (cIterations-- > 0)) + { + OHCIED Ed; + + /* Bail if previous processing ended up in the unrecoverable error state. */ + if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR) + break; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdReady(&Ed)) + { +# if 1 + /* + * Control TDs depends on order and stage. Only one can be in-flight + * at any given time. OTOH, some stages are completed immediately, + * so we process the list until we've got a head which is in-flight + * or reach the end of the list. + */ + do + { + if ( !ohciR3ServiceHeadTd(pDevIns, pThis, pThisCC, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control") + || ohciR3IsTdInFlight(pThisCC, Ed.HeadP & ED_PTR_MASK)) + { + pThis->status |= OHCI_STATUS_CLF; + break; + } + ohciR3ReadEd(pDevIns, EdAddr, &Ed); /* It might have been updated on URB completion. */ + } while (ohciR3IsEdReady(&Ed)); +# else + /* Simplistic, for debugging. */ + ohciR3ServiceHeadTd(pThis, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control"); + pThis->status |= OHCI_STATUS_CLF; +# endif + } + + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogControlEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->ctrl_head, "Ctrl after ", true); +# endif +} + + +/** + * Services the periodic list. + * + * On the interrupt portion of the periodic list we must reassemble URBs from multiple + * TDs using heuristics derived from USB tracing done in the guests and guest source + * code (when available). + */ +static void ohciR3ServicePeriodicList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + /* + * Read the list head from the HCCA. + */ + const unsigned iList = pThis->HcFmNumber % OHCI_HCCA_NUM_INTR; + uint32_t EdAddr; + ohciR3GetDWords(pDevIns, pThis->hcca + iList * sizeof(EdAddr), &EdAddr, 1); + +# ifdef LOG_ENABLED + const uint32_t EdAddrHead = EdAddr; + if (g_fLogInterruptEPs) + { + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x before", iList); + ohciR3DumpEdList(pDevIns, pThisCC, EdAddrHead, sz, true); + } +# endif + + /* + * Iterate the endpoint list. + */ + unsigned cIterations = 128; + while (EdAddr + && (pThis->ctl & OHCI_CTL_PLE) + && (cIterations-- > 0)) + { + OHCIED Ed; + + /* Bail if previous processing ended up in the unrecoverable error state. */ + if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR) + break; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + if (ohciR3IsEdReady(&Ed)) + { + /* + * "There is no separate head pointer of isochronous transfers. The first + * isochronous Endpoint Descriptor simply links to the last interrupt + * Endpoint Descriptor." + */ + if (!(Ed.hwinfo & ED_HWINFO_ISO)) + { + /* + * Presently we will only process the head URB on an interrupt endpoint. + */ + ohciR3ServiceHeadTdMultiple(pDevIns, pThis, pThisCC, VUSBXFERTYPE_INTR, &Ed, EdAddr, "Periodic"); + } + else if (pThis->ctl & OHCI_CTL_IE) + { + /* + * Presently only the head ITD. + */ + ohciR3ServiceIsochronousEndpoint(pDevIns, pThis, pThisCC, &Ed, EdAddr); + } + else + break; + } + else + { + if (Ed.hwinfo & ED_HWINFO_SKIP) + { + Log3(("ohciR3ServicePeriodicList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP)); + /* If the ED is in 'skip' state, no transactions on it are allowed and we must + * cancel outstanding URBs, if any. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr); + if (pUrb) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogInterruptEPs) + { + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iList); + ohciR3DumpEdList(pDevIns, pThisCC, EdAddrHead, sz, true); + } +# endif +} + + +/** + * Update the HCCA. + * + * @param pDevIns The device instance. + * @param pThis The OHCI controller instance data, shared edition. + * @param pThisCC The OHCI controller instance data, ring-3 edition. + */ +static void ohciR3UpdateHCCA(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + OCHIHCCA hcca; + ohciR3PhysRead(pDevIns, pThis->hcca + OHCI_HCCA_OFS, &hcca, sizeof(hcca)); + + hcca.frame = RT_H2LE_U16((uint16_t)pThis->HcFmNumber); + hcca.pad = 0; + + bool fWriteDoneHeadInterrupt = false; + if ( pThis->dqic == 0 + && (pThis->intr_status & OHCI_INTR_WRITE_DONE_HEAD) == 0) + { + uint32_t done = pThis->done; + + if (pThis->intr_status & ~( OHCI_INTR_MASTER_INTERRUPT_ENABLED | OHCI_INTR_OWNERSHIP_CHANGE + | OHCI_INTR_WRITE_DONE_HEAD) ) + done |= 0x1; + + hcca.done = RT_H2LE_U32(done); + pThis->done = 0; + pThis->dqic = 0x7; + + Log(("ohci: Writeback Done (%#010x) on frame %#x (age %#x)\n", hcca.done, + pThis->HcFmNumber, pThis->HcFmNumber - pThisCC->u32FmDoneQueueTail)); +# ifdef LOG_ENABLED + ohciR3DumpTdQueue(pDevIns, pThisCC, hcca.done & ED_PTR_MASK, "DoneQueue"); +# endif + Assert(RT_OFFSETOF(OCHIHCCA, done) == 4); +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueZap(pThisCC); +# endif + fWriteDoneHeadInterrupt = true; + } + + Log3(("ohci: Updating HCCA on frame %#x\n", pThis->HcFmNumber)); + ohciR3PhysWriteMeta(pDevIns, pThis->hcca + OHCI_HCCA_OFS, (uint8_t *)&hcca, sizeof(hcca)); + if (fWriteDoneHeadInterrupt) + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_WRITE_DONE_HEAD); + RT_NOREF(pThisCC); +} + + +/** + * Go over the in-flight URB list and cancel any URBs that are no longer in use. + * This occurs when the host removes EDs or TDs from the lists and we don't notice + * the sKip bit. Such URBs must be promptly canceled, otherwise there is a risk + * they might "steal" data destined for another URB. + */ +static void ohciR3CancelOrphanedURBs(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK + || pThis->hcca < ~OHCI_HCCA_MASK); + unsigned i, cLeft; + int j; + uint32_t EdAddr; + PVUSBURB pUrb; + + /* If the HCCA is not currently valid, or there are no in-flight URBs, + * there's nothing to do. + */ + if (!fValidHCCA || !pThisCC->cInFlight) + return; + + /* Initially mark all in-flight URBs as inactive. */ + for (i = 0, cLeft = pThisCC->cInFlight; cLeft && i < RT_ELEMENTS(pThisCC->aInFlight); i++) + { + if (pThisCC->aInFlight[i].pUrb) + { + pThisCC->aInFlight[i].fInactive = true; + cLeft--; + } + } + Assert(cLeft == 0); + +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* Get hcca data to minimize calls to ohciR3GetDWords/PDMDevHlpPCIPhysRead. */ + uint32_t au32HCCA[OHCI_HCCA_NUM_INTR]; + ohciR3GetDWords(pDevIns, pThis->hcca, au32HCCA, OHCI_HCCA_NUM_INTR); +# endif + + /* Go over all bulk/control/interrupt endpoint lists; any URB found in these lists + * is marked as active again. + */ + for (i = 0; i < OHCI_HCCA_NUM_INTR + 2; i++) + { + switch (i) + { + case OHCI_HCCA_NUM_INTR: + EdAddr = pThis->bulk_head; + break; + case OHCI_HCCA_NUM_INTR + 1: + EdAddr = pThis->ctrl_head; + break; + default: +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + EdAddr = au32HCCA[i]; +# else + ohciR3GetDWords(pDevIns, pThis->hcca + i * sizeof(EdAddr), &EdAddr, 1); +# endif + break; + } + + unsigned cIterED = 128; + while ( EdAddr + && (cIterED-- > 0)) + { + OHCIED Ed; + OHCITD Td; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + uint32_t TailP = Ed.TailP & ED_PTR_MASK; + unsigned cIterTD = 0; + if ( !(Ed.hwinfo & ED_HWINFO_SKIP) + && (TdAddr != TailP)) + { +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif + do + { + ohciR3ReadTd(pDevIns, TdAddr, &Td); + j = ohciR3InFlightFind(pThisCC, TdAddr); + if (j > -1) + pThisCC->aInFlight[j].fInactive = false; + TdAddr = Td.NextTD & ED_PTR_MASK; + /* See #8125. + * Sometimes the ED is changed by the guest between ohciR3ReadEd above and here. + * Then the code reads TD pointed by the new TailP, which is not allowed. + * Luckily Windows guests have Td.NextTD = 0 in the tail TD. + * Also having a real TD at 0 is very unlikely. + * So do not continue. + */ + if (TdAddr == 0) + break; + /* Failsafe for temporarily looped lists. */ + if (++cIterTD == 128) + break; + } while (TdAddr != (Ed.TailP & ED_PTR_MASK)); + } + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + } + + /* In-flight URBs still marked as inactive are not used anymore and need + * to be canceled. + */ + for (i = 0, cLeft = pThisCC->cInFlight; cLeft && i < RT_ELEMENTS(pThisCC->aInFlight); i++) + { + if (pThisCC->aInFlight[i].pUrb) + { + cLeft--; + pUrb = pThisCC->aInFlight[i].pUrb; + if ( pThisCC->aInFlight[i].fInactive + && pUrb->enmState == VUSBURBSTATE_IN_FLIGHT + && pUrb->enmType != VUSBXFERTYPE_CTRL) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + Assert(cLeft == 0); +} + + +/** + * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame. + */ +static void ohciR3StartOfFrame(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + const uint32_t status_old = pThis->status; +# endif + + /* + * Update HcFmRemaining.FRT and update start of frame time. + */ + pThis->frt = pThis->fit; + pThis->SofTime += pThis->cTicksPerFrame; + + /* + * Check that the HCCA address isn't bogus. Linux 2.4.x is known to start + * the bus with a hcca of 0 to work around problem with a specific controller. + */ + bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK + || pThis->hcca < ~OHCI_HCCA_MASK); + +# if 1 + /* + * Update the HCCA. + * Should be done after SOF but before HC read first ED in this frame. + */ + if (fValidHCCA) + ohciR3UpdateHCCA(pDevIns, pThis, pThisCC); +# endif + + /* "After writing to HCCA, HC will set SF in HcInterruptStatus" - guest isn't executing, so ignore the order! */ + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_START_OF_FRAME); + + if (pThis->fno) + { + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_FRAMENUMBER_OVERFLOW); + pThis->fno = 0; + } + + /* If the HCCA address is invalid, we're quitting here to avoid doing something which cannot be reported to the HCD. */ + if (!fValidHCCA) + { + Log(("ohciR3StartOfFrame: skipping hcca part because hcca=%RX32 (our 'valid' range: %RX32-%RX32)\n", + pThis->hcca, ~OHCI_HCCA_MASK, OHCI_HCCA_MASK)); + return; + } + + /* + * Periodic EPs. + */ + if (pThis->ctl & OHCI_CTL_PLE) + ohciR3ServicePeriodicList(pDevIns, pThis, pThisCC); + + /* + * Control EPs. + */ + if ( (pThis->ctl & OHCI_CTL_CLE) + && (pThis->status & OHCI_STATUS_CLF) ) + ohciR3ServiceCtrlList(pDevIns, pThis, pThisCC); + + /* + * Bulk EPs. + */ + if ( (pThis->ctl & OHCI_CTL_BLE) + && (pThis->status & OHCI_STATUS_BLF)) + ohciR3ServiceBulkList(pDevIns, pThis, pThisCC); + else if ((pThis->status & OHCI_STATUS_BLF) + && pThis->fBulkNeedsCleaning) + ohciR3UndoBulkList(pDevIns, pThis, pThisCC); /* If list disabled but not empty, abort endpoints. */ + +# if 0 + /* + * Update the HCCA after processing the lists and everything. A bit experimental. + * + * ASSUME the guest won't be very upset if a TD is completed, retired and handed + * back immediately. The idea is to be able to retire the data and/or status stages + * of a control transfer together with the setup stage, thus saving a frame. This + * behaviour is should be perfectly ok, since the setup (and maybe data) stages + * have already taken at least one frame to complete. + * + * But, when implementing the first synchronous virtual USB devices, we'll have to + * verify that the guest doesn't choke when having a TD returned in the same frame + * as it was submitted. + */ + ohciR3UpdateHCCA(pThis); +# endif + +# ifdef LOG_ENABLED + if (pThis->status ^ status_old) + { + uint32_t val = pThis->status; + uint32_t chg = val ^ status_old; NOREF(chg); + Log2(("ohciR3StartOfFrame: HcCommandStatus=%#010x: %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", val & 1, + chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & (3<<16)? "*" : "", (val >> 16) & 3)); + } +# endif +} + + +/** + * Updates the HcFmNumber and FNO registers. + */ +static void ohciR3BumpFrameNumber(POHCI pThis) +{ + const uint16_t u16OldFmNumber = pThis->HcFmNumber++; + if ((u16OldFmNumber ^ pThis->HcFmNumber) & RT_BIT(15)) + pThis->fno = 1; +} + + +/** + * Callback for periodic frame processing. + */ +static DECLCALLBACK(bool) ohciR3StartFrame(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameNo) +{ + RT_NOREF(u32FrameNo); + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + ohciR3Lock(pThisCC); + + /* Reset idle detection flag */ + pThis->fIdle = true; + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsReset(&g_PhysReadState); +# endif + + if (!(pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR)) + { + /* Frame boundary, so do EOF stuff here. */ + ohciR3BumpFrameNumber(pThis); + if ( (pThis->dqic != 0x7) && (pThis->dqic != 0)) + pThis->dqic--; + + /* Clean up any URBs that have been removed. */ + ohciR3CancelOrphanedURBs(pDevIns, pThis, pThisCC); + + /* Start the next frame. */ + ohciR3StartOfFrame(pDevIns, pThis, pThisCC); + } + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsPrint(&g_PhysReadState); +# endif + + ohciR3Unlock(pThisCC); + return pThis->fIdle; +} + + +/** + * @interface_method_impl{VUSBIROOTHUBPORT,pfnFrameRateChanged} + */ +static DECLCALLBACK(void) ohciR3FrameRateChanged(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameRate) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + Assert(u32FrameRate <= OHCI_DEFAULT_TIMER_FREQ); + + pThis->cTicksPerFrame = pThis->u64TimerHz / u32FrameRate; + if (!pThis->cTicksPerFrame) + pThis->cTicksPerFrame = 1; + pThis->cTicksPerUsbTick = pThis->u64TimerHz >= VUSB_BUS_HZ ? pThis->u64TimerHz / VUSB_BUS_HZ : 1; +} + + +/** + * Start sending SOF tokens across the USB bus, lists are processed in + * next frame + */ +static void ohciR3BusStart(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + pThisCC->RootHub.pIRhConn->pfnPowerOn(pThisCC->RootHub.pIRhConn); + pThis->dqic = 0x7; + + Log(("ohci: Bus started\n")); + + pThis->SofTime = PDMDevHlpTMTimeVirtGet(pDevIns); + int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, OHCI_DEFAULT_TIMER_FREQ); + AssertRC(rc); +} + + +/** + * Stop sending SOF tokens on the bus + */ +static void ohciR3BusStop(POHCICC pThisCC) +{ + int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, 0); + AssertRC(rc); + pThisCC->RootHub.pIRhConn->pfnPowerOff(pThisCC->RootHub.pIRhConn); +} + + +/** + * Move in to resume state + */ +static void ohciR3BusResume(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, bool fHardware) +{ + pThis->ctl &= ~OHCI_CTL_HCFS; + pThis->ctl |= OHCI_USB_RESUME; + + LogFunc(("fHardware=%RTbool RWE=%s\n", + fHardware, (pThis->ctl & OHCI_CTL_RWE) ? "on" : "off")); + + if (fHardware && (pThis->ctl & OHCI_CTL_RWE)) + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_RESUME_DETECT); + + ohciR3BusStart(pDevIns, pThis, pThisCC); +} + + +/* Power a port up or down */ +static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp) +{ + POHCIHUBPORT pPort = &pRh->aPorts[iPort]; + bool fOldPPS = !!(pPort->fReg & OHCI_PORT_PPS); + + LogFlowFunc(("iPort=%u fPowerUp=%RTbool\n", iPort, fPowerUp)); + + if (fPowerUp) + { + /* power up */ + if (pPort->fAttached) + pPort->fReg |= OHCI_PORT_CCS; + if (pPort->fReg & OHCI_PORT_CCS) + pPort->fReg |= OHCI_PORT_PPS; + if (pPort->fAttached && !fOldPPS) + VUSBIRhDevPowerOn(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort)); + } + else + { + /* power down */ + pPort->fReg &= ~(OHCI_PORT_PPS | OHCI_PORT_CCS | OHCI_PORT_PSS | OHCI_PORT_PRS); + if (pPort->fAttached && fOldPPS) + VUSBIRhDevPowerOff(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort)); + } +} + +#endif /* IN_RING3 */ + +/** + * Read the HcRevision register. + */ +static VBOXSTRICTRC HcRevision_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + Log2(("HcRevision_r() -> 0x10\n")); + *pu32Value = 0x10; /* OHCI revision 1.0, no emulation. */ + return VINF_SUCCESS; +} + +/** + * Write to the HcRevision register. + */ +static VBOXSTRICTRC HcRevision_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg, u32Value); + Log2(("HcRevision_w(%#010x) - denied\n", u32Value)); + ASSERT_GUEST_MSG_FAILED(("Invalid operation!!! u32Value=%#010x\n", u32Value)); + return VINF_SUCCESS; +} + +/** + * Read the HcControl register. + */ +static VBOXSTRICTRC HcControl_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + uint32_t ctl = pThis->ctl; + Log2(("HcControl_r -> %#010x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n", + ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1, + (ctl >> 9) & 1, (ctl >> 10) & 1)); + *pu32Value = ctl; + return VINF_SUCCESS; +} + +/** + * Write the HcControl register. + */ +static VBOXSTRICTRC HcControl_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + + /* log it. */ + uint32_t chg = pThis->ctl ^ val; NOREF(chg); + Log2(("HcControl_w(%#010x) => %sCBSR=%d %sPLE=%d %sIE=%d %sCLE=%d %sBLE=%d %sHCFS=%#x %sIR=%d %sRWC=%d %sRWE=%d\n", + val, + chg & 3 ? "*" : "", val & 3, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (val >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (val >> 5) & 1, + chg & (3 << 6)? "*" : "", (val >> 6) & 3, + chg & RT_BIT(8) ? "*" : "", (val >> 8) & 1, + chg & RT_BIT(9) ? "*" : "", (val >> 9) & 1, + chg & RT_BIT(10) ? "*" : "", (val >> 10) & 1)); + if (val & ~0x07ff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x07ff)); + + /* see what changed and take action on that. */ + uint32_t old_state = pThis->ctl & OHCI_CTL_HCFS; + uint32_t new_state = val & OHCI_CTL_HCFS; + +#ifdef IN_RING3 + pThis->ctl = val; + if (new_state != old_state) + { + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + switch (new_state) + { + case OHCI_USB_OPERATIONAL: + LogRel(("OHCI: USB Operational\n")); + ohciR3BusStart(pDevIns, pThis, pThisCC); + break; + case OHCI_USB_SUSPEND: + ohciR3BusStop(pThisCC); + LogRel(("OHCI: USB Suspended\n")); + break; + case OHCI_USB_RESUME: + LogRel(("OHCI: USB Resume\n")); + ohciR3BusResume(pDevIns, pThis, pThisCC, false /* not hardware */); + break; + case OHCI_USB_RESET: + { + LogRel(("OHCI: USB Reset\n")); + ohciR3BusStop(pThisCC); + /** @todo This should probably do a real reset, but we don't implement + * that correctly in the roothub reset callback yet. check it's + * comments and argument for more details. */ + pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, false /* don't do a real reset */); + break; + } + } + } +#else /* !IN_RING3 */ + RT_NOREF(pDevIns); + if ( new_state != old_state ) + { + Log2(("HcControl_w: state changed -> VINF_IOM_R3_MMIO_WRITE\n")); + return VINF_IOM_R3_MMIO_WRITE; + } + pThis->ctl = val; +#endif /* !IN_RING3 */ + + return VINF_SUCCESS; +} + +/** + * Read the HcCommandStatus register. + */ +static VBOXSTRICTRC HcCommandStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t status = pThis->status; + Log2(("HcCommandStatus_r() -> %#010x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n", + status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3)); + *pu32Value = status; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcCommandStatus register. + */ +static VBOXSTRICTRC HcCommandStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + + /* log */ + uint32_t chg = pThis->status ^ val; NOREF(chg); + Log2(("HcCommandStatus_w(%#010x) => %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", val & 1, + chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & (3<<16)? "!!!":"", (pThis->status >> 16) & 3)); + if (val & ~0x0003000f) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x0003000f)); + + /* SOC is read-only */ + val = (val & ~OHCI_STATUS_SOC); + +#ifdef IN_RING3 + /* "bits written as '0' remain unchanged in the register" */ + pThis->status |= val; + if (pThis->status & OHCI_STATUS_HCR) + { + LogRel(("OHCI: Software reset\n")); + ohciR3DoReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, POHCICC), OHCI_USB_SUSPEND, false /* N/A */); + } +#else + if ((pThis->status | val) & OHCI_STATUS_HCR) + { + LogFlow(("HcCommandStatus_w: reset -> VINF_IOM_R3_MMIO_WRITE\n")); + return VINF_IOM_R3_MMIO_WRITE; + } + pThis->status |= val; +#endif + return VINF_SUCCESS; +} + +/** + * Read the HcInterruptStatus register. + */ +static VBOXSTRICTRC HcInterruptStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->intr_status; + Log2(("HcInterruptStatus_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcInterruptStatus register. + */ +static VBOXSTRICTRC HcInterruptStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + + uint32_t res = pThis->intr_status & ~val; + uint32_t chg = pThis->intr_status ^ res; NOREF(chg); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptStatus_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30)? "*" : "", (res >> 30) & 1)); + if ( (val & ~0xc000007f) + && val != 0xffffffff /* ignore clear-all-like requests from xp. */) + Log2(("Unknown bits %#x are set!!!\n", val & ~0xc000007f)); + + /* "The Host Controller Driver may clear specific bits in this + * register by writing '1' to bit positions to be cleared" + */ + pThis->intr_status &= ~val; + ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptStatus_w"); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Read the HcInterruptEnable register + */ +static VBOXSTRICTRC HcInterruptEnable_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->intr; + Log2(("HcInterruptEnable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Writes to the HcInterruptEnable register. + */ +static VBOXSTRICTRC HcInterruptEnable_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + uint32_t res = pThis->intr | val; + uint32_t chg = pThis->intr ^ res; NOREF(chg); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptEnable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1, + chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1)); + if (val & ~0xc000007f) + Log2(("Uknown bits %#x are set!!!\n", val & ~0xc000007f)); + + pThis->intr |= val; + ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptEnable_w"); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Reads the HcInterruptDisable register. + */ +static VBOXSTRICTRC HcInterruptDisable_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ +#if 1 /** @todo r=bird: "On read, the current value of the HcInterruptEnable register is returned." */ + uint32_t val = pThis->intr; +#else /* old code. */ + uint32_t val = ~pThis->intr; +#endif + Log2(("HcInterruptDisable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1)); + + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Writes to the HcInterruptDisable register. + */ +static VBOXSTRICTRC HcInterruptDisable_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + uint32_t res = pThis->intr & ~val; + uint32_t chg = pThis->intr ^ res; NOREF(chg); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptDisable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1, + chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1)); + /* Don't bitch about invalid bits here since it makes sense to disable + * interrupts you don't know about. */ + + pThis->intr &= ~val; + ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptDisable_w"); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Read the HcHCCA register (Host Controller Communications Area physical address). + */ +static VBOXSTRICTRC HcHCCA_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcHCCA_r() -> %#010x\n", pThis->hcca)); + *pu32Value = pThis->hcca; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcHCCA register (Host Controller Communications Area physical address). + */ +static VBOXSTRICTRC HcHCCA_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t Value) +{ + Log2(("HcHCCA_w(%#010x) - old=%#010x new=%#010x\n", Value, pThis->hcca, Value & OHCI_HCCA_MASK)); + pThis->hcca = Value & OHCI_HCCA_MASK; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcPeriodCurrentED register. + */ +static VBOXSTRICTRC HcPeriodCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcPeriodCurrentED_r() -> %#010x\n", pThis->per_cur)); + *pu32Value = pThis->per_cur; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcPeriodCurrentED register. + */ +static VBOXSTRICTRC HcPeriodCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log(("HcPeriodCurrentED_w(%#010x) - old=%#010x new=%#010x (This is a read only register, only the linux guys don't respect that!)\n", + val, pThis->per_cur, val & ~7)); + //AssertMsgFailed(("HCD (Host Controller Driver) should not write to HcPeriodCurrentED! val=%#010x (old=%#010x)\n", val, pThis->per_cur)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->per_cur = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcControlHeadED register. + */ +static VBOXSTRICTRC HcControlHeadED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcControlHeadED_r() -> %#010x\n", pThis->ctrl_head)); + *pu32Value = pThis->ctrl_head; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcControlHeadED register. + */ +static VBOXSTRICTRC HcControlHeadED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcControlHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_head, val & ~7)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->ctrl_head = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcControlCurrentED register. + */ +static VBOXSTRICTRC HcControlCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcControlCurrentED_r() -> %#010x\n", pThis->ctrl_cur)); + *pu32Value = pThis->ctrl_cur; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcControlCurrentED register. + */ +static VBOXSTRICTRC HcControlCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcControlCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_cur, val & ~7)); + AssertMsg(!(pThis->ctl & OHCI_CTL_CLE), ("Illegal write! HcControl.ControlListEnabled is set! val=%#010x\n", val)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->ctrl_cur = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcBulkHeadED register. + */ +static VBOXSTRICTRC HcBulkHeadED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcBulkHeadED_r() -> %#010x\n", pThis->bulk_head)); + *pu32Value = pThis->bulk_head; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcBulkHeadED register. + */ +static VBOXSTRICTRC HcBulkHeadED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcBulkHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_head, val & ~7)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->bulk_head = val & ~7; /** @todo The ATI OHCI controller on my machine enforces 16-byte address alignment. */ + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcBulkCurrentED register. + */ +static VBOXSTRICTRC HcBulkCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcBulkCurrentED_r() -> %#010x\n", pThis->bulk_cur)); + *pu32Value = pThis->bulk_cur; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcBulkCurrentED register. + */ +static VBOXSTRICTRC HcBulkCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcBulkCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_cur, val & ~7)); + AssertMsg(!(pThis->ctl & OHCI_CTL_BLE), ("Illegal write! HcControl.BulkListEnabled is set! val=%#010x\n", val)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->bulk_cur = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + + +/** + * Read the HcDoneHead register. + */ +static VBOXSTRICTRC HcDoneHead_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcDoneHead_r() -> 0x%#08x\n", pThis->done)); + *pu32Value = pThis->done; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcDoneHead register. + */ +static VBOXSTRICTRC HcDoneHead_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcDoneHead_w(0x%#08x) - denied!!!\n", val)); + /*AssertMsgFailed(("Illegal operation!!! val=%#010x\n", val)); - OS/2 does this */ + return VINF_SUCCESS; +} + + +/** + * Read the HcFmInterval (Fm=Frame) register. + */ +static VBOXSTRICTRC HcFmInterval_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = (pThis->fit << 31) | (pThis->fsmps << 16) | (pThis->fi); + Log2(("HcFmInterval_r() -> 0x%#08x - FI=%d FSMPS=%d FIT=%d\n", + val, val & 0x3fff, (val >> 16) & 0x7fff, val >> 31)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcFmInterval (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmInterval_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + + /* log */ + uint32_t chg = val ^ ((pThis->fit << 31) | (pThis->fsmps << 16) | pThis->fi); NOREF(chg); + Log2(("HcFmInterval_w(%#010x) => %sFI=%d %sFSMPS=%d %sFIT=%d\n", + val, + chg & 0x00003fff ? "*" : "", val & 0x3fff, + chg & 0x7fff0000 ? "*" : "", (val >> 16) & 0x7fff, + chg >> 31 ? "*" : "", (val >> 31) & 1)); + if (pThis->fi != (val & OHCI_FMI_FI)) + { + Log(("ohci: FrameInterval: %#010x -> %#010x\n", pThis->fi, val & OHCI_FMI_FI)); + AssertMsg(pThis->fit != ((val >> OHCI_FMI_FIT_SHIFT) & 1), ("HCD didn't toggle the FIT bit!!!\n")); + } + + /* update */ + pThis->fi = val & OHCI_FMI_FI; + pThis->fit = (val & OHCI_FMI_FIT) >> OHCI_FMI_FIT_SHIFT; + pThis->fsmps = (val & OHCI_FMI_FSMPS) >> OHCI_FMI_FSMPS_SHIFT; + return VINF_SUCCESS; +} + +/** + * Read the HcFmRemaining (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmRemaining_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(iReg); + uint32_t Value = pThis->frt << 31; + if ((pThis->ctl & OHCI_CTL_HCFS) == OHCI_USB_OPERATIONAL) + { + /* + * Being in USB operational state guarantees SofTime was set already. + */ + uint64_t tks = PDMDevHlpTMTimeVirtGet(pDevIns) - pThis->SofTime; + if (tks < pThis->cTicksPerFrame) /* avoid muldiv if possible */ + { + uint16_t fr; + tks = ASMMultU64ByU32DivByU32(1, tks, pThis->cTicksPerUsbTick); + fr = (uint16_t)(pThis->fi - tks); + Value |= fr; + } + } + + Log2(("HcFmRemaining_r() -> %#010x - FR=%d FRT=%d\n", Value, Value & 0x3fff, Value >> 31)); + *pu32Value = Value; + return VINF_SUCCESS; +} + +/** + * Write to the HcFmRemaining (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmRemaining_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcFmRemaining_w(%#010x) - denied\n", val)); + AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val)); + return VINF_SUCCESS; +} + +/** + * Read the HcFmNumber (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmNumber_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + uint32_t val = (uint16_t)pThis->HcFmNumber; + Log2(("HcFmNumber_r() -> %#010x - FN=%#x(%d) (32-bit=%#x(%d))\n", val, val, val, pThis->HcFmNumber, pThis->HcFmNumber)); + *pu32Value = val; + return VINF_SUCCESS; +} + +/** + * Write to the HcFmNumber (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmNumber_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcFmNumber_w(%#010x) - denied\n", val)); + AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val)); + return VINF_SUCCESS; +} + +/** + * Read the HcPeriodicStart register. + * The register determines when in a frame to switch from control&bulk to periodic lists. + */ +static VBOXSTRICTRC HcPeriodicStart_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log2(("HcPeriodicStart_r() -> %#010x - PS=%d\n", pThis->pstart, pThis->pstart & 0x3fff)); + *pu32Value = pThis->pstart; + return VINF_SUCCESS; +} + +/** + * Write to the HcPeriodicStart register. + * The register determines when in a frame to switch from control&bulk to periodic lists. + */ +static VBOXSTRICTRC HcPeriodicStart_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + Log2(("HcPeriodicStart_w(%#010x) => PS=%d\n", val, val & 0x3fff)); + if (val & ~0x3fff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x3fff)); + pThis->pstart = val; /** @todo r=bird: should we support setting the other bits? */ + return VINF_SUCCESS; +} + +/** + * Read the HcLSThreshold register. + */ +static VBOXSTRICTRC HcLSThreshold_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + Log2(("HcLSThreshold_r() -> %#010x\n", OHCI_LS_THRESH)); + *pu32Value = OHCI_LS_THRESH; + return VINF_SUCCESS; +} + +/** + * Write to the HcLSThreshold register. + * + * Docs are inconsistent here: + * + * "Neither the Host Controller nor the Host Controller Driver are allowed to change this value." + * + * "This value is calculated by HCD with the consideration of transmission and setup overhead." + * + * The register is marked "R/W" the HCD column. + * + */ +static VBOXSTRICTRC HcLSThreshold_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcLSThreshold_w(%#010x) => LST=0x%03x(%d)\n", val, val & 0x0fff, val & 0x0fff)); + AssertMsg(val == OHCI_LS_THRESH, + ("HCD tried to write bad LS threshold: 0x%x (see function header)\n", val)); + /** @todo the HCD can change this. */ + return VINF_SUCCESS; +} + +/** + * Read the HcRhDescriptorA register. + */ +static VBOXSTRICTRC HcRhDescriptorA_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + uint32_t val = pThis->RootHub.desc_a; +#if 0 /* annoying */ + Log2(("HcRhDescriptorA_r() -> %#010x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTGT=%#x\n", + val, val & 0xff, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1, + (val >> 12) & 1, (val >> 24) & 0xff)); +#endif + *pu32Value = val; + return VINF_SUCCESS; +} + +/** + * Write to the HcRhDescriptorA register. + */ +static VBOXSTRICTRC HcRhDescriptorA_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + uint32_t chg = val ^ pThis->RootHub.desc_a; NOREF(chg); + Log2(("HcRhDescriptorA_w(%#010x) => %sNDP=%d %sPSM=%d %sNPS=%d %sDT=%d %sOCPM=%d %sNOCP=%d %sPOTGT=%#x - %sPowerSwitching Set%sPower\n", + val, + chg & 0xff ?"!!!": "", val & 0xff, + (chg >> 8) & 1 ? "*" : "", (val >> 8) & 1, + (chg >> 9) & 1 ? "*" : "", (val >> 9) & 1, + (chg >> 10) & 1 ?"!!!": "", 0, + (chg >> 11) & 1 ? "*" : "", (val >> 11) & 1, + (chg >> 12) & 1 ? "*" : "", (val >> 12) & 1, + (chg >> 24)&0xff? "*" : "", (val >> 24) & 0xff, + val & OHCI_RHA_NPS ? "No" : "", + val & OHCI_RHA_PSM ? "Port" : "Global")); + if (val & ~0xff001fff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0xff001fff)); + + + if ((val & (OHCI_RHA_NDP | OHCI_RHA_DT)) != OHCI_NDP_CFG(pThis)) + { + Log(("ohci: invalid write to NDP or DT in roothub descriptor A!!! val=0x%.8x\n", val)); + val &= ~(OHCI_RHA_NDP | OHCI_RHA_DT); + val |= OHCI_NDP_CFG(pThis); + } + + pThis->RootHub.desc_a = val; + return VINF_SUCCESS; +} + +/** + * Read the HcRhDescriptorB register. + */ +static VBOXSTRICTRC HcRhDescriptorB_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->RootHub.desc_b; + Log2(("HcRhDescriptorB_r() -> %#010x - DR=0x%04x PPCM=0x%04x\n", + val, val & 0xffff, val >> 16)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcRhDescriptorB register. + */ +static VBOXSTRICTRC HcRhDescriptorB_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + uint32_t chg = pThis->RootHub.desc_b ^ val; NOREF(chg); + Log2(("HcRhDescriptorB_w(%#010x) => %sDR=0x%04x %sPPCM=0x%04x\n", + val, + chg & 0xffff ? "!!!" : "", val & 0xffff, + chg >> 16 ? "!!!" : "", val >> 16)); + + if ( pThis->RootHub.desc_b != val ) + Log(("ohci: unsupported write to root descriptor B!!! 0x%.8x -> 0x%.8x\n", pThis->RootHub.desc_b, val)); + pThis->RootHub.desc_b = val; + return VINF_SUCCESS; +} + +/** + * Read the HcRhStatus (Rh = Root Hub) register. + */ +static VBOXSTRICTRC HcRhStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->RootHub.status; + if (val & (OHCI_RHS_LPSC | OHCI_RHS_OCIC)) + Log2(("HcRhStatus_r() -> %#010x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcRhStatus (Rh = Root Hub) register. + */ +static VBOXSTRICTRC HcRhStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + + /* log */ + uint32_t old = pThis->RootHub.status; + uint32_t chg; + if (val & ~0x80038003) + Log2(("HcRhStatus_w: Unknown bits %#x are set!!!\n", val & ~0x80038003)); + if ( (val & OHCI_RHS_LPSC) && (val & OHCI_RHS_LPS) ) + Log2(("HcRhStatus_w: Warning both CGP and SGP are set! (Clear/Set Global Power)\n")); + if ( (val & OHCI_RHS_DRWE) && (val & OHCI_RHS_CRWE) ) + Log2(("HcRhStatus_w: Warning both CRWE and SRWE are set! (Clear/Set Remote Wakeup Enable)\n")); + + + /* write 1 to clear OCIC */ + if ( val & OHCI_RHS_OCIC ) + pThis->RootHub.status &= ~OHCI_RHS_OCIC; + + /* SetGlobalPower */ + if ( val & OHCI_RHS_LPSC ) + { + unsigned i; + Log2(("ohci: global power up\n")); + for (i = 0; i < OHCI_NDP_CFG(pThis); i++) + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); + } + + /* ClearGlobalPower */ + if ( val & OHCI_RHS_LPS ) + { + unsigned i; + Log2(("ohci: global power down\n")); + for (i = 0; i < OHCI_NDP_CFG(pThis); i++) + ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */); + } + + if ( val & OHCI_RHS_DRWE ) + pThis->RootHub.status |= OHCI_RHS_DRWE; + + if ( val & OHCI_RHS_CRWE ) + pThis->RootHub.status &= ~OHCI_RHS_DRWE; + + chg = pThis->RootHub.status ^ old; + Log2(("HcRhStatus_w(%#010x) => %sCGP=%d %sOCI=%d %sSRWE=%d %sSGP=%d %sOCIC=%d %sCRWE=%d\n", + val, + chg & 1 ? "*" : "", val & 1, + (chg >> 1) & 1 ?"!!!": "", (val >> 1) & 1, + (chg >> 15) & 1 ? "*" : "", (val >> 15) & 1, + (chg >> 16) & 1 ? "*" : "", (val >> 16) & 1, + (chg >> 17) & 1 ? "*" : "", (val >> 17) & 1, + (chg >> 31) & 1 ? "*" : "", (val >> 31) & 1)); + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF(pDevIns, pThis, iReg, val); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +/** + * Read the HcRhPortStatus register of a port. + */ +static VBOXSTRICTRC HcRhPortStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + const unsigned i = iReg - 21; + uint32_t val = pThis->RootHub.aPorts[i].fReg | OHCI_PORT_PPS; /* PortPowerStatus: see todo on power in _w function. */ + if (val & OHCI_PORT_PRS) + { +#ifdef IN_RING3 + RTThreadYield(); +#else + Log2(("HcRhPortStatus_r: yield -> VINF_IOM_R3_MMIO_READ\n")); + return VINF_IOM_R3_MMIO_READ; +#endif + } + if (val & (OHCI_PORT_PRS | OHCI_PORT_CLEAR_CHANGE_MASK)) + Log2(("HcRhPortStatus_r(): port %u: -> %#010x - CCS=%d PES=%d PSS=%d POCI=%d RRS=%d PPS=%d LSDA=%d CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n", + i, val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1, + (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns); + return VINF_SUCCESS; +} + +#ifdef IN_RING3 +/** + * Completion callback for the vusb_dev_reset() operation. + * @thread EMT. + */ +static DECLCALLBACK(void) ohciR3PortResetDone(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) +{ + RT_NOREF(pDev); + + Assert(uPort >= 1); + PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + POHCIHUBPORT pPort = &pThis->RootHub.aPorts[uPort - 1]; + + if (RT_SUCCESS(rc)) + { + /* + * Successful reset. + */ + Log2(("ohciR3PortResetDone: Reset completed.\n")); + pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC); + pPort->fReg |= OHCI_PORT_PES | OHCI_PORT_PRSC; + } + else + { + /* desperate measures. */ + if ( pPort->fAttached + && VUSBIRhDevGetState(pThisCC->RootHub.pIRhConn, uPort) == VUSB_DEVICE_STATE_ATTACHED) + { + /* + * Damn, something weird happened during reset. We'll pretend the user did an + * incredible fast reconnect or something. (probably not gonna work) + */ + Log2(("ohciR3PortResetDone: The reset failed (rc=%Rrc)!!! Pretending reconnect at the speed of light.\n", rc)); + pPort->fReg = OHCI_PORT_CCS | OHCI_PORT_CSC; + } + else + { + /* + * The device have / will be disconnected. + */ + Log2(("ohciR3PortResetDone: Disconnected (rc=%Rrc)!!!\n", rc)); + pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC | OHCI_PORT_PRSC); + pPort->fReg |= OHCI_PORT_CSC; + } + } + + /* Raise roothub status change interrupt. */ + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); +} + +/** + * Sets a flag in a port status register but only set it if a device is + * connected, if not set ConnectStatusChange flag to force HCD to reevaluate + * connect status. + * + * @returns true if device was connected and the flag was cleared. + */ +static bool ohciR3RhPortSetIfConnected(PPDMDEVINS pDevIns, POHCI pThis, int iPort, uint32_t fValue) +{ + /* + * Writing a 0 has no effect + */ + if (fValue == 0) + return false; + + /* + * If CurrentConnectStatus is cleared we set ConnectStatusChange. + */ + if (!(pThis->RootHub.aPorts[iPort].fReg & OHCI_PORT_CCS)) + { + pThis->RootHub.aPorts[iPort].fReg |= OHCI_PORT_CSC; + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + return false; + } + + bool fRc = !(pThis->RootHub.aPorts[iPort].fReg & fValue); + + /* set the bit */ + pThis->RootHub.aPorts[iPort].fReg |= fValue; + + return fRc; +} +#endif /* IN_RING3 */ + +/** + * Write to the HcRhPortStatus register of a port. + */ +static VBOXSTRICTRC HcRhPortStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + const unsigned i = iReg - 21; + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + POHCIHUBPORT p = &pThis->RootHub.aPorts[i]; + uint32_t old_state = p->fReg; + +# ifdef LOG_ENABLED + /* + * Log it. + */ + static const char *apszCmdNames[32] = + { + "ClearPortEnable", "SetPortEnable", "SetPortSuspend", "!!!ClearSuspendStatus", + "SetPortReset", "!!!5", "!!!6", "!!!7", + "SetPortPower", "ClearPortPower", "!!!10", "!!!11", + "!!!12", "!!!13", "!!!14", "!!!15", + "ClearCSC", "ClearPESC", "ClearPSSC", "ClearOCIC", + "ClearPRSC", "!!!21", "!!!22", "!!!23", + "!!!24", "!!!25", "!!!26", "!!!27", + "!!!28", "!!!29", "!!!30", "!!!31" + }; + Log2(("HcRhPortStatus_w(%#010x): port %u:", val, i)); + for (unsigned j = 0; j < RT_ELEMENTS(apszCmdNames); j++) + if (val & (1 << j)) + Log2((" %s", apszCmdNames[j])); + Log2(("\n")); +# endif + + /* Write to clear any of the change bits: CSC, PESC, PSSC, OCIC and PRSC */ + if (val & OHCI_PORT_CLEAR_CHANGE_MASK) + p->fReg &= ~(val & OHCI_PORT_CLEAR_CHANGE_MASK); + + if (val & OHCI_PORT_CLRPE) + { + p->fReg &= ~OHCI_PORT_PES; + Log2(("HcRhPortStatus_w(): port %u: DISABLE\n", i)); + } + + if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PES)) + Log2(("HcRhPortStatus_w(): port %u: ENABLE\n", i)); + + if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PSS)) + Log2(("HcRhPortStatus_w(): port %u: SUSPEND - not implemented correctly!!!\n", i)); + + if (val & OHCI_PORT_PRS) + { + if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PRS)) + { + PVM pVM = PDMDevHlpGetVM(pDevIns); + p->fReg &= ~OHCI_PORT_PRSC; + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(i), false /* don't reset on linux */, + ohciR3PortResetDone, pDevIns, pVM); + } + else if (p->fReg & OHCI_PORT_PRS) + { + /* the guest is getting impatient. */ + Log2(("HcRhPortStatus_w(): port %u: Impatient guest!\n", i)); + RTThreadYield(); + } + } + + if (!(pThis->RootHub.desc_a & OHCI_RHA_NPS)) + { + /** @todo To implement per-device power-switching + * we need to check PortPowerControlMask to make + * sure it isn't gang powered + */ + if (val & OHCI_PORT_CLRPP) + ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */); + if (val & OHCI_PORT_PPS) + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); + } + + /** @todo r=frank: ClearSuspendStatus. Timing? */ + if (val & OHCI_PORT_CLRSS) + { + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); + pThis->RootHub.aPorts[i].fReg &= ~OHCI_PORT_PSS; + pThis->RootHub.aPorts[i].fReg |= OHCI_PORT_PSSC; + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + } + + if (p->fReg != old_state) + { + uint32_t res = p->fReg; + uint32_t chg = res ^ old_state; NOREF(chg); + Log2(("HcRhPortStatus_w(%#010x): port %u: => %sCCS=%d %sPES=%d %sPSS=%d %sPOCI=%d %sRRS=%d %sPPS=%d %sLSDA=%d %sCSC=%d %sPESC=%d %sPSSC=%d %sOCIC=%d %sPRSC=%d\n", + val, i, + chg & 1 ? "*" : "", res & 1, + (chg >> 1) & 1 ? "*" : "", (res >> 1) & 1, + (chg >> 2) & 1 ? "*" : "", (res >> 2) & 1, + (chg >> 3) & 1 ? "*" : "", (res >> 3) & 1, + (chg >> 4) & 1 ? "*" : "", (res >> 4) & 1, + (chg >> 8) & 1 ? "*" : "", (res >> 8) & 1, + (chg >> 9) & 1 ? "*" : "", (res >> 9) & 1, + (chg >> 16) & 1 ? "*" : "", (res >> 16) & 1, + (chg >> 17) & 1 ? "*" : "", (res >> 17) & 1, + (chg >> 18) & 1 ? "*" : "", (res >> 18) & 1, + (chg >> 19) & 1 ? "*" : "", (res >> 19) & 1, + (chg >> 20) & 1 ? "*" : "", (res >> 20) & 1)); + } + RT_NOREF(pDevIns); + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF(pDevIns, pThis, iReg, val); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +/** + * Register descriptor table + */ +static const OHCIOPREG g_aOpRegs[] = +{ + { "HcRevision", HcRevision_r, HcRevision_w }, /* 0 */ + { "HcControl", HcControl_r, HcControl_w }, /* 1 */ + { "HcCommandStatus", HcCommandStatus_r, HcCommandStatus_w }, /* 2 */ + { "HcInterruptStatus", HcInterruptStatus_r, HcInterruptStatus_w }, /* 3 */ + { "HcInterruptEnable", HcInterruptEnable_r, HcInterruptEnable_w }, /* 4 */ + { "HcInterruptDisable", HcInterruptDisable_r, HcInterruptDisable_w }, /* 5 */ + { "HcHCCA", HcHCCA_r, HcHCCA_w }, /* 6 */ + { "HcPeriodCurrentED", HcPeriodCurrentED_r, HcPeriodCurrentED_w }, /* 7 */ + { "HcControlHeadED", HcControlHeadED_r, HcControlHeadED_w }, /* 8 */ + { "HcControlCurrentED", HcControlCurrentED_r, HcControlCurrentED_w }, /* 9 */ + { "HcBulkHeadED", HcBulkHeadED_r, HcBulkHeadED_w }, /* 10 */ + { "HcBulkCurrentED", HcBulkCurrentED_r, HcBulkCurrentED_w }, /* 11 */ + { "HcDoneHead", HcDoneHead_r, HcDoneHead_w }, /* 12 */ + { "HcFmInterval", HcFmInterval_r, HcFmInterval_w }, /* 13 */ + { "HcFmRemaining", HcFmRemaining_r, HcFmRemaining_w }, /* 14 */ + { "HcFmNumber", HcFmNumber_r, HcFmNumber_w }, /* 15 */ + { "HcPeriodicStart", HcPeriodicStart_r, HcPeriodicStart_w }, /* 16 */ + { "HcLSThreshold", HcLSThreshold_r, HcLSThreshold_w }, /* 17 */ + { "HcRhDescriptorA", HcRhDescriptorA_r, HcRhDescriptorA_w }, /* 18 */ + { "HcRhDescriptorB", HcRhDescriptorB_r, HcRhDescriptorB_w }, /* 19 */ + { "HcRhStatus", HcRhStatus_r, HcRhStatus_w }, /* 20 */ + + /* The number of port status register depends on the definition + * of OHCI_NDP_MAX macro + */ + { "HcRhPortStatus[0]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 21 */ + { "HcRhPortStatus[1]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 22 */ + { "HcRhPortStatus[2]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 23 */ + { "HcRhPortStatus[3]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 24 */ + { "HcRhPortStatus[4]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 25 */ + { "HcRhPortStatus[5]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 26 */ + { "HcRhPortStatus[6]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 27 */ + { "HcRhPortStatus[7]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 28 */ + { "HcRhPortStatus[8]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 29 */ + { "HcRhPortStatus[9]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 30 */ + { "HcRhPortStatus[10]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 31 */ + { "HcRhPortStatus[11]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 32 */ + { "HcRhPortStatus[12]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 33 */ + { "HcRhPortStatus[13]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 34 */ + { "HcRhPortStatus[14]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 35 */ +}; + +/* Quick way to determine how many op regs are valid. Since at least one port must + * be configured (and no more than 15), there will be between 22 and 36 registers. + */ +#define NUM_OP_REGS(pohci) (21 + OHCI_NDP_CFG(pohci)) + +AssertCompile(RT_ELEMENTS(g_aOpRegs) > 21); +AssertCompile(RT_ELEMENTS(g_aOpRegs) <= 36); + +/** + * @callback_method_impl{FNIOMMMIONEWREAD} + */ +static DECLCALLBACK(VBOXSTRICTRC) ohciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + RT_NOREF(pvUser); + + /* Paranoia: Assert that IOMMMIO_FLAGS_READ_DWORD works. */ + AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3); + AssertReturn(!(off & 0x3), VERR_INTERNAL_ERROR_4); + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rc; + const uint32_t iReg = off >> 2; + if (iReg < NUM_OP_REGS(pThis)) + rc = g_aOpRegs[iReg].pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv); + else + { + Log(("ohci: Trying to read register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis))); + rc = VINF_IOM_MMIO_UNUSED_FF; + } + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE} + */ +static DECLCALLBACK(VBOXSTRICTRC) ohciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + RT_NOREF(pvUser); + + /* Paranoia: Assert that IOMMMIO_FLAGS_WRITE_DWORD_ZEROED works. */ + AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3); + AssertReturn(!(off & 0x3), VERR_INTERNAL_ERROR_4); + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rc; + const uint32_t iReg = off >> 2; + if (iReg < NUM_OP_REGS(pThis)) + rc = g_aOpRegs[iReg].pfnWrite(pDevIns, pThis, iReg, *(uint32_t const *)pv); + else + { + Log(("ohci: Trying to write to register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis))); + rc = VINF_SUCCESS; + } + return rc; +} + +#ifdef IN_RING3 + +/** + * Saves the state of the OHCI device. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to save the state to. + */ +static DECLCALLBACK(int) ohciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + LogFlow(("ohciR3SaveExec:\n")); + + int rc = pDevIns->pHlpR3->pfnSSMPutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL); + AssertRCReturn(rc, rc); + + /* Save the periodic frame rate so we can we can tell if the bus was started or not when restoring. */ + return pDevIns->pHlpR3->pfnSSMPutU32(pSSM, VUSBIRhGetPeriodicFrameRate(pThisCC->RootHub.pIRhConn)); +} + + +/** + * Loads the state of the OHCI device. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to the saved state. + * @param uVersion The data unit version number. + * @param uPass The data pass. + */ +static DECLCALLBACK(int) ohciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + LogFlow(("ohciR3LoadExec:\n")); + + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + if (uVersion >= OHCI_SAVED_STATE_VERSION_EOF_TIMER) + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL); + else if (uVersion == OHCI_SAVED_STATE_VERSION_8PORTS) + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields8Ports[0], NULL); + else + AssertMsgFailedReturn(("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + AssertRCReturn(rc, rc); + + /* + * Get the frame rate / started indicator. + * + * For older versions there is a timer saved here. We'll skip it and deduce + * the periodic frame rate from the host controller functional state. + */ + if (uVersion > OHCI_SAVED_STATE_VERSION_EOF_TIMER) + { + rc = pHlp->pfnSSMGetU32(pSSM, &pThisCC->uRestoredPeriodicFrameRate); + AssertRCReturn(rc, rc); + } + else + { + rc = pHlp->pfnSSMSkipToEndOfUnit(pSSM); + AssertRCReturn(rc, rc); + + uint32_t fHcfs = pThis->ctl & OHCI_CTL_HCFS; + switch (fHcfs) + { + case OHCI_USB_OPERATIONAL: + case OHCI_USB_RESUME: + pThisCC->uRestoredPeriodicFrameRate = OHCI_DEFAULT_TIMER_FREQ; + break; + default: + pThisCC->uRestoredPeriodicFrameRate = 0; + break; + } + } + + /** @todo could we restore the frame rate here instead of in ohciR3Resume? */ + return VINF_SUCCESS; +} + + +/** + * Reset notification. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ohciR3Reset(PPDMDEVINS pDevIns) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + LogFlow(("ohciR3Reset:\n")); + + /* + * There is no distinction between cold boot, warm reboot and software reboots, + * all of these are treated as cold boots. We are also doing the initialization + * job of a BIOS or SMM driver. + * + * Important: Don't confuse UsbReset with hardware reset. Hardware reset is + * just one way of getting into the UsbReset state. + */ + ohciR3DoReset(pDevIns, pThis, pThisCC, OHCI_USB_RESET, true /* reset devices */); +} + + +/** + * Resume notification. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ohciR3Resume(PPDMDEVINS pDevIns) +{ + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + LogFlowFunc(("\n")); + + /* Restart the frame thread if it was active when the loaded state was saved. */ + uint32_t uRestoredPeriodicFR = pThisCC->uRestoredPeriodicFrameRate; + pThisCC->uRestoredPeriodicFrameRate = 0; + if (uRestoredPeriodicFR) + { + LogFlowFunc(("Bus was active, enable periodic frame processing (rate: %u)\n", uRestoredPeriodicFR)); + int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, uRestoredPeriodicFR); + AssertRC(rc); + } +} + + +/** + * Info handler, device version. Dumps OHCI control registers. + * + * @param pDevIns Device instance which registered the info. + * @param pHlp Callback functions for doing output. + * @param pszArgs Argument string. Optional and specific to the handler. + */ +static DECLCALLBACK(void) ohciR3InfoRegs(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + uint32_t val, ctl, status; + + /* Control register */ + ctl = pThis->ctl; + pHlp->pfnPrintf(pHlp, "HcControl: %08x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n", + ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1, + (ctl >> 9) & 1, (ctl >> 10) & 1); + + /* Command status register */ + status = pThis->status; + pHlp->pfnPrintf(pHlp, "HcCommandStatus: %08x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n", + status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3); + + /* Interrupt status register */ + val = pThis->intr_status; + pHlp->pfnPrintf(pHlp, "HcInterruptStatus: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1); + + /* Interrupt enable register */ + val = pThis->intr; + pHlp->pfnPrintf(pHlp, "HcInterruptEnable: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1); + + /* HCCA address register */ + pHlp->pfnPrintf(pHlp, "HcHCCA: %08x\n", pThis->hcca); + + /* Current periodic ED register */ + pHlp->pfnPrintf(pHlp, "HcPeriodCurrentED: %08x\n", pThis->per_cur); + + /* Control ED registers */ + pHlp->pfnPrintf(pHlp, "HcControlHeadED: %08x\n", pThis->ctrl_head); + pHlp->pfnPrintf(pHlp, "HcControlCurrentED: %08x\n", pThis->ctrl_cur); + + /* Bulk ED registers */ + pHlp->pfnPrintf(pHlp, "HcBulkHeadED: %08x\n", pThis->bulk_head); + pHlp->pfnPrintf(pHlp, "HcBulkCurrentED: %08x\n", pThis->bulk_cur); + + /* Done head register */ + pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done); + + /* Done head register */ + pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done); + + /* Root hub descriptor A */ + val = pThis->RootHub.desc_a; + pHlp->pfnPrintf(pHlp, "HcRhDescriptorA: %08x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTPGT=%d\n", + val, (uint8_t)val, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1, (val >> 12) & 1, (uint8_t)(val >> 24)); + + /* Root hub descriptor B */ + val = pThis->RootHub.desc_b; + pHlp->pfnPrintf(pHlp, "HcRhDescriptorB: %08x - DR=%#04x PPCM=%#04x\n", val, (uint16_t)val, (uint16_t)(val >> 16)); + + /* Root hub status register */ + val = pThis->RootHub.status; + pHlp->pfnPrintf(pHlp, "HcRhStatus: %08x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n\n", + val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1); + + /* Port status registers */ + for (unsigned i = 0; i < OHCI_NDP_CFG(pThis); ++i) + { + val = pThis->RootHub.aPorts[i].fReg; + pHlp->pfnPrintf(pHlp, "HcRhPortStatus%02d: CCS=%d PES =%d PSS =%d POCI=%d PRS =%d PPS=%d LSDA=%d\n" + " %08x - CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n", + i, val & 1, (val >> 1) & 1, (val >> 2) & 1,(val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1, + val, (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1); + } +} + + +/** + * Destruct a device instance. + * + * Most VM resources are freed by the VM. This callback is provided so that any non-VM + * resources can be freed correctly. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(int) ohciR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + + if (RTCritSectIsInitialized(&pThisCC->CritSect)) + RTCritSectDelete(&pThisCC->CritSect); + PDMDevHlpCritSectDelete(pDevIns, &pThis->CsIrq); + + /* + * Tear down the per endpoint in-flight tracking... + */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct,OHCI constructor} + */ +static DECLCALLBACK(int) ohciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCIR3); + + /* + * Init instance data. + */ + pThisCC->pDevInsR3 = pDevIns; + + PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; + PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev); + + PDMPciDevSetVendorId(pPciDev, 0x106b); + PDMPciDevSetDeviceId(pPciDev, 0x003f); + PDMPciDevSetClassProg(pPciDev, 0x10); /* OHCI */ + PDMPciDevSetClassSub(pPciDev, 0x03); + PDMPciDevSetClassBase(pPciDev, 0x0c); + PDMPciDevSetInterruptPin(pPciDev, 0x01); +#ifdef VBOX_WITH_MSI_DEVICES + PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST); + PDMPciDevSetCapabilityList(pPciDev, 0x80); +#endif + + pThisCC->RootHub.pOhci = pThis; + pThisCC->RootHub.IBase.pfnQueryInterface = ohciR3RhQueryInterface; + pThisCC->RootHub.IRhPort.pfnGetAvailablePorts = ohciR3RhGetAvailablePorts; + pThisCC->RootHub.IRhPort.pfnGetUSBVersions = ohciR3RhGetUSBVersions; + pThisCC->RootHub.IRhPort.pfnAttach = ohciR3RhAttach; + pThisCC->RootHub.IRhPort.pfnDetach = ohciR3RhDetach; + pThisCC->RootHub.IRhPort.pfnReset = ohciR3RhReset; + pThisCC->RootHub.IRhPort.pfnXferCompletion = ohciR3RhXferCompletion; + pThisCC->RootHub.IRhPort.pfnXferError = ohciR3RhXferError; + pThisCC->RootHub.IRhPort.pfnStartFrame = ohciR3StartFrame; + pThisCC->RootHub.IRhPort.pfnFrameRateChanged = ohciR3FrameRateChanged; + + /* USB LED */ + pThisCC->RootHub.Led.u32Magic = PDMLED_MAGIC; + pThisCC->RootHub.ILeds.pfnQueryStatusLed = ohciR3RhQueryStatusLed; + + + /* + * Read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Ports", ""); + + /* Number of ports option. */ + uint32_t cPorts; + int rc = pDevIns->pHlpR3->pfnCFGMQueryU32Def(pCfg, "Ports", &cPorts, OHCI_NDP_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("OHCI configuration error: failed to read Ports as integer")); + if (cPorts == 0 || cPorts > OHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("OHCI configuration error: Ports must be in range [%u,%u]"), + 1, OHCI_NDP_MAX); + + /* Store the configured NDP; it will be used everywhere else from now on. */ + pThis->RootHub.desc_a = cPorts; + + /* + * Register PCI device and I/O region. + */ + rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = 0x80; + MsiReg.iMsiNextOffset = 0x00; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE(rc)) + { + PDMPciDevSetCapabilityList(pPciDev, 0x0); + /* That's OK, we can work without MSI */ + } +#endif + + rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, 4096, PCI_ADDRESS_SPACE_MEM, ohciMmioWrite, ohciMmioRead, NULL /*pvUser*/, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED + | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE, "USB OHCI", &pThis->hMmio); + AssertRCReturn(rc, rc); + + /* + * Register the saved state data unit. + */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, OHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL, + NULL, NULL, NULL, + NULL, ohciR3SaveExec, NULL, + NULL, ohciR3LoadExec, NULL); + AssertRCReturn(rc, rc); + + /* + * Attach to the VBox USB RootHub Driver on LUN #0. + */ + rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->RootHub.IBase, &pThisCC->RootHub.pIBase, "RootHub"); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No roothub driver attached to LUN #0!\n")); + return rc; + } + pThisCC->RootHub.pIRhConn = PDMIBASE_QUERY_INTERFACE(pThisCC->RootHub.pIBase, VUSBIROOTHUBCONNECTOR); + AssertMsgReturn(pThisCC->RootHub.pIRhConn, + ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"), + VERR_PDM_MISSING_INTERFACE); + + /* + * Attach status driver (optional). + */ + PPDMIBASE pBase; + rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->RootHub.IBase, &pBase, "Status Port"); + if (RT_SUCCESS(rc)) + pThisCC->RootHub.pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS); + else if (rc != VERR_PDM_NO_ATTACHED_DRIVER) + { + AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc)); + return rc; + } + + /* Set URB parameters. */ + rc = VUSBIRhSetUrbParams(pThisCC->RootHub.pIRhConn, sizeof(VUSBURBHCIINT), sizeof(VUSBURBHCITDINT)); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to set URB parameters")); + + /* + * Take down the virtual clock frequence for use in ohciR3FrameRateChanged(). + * (Used to be a timer, thus the name.) + */ + pThis->u64TimerHz = PDMDevHlpTMTimeVirtGetFreq(pDevIns); + + /* + * Critical sections: explain + */ + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CsIrq, RT_SRC_POS, "OHCI#%uIrq", iInstance); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to create critical section")); + + rc = RTCritSectInit(&pThisCC->CritSect); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to create critical section")); + + /* + * Do a hardware reset. + */ + ohciR3DoReset(pDevIns, pThis, pThisCC, OHCI_USB_RESET, false /* don't reset devices */); + +# ifdef VBOX_WITH_STATISTICS + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledGenUrbs, STAMTYPE_COUNTER, "CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatDroppedUrbs, STAMTYPE_COUNTER, "DroppedUrbs", STAMUNIT_OCCURENCES, "Dropped URBs (endpoint halted, or URB canceled)."); +# endif + + /* + * Register debugger info callbacks. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "ohci", "OHCI control registers.", ohciR3InfoRegs); + +# if 0/*def DEBUG_bird*/ +// g_fLogInterruptEPs = true; + g_fLogControlEPs = true; + g_fLogBulkEPs = true; +# endif + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) ohciRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ohciMmioWrite, ohciMmioRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +const PDMDEVREG g_DeviceOHCI = +{ + /* .u32version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "usb-ohci", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB, + /* .cMaxInstances = */ ~0U, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(OHCI), + /* .cbInstanceCC = */ sizeof(OHCICC), + /* .cbInstanceRC = */ 0, + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "OHCI USB controller.\n", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ ohciR3Construct, + /* .pfnDestruct = */ ohciR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ ohciR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ ohciR3Resume, + /* .pfnAttach = */ NULL, + /* .pfnDetach = */ NULL, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ NULL, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ ohciRZConstruct, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RC) + /* .pfnConstruct = */ ohciRZConstruct, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +#endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ |