// Handle an interrupt from the VM. void DebuggerProxy::interrupt(CmdInterrupt &cmd) { TRACE_RB(2, "DebuggerProxy::interrupt\n"); // Make any breakpoints that have passed breakable again. setBreakableForBreakpointsNotMatching(cmd); // At this point we have an interrupt, but we don't know if we're on the // thread the proxy considers "current". // NB: BreakPointReached really means we've got control of a VM thread from // the opcode hook. This could be for a breakpoint, stepping, etc. // Wait until this thread is the one this proxy wants to debug. if (!blockUntilOwn(cmd, true)) return; // We know we're on the "current" thread, so we can process any active flow // command, stop if we're at a breakpoint, handle other interrupts, etc. if (checkFlowBreak(cmd)) { // We've hit a breakpoint and now need to make sure that breakpoints // won't be hit again for this site until control leaves this site. // (Breakpoints can still get hit if control reaches this site during // a call that is part of this site because the flags are stacked.) unsetBreakableForBreakpointsMatching(cmd); while (true) { try { // We're about to send the client an interrupt and start // waiting for commands back from it. Disable signal polling // during this time, since our protocol requires that only one // thread talk to the client at a time. disableSignalPolling(); SCOPE_EXIT { enableSignalPolling(); }; processInterrupt(cmd); } catch (const DebuggerException &e) { TRACE(2, "DebuggerException from processInterrupt!\n"); switchThreadMode(Normal); throw; } catch (...) { TRACE(2, "Unknown exception from processInterrupt!\n"); assertx(false); // no other exceptions should be seen here switchThreadMode(Normal); throw; } if (cmd.getInterruptType() == PSPEnded) break; if (!m_newThread) break; // we're not switching threads switchThreadMode(Normal, m_newThread->m_id); m_newThread.reset(); blockUntilOwn(cmd, false); } } if ((m_threadMode == Normal) || (cmd.getInterruptType() == PSPEnded)) { // If the thread mode is Normal we let other threads with // interrupts go ahead and process them. We also do this when the // thread is at PSPEnded because the thread is done. switchThreadMode(Normal); } }
static void runScreenSimulation(int16_t *synthBuffer, uint32_t cyclesPerScreen, uint16_t samplesPerCall, int16_t **synthTraceBufs) { setupVolumeHack(); // cpu cycles used during processing int32_t irqCycles= 0, nmiCycles= 0, mainProgCycles= 0; // only mainProgCycles can become negative.. // reminder: timings of IRQ and NMI are calculated as if these where independent (which is // of course flawed). A proper premptive scheduling is not simulated. int32_t availableIrqCycles= cyclesPerScreen; // cpu cycles still available for processing (limitation: only the waiting time is currently considered..) int32_t availableNmiCycles= cyclesPerScreen; uint32_t synthPos= 0; // start time for the samples synthesized from SID settings (measured in cycles) /* * process NMI and IRQ interrupts in their order of appearance - and as long as they fit into this screen */ uint32_t currentIrqTimer= forwardToNextInterrupt(getIrqTimerMode(CIA1), CIA1, availableIrqCycles); // FIXME: the assumption that only RSID uses NMI meanwhile proved to be wrong (see MicroProse_Soccer_V1.sid // tracks >4) .. also an IRQ run may also update the NMI schedule! flawed: ciaForwardToNextInterrupt // calculation does NOT consider the time when the timer is actually started but only the time that some // interrupt returns (which will lead to distortions) uint32_t currentNmiTimer= envIsRSID() ? ciaForwardToNextInterrupt(CIA2, availableNmiCycles) : NO_INT; // KNOWN LIMITATION: ideally all 4 timers should be kept in sync - not just A/B within the same unit! // however progs that actually rely on multiple timers (like Vicious_SID_2-Carmina_Burana.sid) probably // need timer info that is continuously updated - so that effort would only make sense in a full fledged // cycle-by-cycle emulator. uint8_t hack= vicIsIrqActive() && isCiaActive(CIA1); uint32_t tNmi= currentNmiTimer; // NMI simulation progress in cycles uint32_t tIrq= currentIrqTimer; // IRQ simulation progress in cycles uint32_t tDone= 0; uint16_t mainProgStep= 100; // periodically give the main prog a chance to run.. uint16_t mainProgStart= mainProgStep; if (0 || (envIsRSID() && (currentIrqTimer == NO_INT) && (currentNmiTimer == NO_INT))) { _mainLoopOnlyMode= 1; mainProgStart= 0; // this might be the better choice in any case.. but to avoid regression testing lets use it here for now.. } else { _mainLoopOnlyMode= 0; // Wonderland_XII-Digi_part_4 has high interrupt rate.. #define ABORT_FUSE 400 // better safe than sorry.. no need to crash the player with an endless loop uint16_t fuse; for (fuse= 0; (fuse<ABORT_FUSE) && ((currentIrqTimer != NO_INT) || (currentNmiTimer != NO_INT)) ; fuse++) { // periodically give unused cycles to main program (needed, e.g. by THCM's stuff) if (tDone > mainProgStart) { // todo: the handing of main prog cycles is rather flawed (timing for main code that would normally run before // 'mainProgStep'). In an alternative "improved" impl I ran "main" directly before "processInterrupt" calls below - letting // "main" use all cycles unused up to that moment. also I had added interruption for "LDA#00; BEQ .." based endless main-loop // logic (which would require IRQ logic to break out) to avoid unnecessary cycle usage.. while those changes work fine for // THCM's recent stuff they broke stuff like "Arkanoid", etc (so I rolled them back for now..) // -> now that the D012 handling has been improved I might give it another try.. int32_t availableMainCycles= tDone - (nmiCycles + irqCycles + mainProgCycles); if (availableMainCycles > 0) { processMain(cyclesPerScreen, mainProgStart, availableMainCycles); mainProgCycles+= availableMainCycles; } mainProgStart= tDone + mainProgStep; } // FIXME: better allow NMI to interrupt IRQ, i.e. set respective cycle-limit // accordingly - and then resume the IRQ later. see Ferrari_Formula_One.sid if (!isIrqNext(currentIrqTimer, currentNmiTimer, tIrq, tNmi)) { // handle next NMI tDone= tNmi; // problem: songs like Arcade_Classics re-set the d418 volume nowhere else but in their // NMI digi-player: when only used for the short intervals between NMIs the respective changing // settings do not seem to pose any problems for the rendering of the regular SID output // (or are even intentionally creating some kind of tremolo) but when SID output rendering // is performed for longer intervals (e.g. like is done here) then "wrong" settings are also // used for these longer intervals which may cause audible clicking/pauses in the output. // if the handled NMIs were preemtively scheduled/properly timed, then renderSynth() could // be performed also for these shorter intervals.. but unfortunately they are not and respective // rendering cannot be done here, i.e. the IRQ based rendering then has no clue regarding the // validity/duration of respectice NMI-volume infos.. nmiCycles+= processInterrupt(NMI_OFFSET_MASK, getNmiVector(), tNmi, cyclesPerScreen); availableNmiCycles-= currentNmiTimer; if (availableNmiCycles <= 0) { availableNmiCycles= 0; // see unsigned use below } currentNmiTimer= ciaForwardToNextInterrupt(CIA2, availableNmiCycles); tNmi+= currentNmiTimer; // will be garbage on last run.. } else { // handle next IRQ tDone= tIrq; renderSynth(synthBuffer, cyclesPerScreen, samplesPerCall, synthPos, tIrq, synthTraceBufs); synthPos= tIrq; if (hack) { // in case CIA1 has also been configured to trigger IRQs, our current "raster IRQ or timer IRQ" impl // is obviously flawed.. this hack will fix simple scenarios like Cycles.sid ciaSignalUnderflow(CIA1, TIMER_A); } // FIXME: multi-frame IRQ handling would be necessary to deal with songs like: Musik_Run_Stop.sid uint32_t usedCycles= processInterrupt(IRQ_OFFSET_MASK, getIrqVector(), tIrq, _irqTimeout); // flaw: IRQ might have switched off NMI, e.g. Vicious_SID_2-Blood_Money.sid if (usedCycles >=_irqTimeout) { // IRQ gets aborted due to a timeout if (cpuIrqFlag()) { // this IRQ handler does not want to be interrupted.. (either the code got stuck due to some impossible // condition - e.g. waiting for a timer which we don't update - or it is an endless IRQ routine that needs to // continue..) } else { // this IRQ handler was intentionally waiting to get interrupted - and it would normally not have burned // the same amount of cycles (we have no chance to get the timing right here..) usedCycles= 1; } } irqCycles+= usedCycles; availableIrqCycles-= currentIrqTimer; if (availableIrqCycles <= 0) { availableIrqCycles= 0; // see unsigned use below } currentIrqTimer= forwardToNextInterrupt(getIrqTimerMode(CIA1), CIA1, availableIrqCycles); tIrq+= currentIrqTimer; // will be garbage on last run.. if (envIsTimerDrivenPSID() && (currentIrqTimer != NO_INT)) { if(usedCycles > currentIrqTimer) { currentIrqTimer= NO_INT; // hack for Eye_of_the_Tiger } } // some dumbshit timer-PSIDs do not ackn IRQ (MasterComposer rips & Automatas.sid) if(envIsTimerDrivenPSID()) { ciaReadMem(0xdc0d); } } } #ifdef DEBUG if (fuse >= ABORT_FUSE) { EM_ASM_({ console.log('fuse blown: ' + $0); }, fuse); EM_ASM_({ console.log('last IRQ timer: ' + $0); }, currentIrqTimer);
/*----------------------------------------------------------------------------*/ void PIN_INT7_ISR(void) { processInterrupt(7); }
/*----------------------------------------------------------------------------*/ void PIN_INT0_ISR(void) { processInterrupt(0); }
/*----------------------------------------------------------------------------*/ void PIN_INT5_ISR(void) { processInterrupt(5); }
/*----------------------------------------------------------------------------*/ void PIN_INT6_ISR(void) { processInterrupt(6); }
/*----------------------------------------------------------------------------*/ void PIN_INT4_ISR(void) { processInterrupt(4); }
/*----------------------------------------------------------------------------*/ void PIN_INT3_ISR(void) { processInterrupt(3); }
/*----------------------------------------------------------------------------*/ void PIN_INT2_ISR(void) { processInterrupt(2); }
/*----------------------------------------------------------------------------*/ void PIN_INT1_ISR(void) { processInterrupt(1); }
// Handle an interrupt from the VM. void DebuggerProxy::interrupt(CmdInterrupt &cmd) { TRACE(2, "DebuggerProxy::interrupt\n"); changeBreakPointDepth(cmd); if (cmd.getInterruptType() == BreakPointReached) { if (!needInterrupt()) return; // NB: stepping is represented as a BreakPointReached interrupt. // Modify m_lastLocFilter to save current location. This will short-circuit // the work done up in phpDebuggerOpcodeHook() and ensure we don't break on // this line until we're completely off of it. InterruptSite *site = cmd.getSite(); if (g_vmContext->m_lastLocFilter) { g_vmContext->m_lastLocFilter->clear(); } else { g_vmContext->m_lastLocFilter = new VM::PCFilter(); } if (debug && Trace::moduleEnabled(Trace::bcinterp, 5)) { Trace::trace("prepare source loc filter\n"); const VM::OffsetRangeVec& offsets = site->getCurOffsetRange(); for (VM::OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { Trace::trace("block source loc in %s:%d: unit %p offset [%d, %d)\n", site->getFile(), site->getLine0(), site->getUnit(), it->m_base, it->m_past); } } g_vmContext->m_lastLocFilter->addRanges(site->getUnit(), site->getCurOffsetRange()); // If the breakpoint is not to be processed, we should continue execution. BreakPointInfoPtr bp = getBreakPointAtCmd(cmd); if (bp) { if (!bp->breakable(getRealStackDepth())) { return; } else { bp->unsetBreakable(getRealStackDepth()); } } } // Wait until this thread is the thread this proxy wants to debug. // NB: breakpoints and control flow checks happen here, too, and return false // if we're not done with the flow, or not at a breakpoint, etc. if (!blockUntilOwn(cmd, true)) { return; } if (processFlowBreak(cmd)) { while (true) { try { Lock lock(m_signalMutex); m_signum = CmdSignal::SignalNone; processInterrupt(cmd); } catch (const DebuggerException &e) { switchThreadMode(Normal); throw; } catch (...) { assert(false); // no other exceptions should be seen here switchThreadMode(Normal); throw; } if (cmd.getInterruptType() == PSPEnded) { switchThreadMode(Normal); return; // we are done with this thread } if (!m_newThread) { break; // we're not switching threads } switchThreadMode(Normal, m_newThread->m_id); blockUntilOwn(cmd, false); } } if (m_threadMode == Normal) { Lock lock(this); m_thread = 0; notify(); } else if (cmd.getInterruptType() == PSPEnded) { switchThreadMode(Normal); // we are done with this thread } }