/* -*- mode: C++; tab-width: 4 -*- */ /* ===================================================================== *\ Copyright (c) 2000-2001 Palm, Inc. or its subsidiaries. All rights reserved. This file is part of the Palm OS Emulator. 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; either version 2 of the License, or (at your option) any later version. \* ===================================================================== */ #include "EmCommon.h" #include "EmPalmOS.h" #include "EmBankDRAM.h" // EmBankDRAM::SetLong #include "EmBankMapped.h" // EmBankMapped::SetLong #include "EmBankROM.h" // EmBankROM::SetLong #include "EmBankSRAM.h" // EmBankSRAM::SetLong #include "DebugMgr.h" // Debug::HandleSystemCall #include "EmCPU68K.h" // gCPU68K, gStackHigh, etc. #include "EmErrCodes.h" // kError_UnimplementedTrap, kError_InvalidLibraryRefNum #include "EmMemory.h" // CEnableFullAccess #include "EmPalmHeap.h" // EmPalmHeap, GetHeapByPtr #include "EmPalmFunction.h" // ProscribedFunction #include "EmPalmStructs.h" // EmAliasCardHeaderType #include "EmPatchMgr.h" // EmPatchMgr #include "EmPatchState.h" // EmPatchState #include "EmSession.h" // gSession->Reset #include "ErrorHandling.h" // Errors::ReportInvalidPC #include "Logging.h" // LogSystemCalls #include "MetaMemory.h" // MetaMemory::InRAMOSComponent #include "Miscellaneous.h" // GetSystemCallContext #include "Profiling.h" // gProfilingEnabled #include "ROMStubs.h" // IntlSetStrictChecks #include "UAE.h" // CHECK_STACK_POINTER_DECREMENT #include "EmEventPlayback.h" // EmEventPlayback::Initialize (); #include "EmLowMem.h" // EmLowMem::Initialize (); #include "EmPalmFunction.h" // EmPalmFunctionInit (); #include "EmPalmHeap.h" // EmPalmHeap::Initialize (); #include "EmPatchMgr.h" // EmPatchMgr::Initialize (); #include "Hordes.h" // Hordes::Initialize (); #include "Platform_NetLib.h" // Platform_NetLib::Initialize(); #define LOG_FUNCTION_CALLS 0 static StackList gStackList; static StackRange gBootStack; static StackRange gKernelStack; static StackRange gInterruptStack; static emuptr gBigROMEntry; static const uint32 CJ_TAGAMX = 0x414D5800; static const uint32 CJ_TAGFENCE = 0x55555555; // Subtract 34 bytes from the size of the stack. That's because // if an interrupt occurs, 34 bytes are pushed onto the stack before // the OS switches over to the interrupt stack. // // Exception 6 bytes // Old SR and old PC // LINK 4 bytes // Old A6 // MOVEM.L 20 bytes // Old D0, D1, D2, A0, A1 // MOVE A0, -(SP) 4 bytes // Old A0 (again) // --------------------------------------------------------- // Total 34 bytes static const int kInterruptOverhead = 34; static emuptr gStackLowWaterMark = 0; /*********************************************************************** * * FUNCTION: EmPalmOS::Initialize * * DESCRIPTION: Standard initialization function. Responsible for * initializing this sub-system when a new session is * created. Will be followed by at least one call to * Reset or Load. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::Initialize (void) { EmAssert (gCPU68K); gCPU68K->InstallHookException (kException_SysCall, HandleTrap15); gCPU68K->InstallHookJSR (HandleJSR); gCPU68K->InstallHookJSR_Ind (HandleJSR_Ind); gCPU68K->InstallHookLINK (HandleLINK); gCPU68K->InstallHookRTE (HandleRTE); gCPU68K->InstallHookRTS (HandleRTS); gCPU68K->InstallHookNewPC (HandleNewPC); gCPU68K->InstallHookNewSP (HandleNewSP); gBigROMEntry = EmMemNULL; // Add a notification for IntlStrictChecks gPrefs->AddNotification (&EmPalmOS::PrefsChanged, kPrefKeyReportStrictIntlChecks); gPrefs->AddNotification (&EmPalmOS::PrefsChanged, kPrefKeyReportOverlayErrors); Hordes::Initialize (); EmEventPlayback::Initialize (); EmPatchMgr::Initialize (); Platform_NetLib::Initialize (); EmPalmHeap::Initialize (); EmLowMem::Initialize (); EmPalmFunctionInit (); } /*********************************************************************** * * FUNCTION: EmPalmOS::Reset * * DESCRIPTION: Standard reset function. Sets the sub-system to a * default state. This occurs not only on a Reset (as * from the menu item), but also when the sub-system * is first initialized (Reset is called after Initialize) * as well as when the system is re-loaded from an * insufficient session file. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::Reset (void) { gStackList.clear (); gBootStack = StackRange (); gKernelStack = StackRange (); gInterruptStack = StackRange (); gKernelStackOverflowed = 0; Hordes::Reset (); EmEventPlayback::Reset (); EmPatchMgr::Reset (); Platform_NetLib::Reset (); EmPalmHeap::Reset (); EmLowMem::Reset (); // If the appropriate modifier key is down, install a temporary breakpoint // at the start of the Big ROM. if (Platform::StopOnResetKeyDown ()) { emuptr romStart = EmBankROM::GetMemoryStart (); emuptr headerVersion = EmMemGet32 (romStart + offsetof (CardHeaderType, hdrVersion)); long bigROMOffset = 0x03000; if (headerVersion > 1) { bigROMOffset = EmMemGet32 (romStart + offsetof (CardHeaderType, bigROMOffset)); bigROMOffset &= 0x000FFFFF; // Allows for 1 Meg offset. } emuptr resetVector = EmMemGet32 (romStart + bigROMOffset + offsetof (CardHeaderType, resetVector)); Debug::SetBreakpoint (dbgTempBPIndex, resetVector, NULL); } } /*********************************************************************** * * FUNCTION: EmPalmOS::Save * * DESCRIPTION: Standard save function. Saves any sub-system state to * the given session file. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::Save (SessionFile& f) { Hordes::Save (f); EmEventPlayback::Save (f); EmPatchMgr::Save (f); Platform_NetLib::Save (f); EmPalmHeap::Save (f); EmLowMem::Save (f); const long kCurrentVersion = 2; Chunk chunk; EmStreamChunk s (chunk); s << kCurrentVersion; s << gStackList; s << gBootStack; s << gKernelStack; s << gInterruptStack; s << gStackHigh; s << gStackLowWaterMark; s << gStackLowWarn; s << gStackLow; s << gKernelStackOverflowed; f.WriteStackInfo (chunk); } /*********************************************************************** * * FUNCTION: EmPalmOS::Load * * DESCRIPTION: Standard load function. Loads any sub-system state * from the given session file. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::Load (SessionFile& f) { Hordes::Load (f); EmEventPlayback::Load (f); EmPatchMgr::Load (f); Platform_NetLib::Load (f); EmPalmHeap::Load (f); EmLowMem::Load (f); Chunk chunk; if (f.ReadStackInfo (chunk)) { long version; EmStreamChunk s (chunk); s >> version; if (version >= 1) { s >> gStackList; s >> gBootStack; s >> gKernelStack; s >> gInterruptStack; s >> gStackHigh; s >> gStackLowWaterMark; s >> gStackLowWarn; s >> gStackLow; } if (version >= 2) { s >> gKernelStackOverflowed; } } else { f.SetCanReload (false); // Need to reboot } } /*********************************************************************** * * FUNCTION: EmPalmOS::Dispose * * DESCRIPTION: Standard dispose function. Completely release any * resources acquired or allocated in Initialize and/or * Load. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::Dispose (void) { EmLowMem::Dispose (); EmPalmHeap::Dispose (); Platform_NetLib::Dispose (); EmPatchMgr::Dispose (); EmEventPlayback::Dispose (); Hordes::Dispose (); } #pragma mark - /*********************************************************************** * * FUNCTION: EmPalmOS::CheckStackPointerAssignment * * DESCRIPTION: !!! This operation slows down Gremlins by about 10%; it * should be sped up or made an option. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::CheckStackPointerAssignment (void) { // If it was a "drastic" change, assume that we're *setting* the // stack pointer to a new stack. Scarf up information about that // block of memory and treat that block as a stack. // See if we already know about this stack. emuptr curA7 = gCPU->GetSP (); StackList::iterator iter = gStackList.begin (); while (iter != gStackList.end ()) { if ((curA7 >= (*iter).fBottom) && (curA7 <= (*iter).fTop)) { // If so, switch to it. SetCurrentStack (*iter); return; } ++iter; } // If not, get some information about it and save it off. const EmPalmHeap* heap = EmPalmHeap::GetHeapByPtr (curA7); if (heap) { const EmPalmChunk* chunk = heap->GetChunkBodyContaining (curA7); if (chunk) { // The second time we switch stacks, we're switching from the // boot stack to the kernel stack. In that case, forget any // notion of the boot stack. if (gBootStack.fBottom != EmMemNULL) { ForgetStack (gBootStack.fBottom); } // See if this looks like the AMX kernel stack. // We detect this by seeing (a) if the stack pointer // is being set to a value not too close to the end // of the chunk it's in (indicating that the stack is // embedded in some other data, as is the AMX stack) // and (b) if the values the stack pointer points to // are the appropriate tags. CEnableFullAccess munge; if (curA7 <= (chunk->BodyEnd () - 8) && EmMemGet32 (curA7) == CJ_TAGAMX && EmMemGet32 (curA7 + 4) == CJ_TAGFENCE) { RememberKernelStack (); // Remember this stack as the "current" stack. SetCurrentStack (gKernelStack); } else { RememberStackChunk (*chunk); // Remember this stack as the "current" stack. StackRange range (chunk->BodyStart (), chunk->BodyStart () + chunk->BodySize ()); SetCurrentStack (range); } } else { // !!! } } else { if (gStackList.size () == 0) { // We're switching from the reset stack to the boot stack. RememberBootStack (); // Remember this stack as the "current" stack. SetCurrentStack (gBootStack); } } } /*********************************************************************** * * FUNCTION: EmPalmOS::CheckStackPointerDecrement * * DESCRIPTION: !!! This operation slows down Gremlins by about 10%; it * should be sped up or made an option. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::CheckStackPointerDecrement (void) { // If it was an "incremental" change, make sure that A7 is not // now outside the stack it used to be in (as indicated by oldA7). // Test for "close to the end" (in which case we display a warning), and // "past the end" (in which case we print a fatal error). if (gKernelStackOverflowed) return; emuptr curA7 = gCPU->GetSP (); Bool warning = curA7 < gStackLowWarn; Bool fatal = curA7 < gStackLow; if (gStackLowWaterMark > curA7) gStackLowWaterMark = curA7; if (warning) { // Special hack for the kernel stack overflowing into the // interrupt stack. If there was a stack overflow and the // current stack is the kernel stack, then ignore the error. // Also, try limiting this hack to older Palm OS's by looking // at the stack size. Newer ROMs will have larger stacks and // should not be allowed to have this bug. if (fatal && gKernelStack.fTop == gStackHigh && gKernelStack.fTop - gKernelStack.fBottom <= 0x2F4) { gKernelStackOverflowed = 1; return; } if (fatal) { Errors::ReportErrStackOverflow (); } else { EmAssert (gSession); gSession->ScheduleDeferredError (new EmDeferredErrStackFull); } } } /*********************************************************************** * * FUNCTION: EmPalmOS::CheckKernelStack * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::CheckKernelStack (void) { EmAssert (gKernelStackOverflowed); emuptr curA7 = gCPU->GetSP (); if (curA7 <= gStackHigh && curA7 >= gStackLow) { gKernelStackOverflowed = 0; } } /*********************************************************************** * * FUNCTION: EmPalmOS::RememberStackChunk * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::RememberStackChunk (const EmPalmChunk& chunk) { // Create a range object for the chunk. StackRange range (chunk.BodyStart (), chunk.BodyEnd ()); // Remember it for later. RememberStackRange (range); } /*********************************************************************** * * FUNCTION: EmPalmOS::RememberBootStack * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::RememberBootStack (void) { /* There's a constant in SystemPrv.h that indicates that the boot stack should be 4K long. However, that 4K is relative to the start of the dynamic heap, which is used while the boot stack is in effect. As the system boots up, blocks are allocated and de-allocated in the stack range. We have a check in ForgetStack that checks to see if a MemFooFree operation is taking place within a stack -- normally a dubious operation -- and it's noticing this situation. For now, it looks like we only use 0x438 bytes* of the boot stack, so I'll allow 0x500. This should prevent the overlap of the boot stack with any allocated chunks. * This was against one particular ROM, but I don't remember which. Checking a Palm IIIc 4.0 Debug ROM just now, I see it using 0x308 bytes. */ // const int kBootStackSize = 0x1000; // Per SystemPrv.h const int kBootStackSize = 0x0500; // Per experimentation gBootStack = StackRange ( gCPU->GetSP () - kBootStackSize, gCPU->GetSP ()); RememberStackRange (gBootStack); } /*********************************************************************** * * FUNCTION: EmPalmOS::RememberKernelStack * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::RememberKernelStack (void) { // Scan down for the bottom of stack marker. CEnableFullAccess munge; emuptr ksp = gCPU->GetSP (); while (EmMemGet32 (ksp) != CJ_TAGFENCE) ksp -= 2; emuptr isp = ksp - 4; while (EmMemGet32 (isp) != CJ_TAGFENCE) isp -= 2; // Add the two stacks. Add the interrupt stack first, and the // kernel stack second. That way, the last added stack -- the // kernel stack -- is remembered as the "current" stack. gInterruptStack = StackRange (isp + 4, ksp); RememberStackRange (gInterruptStack); gKernelStack = StackRange (ksp + 4, gCPU->GetSP ()); RememberStackRange (gKernelStack); } /*********************************************************************** * * FUNCTION: EmPalmOS::RememberStackRange * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::RememberStackRange (const StackRange& range) { #if _DEBUG // Make sure this range doesn't overlap any other stack in our list. StackList::iterator iter = gStackList.begin (); while (iter != gStackList.end ()) { if (range.fBottom >= iter->fBottom && range.fBottom < iter->fTop) EmAssert (false); if (range.fTop > iter->fBottom && range.fTop <= iter->fTop) EmAssert (false); ++iter; } #endif // Add the range to the list. gStackList.push_back (range); } /*********************************************************************** * * FUNCTION: EmPalmOS::SetCurrentStack * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::SetCurrentStack (const StackRange& range) { // Record the low-water mark of the previous stack. StackList::iterator iter = gStackList.begin (); while (iter != gStackList.end ()) { if (gStackHigh == iter->fTop) { iter->fLowWaterMark = gStackLowWaterMark; if (gStackHigh == gBootStack.fTop) gBootStack.fLowWaterMark = gStackLowWaterMark; if (gStackHigh == gKernelStack.fTop) gKernelStack.fLowWaterMark = gStackLowWaterMark; if (gStackHigh == gInterruptStack.fTop) gInterruptStack.fLowWaterMark = gStackLowWaterMark; break; } ++iter; } // Determine the amount to test against when determining if we are // close to overflowing the stack. For applications, let's set this // to 50 bytes. For the kernel stack, set it to zero; some Palm OS's // get damn close to the end of the kernel stack (the Palm VII ROM gets // within 8 bytes!). const int kAppStackSlush = 50; const int kKernelStackSlush = 0; int stackSlush = kAppStackSlush; if (range == gKernelStack) stackSlush = kKernelStackSlush; gStackHigh = range.fTop; gStackLowWaterMark = range.fLowWaterMark; gStackLowWarn = range.fBottom + kInterruptOverhead + stackSlush; gStackLow = range.fBottom + kInterruptOverhead; } /*********************************************************************** * * FUNCTION: EmPalmOS::ForgetStack * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::ForgetStack (emuptr stackBottom) { StackList::iterator iter = gStackList.begin (); while (iter != gStackList.end ()) { // If the pointer is in the *middle* of a stack, that's not good. if (stackBottom > iter->fBottom && stackBottom < iter->fTop) EmAssert (false); // If the pointer is to the beginning of the stack, we've found // the one we want to remove. if (stackBottom == iter->fBottom) { gStackList.erase (iter); if (stackBottom == gBootStack.fBottom) { #define TRACK_BOOT_ALLOCATION 0 #if TRACK_BOOT_ALLOCATION LogAppendMsg ("===== Forgetting Boot Stack, top = 0x%08X, low water mark: 0x%08X =====", gBootStack.fTop, gBootStack.fLowWaterMark); #endif gBootStack = StackRange (); } return; } ++iter; } } /*********************************************************************** * * FUNCTION: EmPalmOS::ForgetStacksIn * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::ForgetStacksIn (emuptr start, uint32 range) { StackList::iterator iter = gStackList.begin (); while (iter != gStackList.end ()) { if ((iter->fBottom >= start) && (iter->fTop <= (start + range))) { gStackList.erase (iter); if (start == gBootStack.fBottom) { #define TRACK_BOOT_ALLOCATION 0 #if TRACK_BOOT_ALLOCATION LogAppendMsg ("===== Forgetting Boot Stack, top = 0x%08X, low water mark: 0x%08X =====", gBootStack.fTop, gBootStack.fLowWaterMark); #endif gBootStack = StackRange (); } // We munged the collection; start over. iter = gStackList.begin (); continue; } ++iter; } } /*********************************************************************** * * FUNCTION: EmPalmOS::GetBootStack * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ StackRange EmPalmOS::GetBootStack (void) { return gBootStack; } /*********************************************************************** * * FUNCTION: EmPalmOS::IsInStack * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::IsInStack (emuptr addr) { return addr >= gStackLow && addr < gStackHigh; } /*********************************************************************** * * FUNCTION: EmPalmOS::IsInStackBlock * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::IsInStackBlock (emuptr addr) { return addr >= gCPU->GetSP () && addr < gStackHigh; } /*********************************************************************** * * FUNCTION: EmPalmOS::GenerateStackCrawl * * DESCRIPTION: Starting with the current PC and A6, generate a list * of active functions. * * PARAMETERS: frameList - reference to the collection to receive * the results. * * RETURNED: Nothing * ***********************************************************************/ void EmPalmOS::GenerateStackCrawl (EmStackFrameList& frameList) { // Make sure we have access to all of memory, in case A6 is bogus // or used for some purpose other than frame links. CEnableFullAccess munge; // Clear out the stack crawl list. frameList.clear (); // Push the initial stack frame onto the stack. This consists // of the current PC and A6 values. EmStackFrame frame; frame.fAddressInFunction = m68k_getpc (); frame.fA6 = m68k_areg (regs, 6); frameList.push_back (frame); // If A6 is odd or not in the current stack, stop the stack crawl. if (::IsOdd (frame.fA6) || !EmPalmOS::IsInStack (frame.fA6)) return; while (1) { emuptr oldA6 = frame.fA6; // Get the previous A6 and function from the stack. frame.fAddressInFunction = EmMemGet32 (oldA6 + 4); frame.fA6 = EmMemGet32 (oldA6); // If A6 is odd or not in the current stack, stop the stack crawl. if (::IsOdd (frame.fA6) || !EmPalmOS::IsInStack (frame.fA6)) return; // If the new A6 is not greater than the old A6, stop // the stack crawl. if (frame.fA6 <= oldA6) return; // If the return address is not valid, let's take a chance // that what's on the stack is . // We'd see this sequence if an exception has occurred and // and exception frame is on the stack. if (!EmMemCheckAddress (frame.fAddressInFunction, 2)) { frame.fAddressInFunction = EmMemGet32 (oldA6 + 6); // If the return address still doesn't look valid, // stop the stack crawl. if (!EmMemCheckAddress (frame.fAddressInFunction, 2)) { return; } } // Everything looks OK, push this information onto our stack. frameList.push_back (frame); } } // --------------------------------------------------------------------------- // ¥ EmPalmOS::PrefsChanged // --------------------------------------------------------------------------- // Respond to a preference change. static void PrvRespondToPrefsChange (PrefKeyType prefKey) { if (EmPatchState::UIInitialized ()) { if (::PrefKeysEqual (prefKey, kPrefKeyReportStrictIntlChecks) && EmPatchMgr::IntlMgrAvailable ()) { Preference pref (kPrefKeyReportStrictIntlChecks, false); ::IntlSetStrictChecks (*pref); } if (::PrefKeysEqual (prefKey, kPrefKeyReportOverlayErrors)) { Preference pref (kPrefKeyReportOverlayErrors, false); (void) ::FtrSet (omFtrCreator, omFtrShowErrorsFlag, *pref); } } } void EmPalmOS::PrefsChanged (PrefKeyType prefKey, void*) { if (gSession) { #if HAS_OMNI_THREAD if (gSession->InCPUThread ()) { ::PrvRespondToPrefsChange (prefKey); } else #endif { EmSessionStopper stopper (gSession, kStopOnSysCall); ::PrvRespondToPrefsChange (prefKey); } } } #pragma mark - /*********************************************************************** * * FUNCTION: EmPalmOS::HandleTrap15 * * DESCRIPTION: Handle a trap. Traps are of the format TRAP $F / $Axxx. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::HandleTrap15 (ExceptionNumber) { return EmPalmOS::HandleSystemCall (true); } /*********************************************************************** * * FUNCTION: Software::HandleJSR * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::HandleJSR (emuptr oldpc, emuptr dest) { #if HAS_PROFILING if (gProfilingEnabled) { StDisableMemoryCycleCounting stopper; Boolean skipProfiling = false; // In multi-segmented application generated by CodeWarrior, // inter-segment function calls are implemented by JSR'ing // to a jump table entry. This jump table entry contains // a JMP to the final location. Detect this case and emit // a function entry record for the final location, not the // jump table entry. const uint16 kOpcode_JMP_Abs32 = 0x4EF9; uint8* realMem = EmMemGetRealAddress (dest); if (EmMemDoGet16 (realMem) == kOpcode_JMP_Abs32) { dest = EmMemDoGet32 (realMem + 2); } else { //--------------------------------------------------------------------- // We don't want to process JSRs to a switch statement. Currently // CodeWarrior generates a switch statement block like this: // MOVE.L (A7)+,A0 | 205F // MOVE.L A0,A1 | 2248 // ADD.W (A0)+,A1 | D2D8 // CMP.W (A0)+,D0 | B058 // BGE.S *+$0004 ; 10C20F5C | 6C02 // JMP (A1) | 4ED1 // CMP.W (A0)+,D0 | B058 // BLE.S *+$0004 ; 10C20F62 | 6F02 // JMP (A1) | 4ED1 // MOVE.W (A0)+,D1 | 3218 // CMP.W (A0)+,D0 | B058 // BNE.S *+$0006 ; 10C20F6C | 6604 // ADD.W (A0),A0 | D0D0 // JMP (A0) | 4ED0 // ADDQ.W #$02,A0 | 5448 // DBF D1,*-$000A ; 10C20F64 | 51C9 FFF4 // JMP (A1) | 4ED1 //--------------------------------------------------------------------- skipProfiling = (EmMemDoGet32 (realMem) == 0x205F2248) && (EmMemDoGet32 (realMem + 4) == 0xD2D8B058) && (EmMemDoGet32 (realMem + 8) == 0x6C024ED1); } if (!skipProfiling) { ProfileFnEnter (dest, oldpc); } } #endif #if LOG_FUNCTION_CALLS // char fromName[80]; char toName[80]; // ::FindFunctionName (oldpc, fromName, NULL, NULL, 80); ::FindFunctionName (dest, toName, NULL, NULL, 80); EmStackFrameList stackCrawl; EmPalmOS::GenerateStackCrawl (stackCrawl); string dots ("|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-"); dots.resize (stackCrawl.size ()); // LogAppendMsg (">>> %s --> %s", fromName, toName); // LogAppendMsg ("--- System Call 0xA08D: %s", fromName, toName); LogAppendMsg ("--- Function Call: %s%s", dots.c_str (), toName); #endif return false; // We didn't completely handle it; do default handling. } /*********************************************************************** * * FUNCTION: EmPalmOS::HandleJSR_Ind * * DESCRIPTION: Check for SYS_TRAP_FAST calls. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::HandleJSR_Ind (emuptr oldpc, emuptr dest) { Bool handledIt = false; // Default to calling ROM. // inline asm SysTrapFast(Int trapNum) // { // MOVE.L struct(LowMemType.fixed.globals.sysDispatchTableP), A1 // MOVE.L ((trapNum-sysTrapBase)*4)(A1), A1 // JSR (A1) // call it // } // // #define SYS_TRAP_FAST(trapNum) // FIVEWORD_INLINE( // 0x2278, 0x0122, // 0x2269, (trapNum-sysTrapBase)*4, // 0x4e91) uint8* realMem = EmMemGetRealAddress (oldpc); if (EmMemDoGet16 (realMem) == 0x4e91 && EmMemDoGet16 (realMem - 4) == 0x2269 && EmMemDoGet16 (realMem - 8) == 0x2278) { handledIt = EmPalmOS::HandleSystemCall (false); } else { if (gBigROMEntry == EmMemNULL) { emuptr base = EmBankROM::GetMemoryStart (); // Check every romDelta (4K) for up to maxBigROMOffset //(256K) till we find the "Big" ROM const UInt32 romDelta = 4 * 1024L; // BigROM starts on a 4K boundary const UInt32 maxBigROMOffset = 256 * 1024L; // Give up looking past here UInt16 loops = maxBigROMOffset / romDelta; // How many loops to do emuptr bP = base + romDelta; while (loops--) { // Ignore older card headers that might have been // programmed in at a lower address (hdrVersion < 3) EmAliasCardHeaderType cardHdr (bP); UInt32 signature = cardHdr.signature; UInt16 hdrVersion = cardHdr.hdrVersion; if (signature == sysCardSignature && hdrVersion >= 3) { gBigROMEntry = cardHdr.resetVector; break; } bP += romDelta; } // See if we found it (may have hdrVersion < 3) if (gBigROMEntry == EmMemNULL) { EmAliasCardHeaderType smallCardHdr (base); UInt32 bigROMOffset; if (smallCardHdr.hdrVersion == 2) { bigROMOffset = smallCardHdr.bigROMOffset & 0x000FFFFF; } else { bigROMOffset = 0x3000; } EmAliasCardHeaderType bigCardHdr (base + bigROMOffset); gBigROMEntry = bigCardHdr.resetVector; } } if (dest == gBigROMEntry) { EmAssert (gSession); gSession->ScheduleReset (kResetSys); } } return handledIt; } /*********************************************************************** * * FUNCTION: EmPalmOS::HandleLINK * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::HandleLINK (int /*linkSize*/) { #if 0 Preference pref (kPrefKeyFillStack); if (*pref && linkSize < 0) { uae_memset (gCPU->GetSP (), 0xD5, -linkSize); } #endif } /*********************************************************************** * * FUNCTION: EmPalmOS::HandleRTS * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::HandleRTS (emuptr dest) { UNUSED_PARAM (dest); #if HAS_PROFILING if (gProfilingEnabled) { StDisableMemoryCycleCounting stopper; ProfileFnExit (dest, gCPU->GetPC () + 2); } #endif #if LOG_FUNCTION_CALLS char fromName[80]; char toName[80]; ::FindFunctionName (gCPU->GetPC(), fromName, NULL, NULL, 80); ::FindFunctionName (dest, toName, NULL, NULL, 80); LogAppendMsg ("<<< %s --> %s", fromName, toName); #endif #if 0 char fromName[80]; ::FindFunctionName (gCPU->GetPC(), fromName, NULL, NULL, 80); if (strcmp (fromName, "HwrADC") == 0) { // A7 points to return address // A7 + 4 points to UInt16 (command) // A7 + 6 points to pointer to result enum hwrADCCmdEnum { hwrADCCmdReadXYPos=0, // Read Digitizer X & Y Positions hwrADCCmdReadBatt, // Read Battery Level hwrADCCmdReadAux, // Read Battery Level hwrADCCmdReadTemp, // Read Temperature hwrADCCmdReadXPos, // Read Digitizer X Position hwrADCCmdReadYPos, // Read Digitizer Y Position hwrADCCmdReadZPos, // Read Digitizer Z Position (Pressure) hwrADCCmdReadXYZPos, // Read Digitizer X, Y & Z Positions hwrADCCmdPenIRQOn // Enable Pen IRQ } ; struct HwrADCReadResultType { UInt16 reserved1; //mVolts; // level in millivolts (2500 = 2.5 volts) UInt16 abs; // absolute level (0 -> 255) UInt16 aux; // absolute level (0 -> 255) }; const char* text[] = { "hwrADCCmdReadXYPos", "hwrADCCmdReadBatt", "hwrADCCmdReadAux", "hwrADCCmdReadTemp", "hwrADCCmdReadXPos", "hwrADCCmdReadYPos", "hwrADCCmdReadZPos", "hwrADCCmdReadXYZPos", "hwrADCCmdPenIRQOn", "unknown" }; UInt16 cmd = EmMemGet16 (gCPU->GetSP () + 4); emuptr ptr = EmMemGet32 (gCPU->GetSP () + 6); UInt16 reserved1 = EmMemGet16 (ptr + 0); UInt16 abs = EmMemGet16 (ptr + 2); UInt16 aux = EmMemGet16 (ptr + 4); LogAppendMsg ("HwrADC (%s), reserved1 = 0x%04X, abs = 0x%04X, aux = 0x%04X", text[cmd], (int) reserved1, (int) abs, (int) aux); } else if (strcmp (fromName, "HwrDockStatus") == 0) { UInt16 result = m68k_dreg (regs, 0); LogAppendMsg ("HwrDockStatus result = 0x%04X",(int) result); } #endif return false; // We didn't completely handle it; do default handling. } /*********************************************************************** * * FUNCTION: EmPalmOS::HandleRTE * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::HandleRTE (emuptr dest) { UNUSED_PARAM (dest); #if HAS_PROFILING if (gProfilingEnabled) { ProfileInterruptExit (dest); } #endif #if LOG_FUNCTION_CALLS char fromName[80]; char toName[80]; ::FindFunctionName (gCPU->GetPC(), fromName, NULL, NULL, 80); ::FindFunctionName (dest, toName, NULL, NULL, 80); LogAppendMsg ("<<< %s --> %s", fromName, toName); #endif return false; // We didn't completely handle it; do default handling. } /*********************************************************************** * * FUNCTION: EmPalmOS::HandleNewPC * * DESCRIPTION: The PC is about to be changed via a JSR, BSR, RTS, or * RTE. Check that the new address appears to be valid. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::HandleNewPC (emuptr dest) { // If the address is odd, then it's bad. if ((dest & 1) != 0) Errors::ReportErrInvalidPC (dest, kOddAddress); // if (!EmPatchState::UIInitialized ()) // return; // Ensure that the PC is in ROM or RAM. EmMemPutFunc longSetter = EmMemGetBank (dest).lput; if (longSetter == EmBankROM::SetLong || longSetter == EmBankFlash::SetLong) { if (!EmBankROM::ValidAddress (dest, 2)) { Errors::ReportErrInvalidPC (dest, kNotInCodeSegment); } } else if (longSetter == EmBankSRAM::SetLong) { } else if (longSetter == EmBankDRAM::SetLong) { if (dest < (256 + 693 * 4) /*MetaMemory::GetSysGlobalsEnd ()*/) { Errors::ReportErrInvalidPC (dest, kNotInCodeSegment); } } else if (longSetter == EmBankMapped::SetLong) { if (!EmBankMapped::ValidAddress (dest, 2)) { Errors::ReportErrInvalidPC (dest, kUnmappedAddress); } } else { Errors::ReportErrInvalidPC (dest, kUnmappedAddress); } } /*********************************************************************** * * FUNCTION: EmPalmOS::HandleNewSP * * DESCRIPTION: !!! This operation slows down Gremlins by about 10%; it * should be sped up or made an option. * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ void EmPalmOS::HandleNewSP (EmStackChangeType type) { if (type == kStackPointerChanged) { CheckStackPointerAssignment (); } else if (type == kStackPointerDecremented) { CheckStackPointerDecrement (); } else if (type == kStackPointerKernelStackHack) { CheckKernelStack (); } } #pragma mark - /*********************************************************************** * * FUNCTION: EmPalmOS::HandleSystemCall * * DESCRIPTION: . * * PARAMETERS: None. * * RETURNED: Nothing. * ***********************************************************************/ Bool EmPalmOS::HandleSystemCall (Bool fromTrap) { // ====================================================================== // First things first: if we need to break execution on the next call // to a system function, make sure that happens. // ====================================================================== EmAssert (gSession); EmAssert (gCPU68K); #if HAS_PROFILING int oldProfilingCounted = gProfilingCounted; gProfilingCounted = false; #endif // If the system call is being made by a TRAP $F, the PC has already // been bumped past the opcode. If being made with a JSR via the // SYS_TRAP_FAST macro, the PC has not been adjusted. Determine a // "pcAdjust" value that allows us to get back to the start of the // instruction that got us here. int pcAdjust = fromTrap ? 2 : 0; if (!gSession->IsNested () && gSession->GetBreakOnSysCall ()) { CEnableFullAccess munge; // Check the memory manager semaphore. If we're stopping on a // system call, it's most likely that we want to make some nested // Palm OS calls. Some of those calls may try to acquire the // memory manager semaphore. Therefore, make sure it's available. UInt32 memSemaphoreIDP = EmLowMem_GetGlobal (memSemaphoreID); EmAliascj_xsmb memSemaphoreID (memSemaphoreIDP); if (memSemaphoreID.xsmuse == 0) { // Check the current task. We don't want to break on a system // call if we're in the background. If we're breaking on a // system call, it's likely because some other part of Poser // wants to make OS calls. Occassionally, those OS calls result // in trying to switch to the UI task (see, for example, // PrvEditCardOptionsOK). If we're already in the UI task, then // that's OK, but if we're in a background task, an attempted // switch to the UI task will fail, due to Poser turning off // interrupts when it makes OS calls. SysKernelInfoType taskInfo; taskInfo.selector = sysKernelInfoSelCurTaskInfo; Err err = ::SysKernelInfo (&taskInfo); if (err == errNone) { if (taskInfo.param.task.tag == 'psys') { gCPU->SetPC (gCPU->GetPC () - pcAdjust); gSession->ScheduleSuspendSysCall (); #if HAS_PROFILING gProfilingCounted = oldProfilingCounted; #endif // Return true to say that everything has been handled. return true; } // If we took a stab at this and didn't find that we were // in the UI task, then call EvtWakeup. If our context is // one where an application has called EvtGetEvent with a // "sleep forever" timeout, then the current task will be // 'AMX ', and will stay that way. Calling EvtWakeup will // force an eventual switch to the UI task. // // Note that we wake-up the UI thread only if we're in the // root AMX thread. This prevents us from trying to call // it when the Palm context is one where a background task // is the current task. It's possible for that task to be // sleeping or something (the example we ran into was with // a background task calling SysDelay). EvtWakeup will try // to switch to the UI task, but will not return until the // UI task switches back to the background task. That will // never happen because interrupts are turned off during // all Poser calls into the OS (such as the call to // EvtWakeup). Thus, the only safe places to call // EvtWakeup are in the UI or AMX tasks. else if (taskInfo.param.task.tag == 0x414d5800) { ::EvtWakeup (); } } } } // ====================================================================== // Determine what ROM function is about to be called, and determine // the method by which it is being called. // ====================================================================== SystemCallContext context; Bool gotFunction = GetSystemCallContext ( gCPU->GetPC () - pcAdjust, context); // ====================================================================== // Validate the address for the ROM function we're about to call. // ====================================================================== if (!gotFunction) { #if HAS_PROFILING gProfilingCounted = oldProfilingCounted; #endif if (context.fError == kError_UnimplementedTrap) { Errors::ReportErrUnimplementedTrap (context); } else if (context.fError == kError_InvalidLibraryRefNum) { Errors::ReportErrInvalidRefNum (context); } // We should never get here. context.fError should always equal // one of those two error codes, and those two functions we call // should never return (they should throw exceptions). EmAssert (false); } // ====================================================================== // Record what function we're calling. // ====================================================================== if (!gSession->IsNested () && LogSystemCalls ()) { char name [sysPktMaxNameLen]; strcpy (name, ::GetTrapName (context, true)); EmStackFrameList stackCrawl; EmPalmOS::GenerateStackCrawl (stackCrawl); string dots ("|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-"); dots.resize (stackCrawl.size ()); LogAppendMsg ("--- System Call 0x%04X: %s%s.", (long) context.fTrapWord, dots.c_str (), name); } // ====================================================================== // Let the debugger have a crack at it. It may want to do a "break // on ATrap" thingy. If so, it will return true. Otherwise, // if no action is necessary, it will return false. // ====================================================================== if (!gSession->IsNested () && Debug::HandleSystemCall (context)) { // Return true to say that everything has been handled. #if HAS_PROFILING gProfilingCounted = oldProfilingCounted; #endif return true; } // ====================================================================== // Check for functions unsupported on ARM. ProscribedFunction // returns true if function is not supported. // ====================================================================== if ((Memory::IsPCInRAM () && !MetaMemory::InRAMOSComponent (context.fPC)) && ::ProscribedFunction (context)) { gSession->ScheduleDeferredError ( new EmDeferredErrProscribedFunction (context)); } // ====================================================================== // Tell the profiler that we're entering a function. // ====================================================================== #if HAS_PROFILING // This ProfileFnEnter will show the function call within the trap handler // exception, which gives us a nice breakdown of which traps are being // called and frequency. if (gProfilingEnabled && context.fViaTrap) { ProfileFnEnter (context.fTrapWord, context.fNextPC); } #endif // ====================================================================== // If this trap is patched, let the patch handler handle the patch. // ====================================================================== CallROMType result = EmPatchMgr::HandleSystemCall (context); // ====================================================================== // If we completely handled the function in head and tail patches, tell // the profiler that we exited the function and get out of here. // ====================================================================== if (result == kSkipROM) { #if HAS_PROFILING if (gProfilingEnabled) { ProfileFnExit (context.fNextPC, context.fTrapWord); } #endif // Set the PC to point past the instructions that got us here. gCPU->SetPC (context.fNextPC); // Return true to say that everything has been handled. #if HAS_PROFILING gProfilingCounted = oldProfilingCounted; #endif return true; } // ====================================================================== // If we're profiling, don't dispatch to the ROM function outselves. // We want the ROM to do it so that we get accurate dispatch times. // ====================================================================== #if HAS_PROFILING // Don't do native dispatching if the profiler is on! if (gProfilingEnabled) { // Return false to do default exception handler processing. gProfilingCounted = oldProfilingCounted; return false; } #endif #if HAS_PROFILING gProfilingCounted = oldProfilingCounted; #endif // ====================================================================== // Otherwise, let's run the trap dispatcher native. This gives // us about a 10-20% speed-up. // ====================================================================== // Push the return address onto the stack. Subtract 4 from the stack, // and then store the appropriate return address. gCPU->SetSP (gCPU->GetSP () - 4); CHECK_STACK_POINTER_DECREMENT (); EmMemPut32 (gCPU->GetSP (), context.fNextPC); // Change to the new PC. If possible, use the fully dereferenced destination // address. This speeds things up for libraries and sub-dispatched managers // (like Floating Point, International, Text, and Overlay managers). emuptr destPC = context.fDestPC2; if (Debug::BreakpointInstalled ()) { destPC = context.fDestPC1; } EmPalmOS::HandleNewPC (destPC); gCPU->SetPC (destPC); // Return true to say that everything has been handled. return true; } EmStream& operator << (EmStream& s, const StackRange& range) { s << range.fBottom; s << range.fTop; s << range.fLowWaterMark; return s; } EmStream& operator >> (EmStream& s, StackRange& range) { s >> range.fBottom; s >> range.fTop; s >> range.fLowWaterMark; return s; }