diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index 1e190f9ac0..d231adcfd2 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -153,6 +153,10 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) mmio->Register(base | IPC_PPCCTRL, MMIO::ComplexRead([](u32) { return ctrl.ppc(); }), MMIO::ComplexWrite([](u32, u32 val) { ctrl.ppc(val); + // The IPC interrupt is triggered when IY1/IY2 is set and + // Y1/Y2 is written to -- even when this results in clearing the bit. + if ((val >> 2 & 1 && ctrl.IY1) || (val >> 1 & 1 && ctrl.IY2)) + ppc_irq_flags |= INT_CAUSE_IPC_BROADWAY; if (ctrl.X1) HLE::GetIOS()->EnqueueIPCRequest(ppc_msg); HLE::GetIOS()->UpdateIPC(); @@ -207,13 +211,19 @@ static void UpdateInterrupts(u64 userdata, s64 cyclesLate) !!(ppc_irq_flags & ppc_irq_masks)); } +void ClearX1() +{ + ctrl.X1 = 0; +} + void GenerateAck(u32 _Address) { - arm_msg = _Address; // dunno if it's really set here, but HLE needs to stay in context ctrl.Y2 = 1; DEBUG_LOG(WII_IPC, "GenerateAck: %08x | %08x [R:%i A:%i E:%i]", ppc_msg, _Address, ctrl.Y1, ctrl.Y2, ctrl.X1); - CoreTiming::ScheduleEvent(1000, updateInterrupts, 0); + // Based on a hardware test, the IPC interrupt takes approximately 100 TB ticks to fire + // after Y2 is seen in the control register. + CoreTiming::ScheduleEvent(100 * SystemTimers::TIMER_RATIO, updateInterrupts); } void GenerateReply(u32 _Address) @@ -222,7 +232,9 @@ void GenerateReply(u32 _Address) ctrl.Y1 = 1; DEBUG_LOG(WII_IPC, "GenerateReply: %08x | %08x [R:%i A:%i E:%i]", ppc_msg, _Address, ctrl.Y1, ctrl.Y2, ctrl.X1); - UpdateInterrupts(); + // Based on a hardware test, the IPC interrupt takes approximately 100 TB ticks to fire + // after Y1 is seen in the control register. + CoreTiming::ScheduleEvent(100 * SystemTimers::TIMER_RATIO, updateInterrupts); } bool IsReady() diff --git a/Source/Core/Core/HW/WII_IPC.h b/Source/Core/Core/HW/WII_IPC.h index d97e7eab0c..34419b8988 100644 --- a/Source/Core/Core/HW/WII_IPC.h +++ b/Source/Core/Core/HW/WII_IPC.h @@ -42,6 +42,7 @@ void DoState(PointerWrap& p); void RegisterMMIO(MMIO::Mapping* mmio, u32 base); +void ClearX1(); void GenerateAck(u32 _Address); void GenerateReply(u32 _Address); diff --git a/Source/Core/Core/IOS/Device.cpp b/Source/Core/Core/IOS/Device.cpp index b0301205aa..d14c8cf06a 100644 --- a/Source/Core/Core/IOS/Device.cpp +++ b/Source/Core/Core/IOS/Device.cpp @@ -184,10 +184,18 @@ IPCCommandResult Device::Unsupported(const Request& request) return GetDefaultReply(IPC_EINVAL); } -// Returns an IPCCommandResult for a reply that takes 25 us (based on ES::GetTicketViews) +// Returns an IPCCommandResult for a reply with an average reply time for devices +// Please avoid using this function if more accurate timings are known. IPCCommandResult Device::GetDefaultReply(const s32 return_value) { - return {return_value, true, SystemTimers::GetTicksPerSecond() / 40000}; + // Based on a hardware test, a device takes at least ~2700 ticks to reply to an IPC request. + // Depending on how much work a command performs, this can take much longer (10000+) + // especially if the NAND filesystem is accessed. + // + // Because we currently don't emulate timing very accurately, we should not return + // the minimum possible reply time (~960 ticks from the kernel or ~2700 from devices) + // but an average time, otherwise we are going to be much too fast in most cases. + return {return_value, true, 4000 * SystemTimers::TIMER_RATIO}; } // Returns an IPCCommandResult with no reply. Useful for async commands that will generate a reply diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 975bdd8268..4abd2312a5 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -475,7 +475,7 @@ IPCCommandResult Kernel::OpenDevice(OpenRequest& request) if (new_fd < 0 || new_fd >= IPC_MAX_FDS) { ERROR_LOG(IOS, "Couldn't get a free fd, too many open files"); - return Device::Device::GetDefaultReply(FS_EFDEXHAUSTED); + return IPCCommandResult{IPC_EMAX, true, 5000 * SystemTimers::TIMER_RATIO}; } request.fd = new_fd; @@ -497,7 +497,7 @@ IPCCommandResult Kernel::OpenDevice(OpenRequest& request) if (!device) { ERROR_LOG(IOS, "Unknown device: %s", request.path.c_str()); - return Device::Device::GetDefaultReply(IPC_ENOENT); + return {IPC_ENOENT, true, 3700 * SystemTimers::TIMER_RATIO}; } IPCCommandResult result = device->Open(request); @@ -511,6 +511,9 @@ IPCCommandResult Kernel::OpenDevice(OpenRequest& request) IPCCommandResult Kernel::HandleIPCCommand(const Request& request) { + if (request.command < IPC_CMD_OPEN || request.command > IPC_CMD_IOCTLV) + return IPCCommandResult{IPC_EINVAL, true, 978 * SystemTimers::TIMER_RATIO}; + if (request.command == IPC_CMD_OPEN) { OpenRequest open_request{request.address}; @@ -519,7 +522,7 @@ IPCCommandResult Kernel::HandleIPCCommand(const Request& request) const auto device = (request.fd < IPC_MAX_FDS) ? m_fdmap[request.fd] : nullptr; if (!device) - return Device::Device::GetDefaultReply(IPC_EINVAL); + return IPCCommandResult{IPC_EINVAL, true, 550 * SystemTimers::TIMER_RATIO}; IPCCommandResult ret; u64 wall_time_before = Common::Timer::GetTimeUs(); @@ -547,7 +550,7 @@ IPCCommandResult Kernel::HandleIPCCommand(const Request& request) break; default: ASSERT_MSG(IOS, false, "Unexpected command: %x", request.command); - ret = Device::Device::GetDefaultReply(IPC_EINVAL); + ret = IPCCommandResult{IPC_EINVAL, true, 978 * SystemTimers::TIMER_RATIO}; break; } @@ -582,7 +585,11 @@ void Kernel::ExecuteIPCCommand(const u32 address) // Happens AS SOON AS IPC gets a new pointer! void Kernel::EnqueueIPCRequest(u32 address) { - CoreTiming::ScheduleEvent(1000, s_event_enqueue, address | ENQUEUE_REQUEST_FLAG); + // Based on hardware tests, IOS takes between 5µs and 10µs to acknowledge an IPC request. + // Console 1: 456 TB ticks before ACK + // Console 2: 658 TB ticks before ACK + CoreTiming::ScheduleEvent(500 * SystemTimers::TIMER_RATIO, s_event_enqueue, + address | ENQUEUE_REQUEST_FLAG); } // Called to send a reply to an IOS syscall @@ -615,8 +622,6 @@ void Kernel::HandleIPCEvent(u64 userdata) UpdateIPC(); } -// This is called every IPC_HLE_PERIOD from SystemTimers.cpp -// Takes care of routing ipc <-> ipc HLE void Kernel::UpdateIPC() { if (!IsReady()) @@ -624,6 +629,7 @@ void Kernel::UpdateIPC() if (m_request_queue.size()) { + ClearX1(); GenerateAck(m_request_queue.front()); u32 command = m_request_queue.front(); m_request_queue.pop_front();