Files
AppleWin/source/Debugger/BreakpointCard.cpp
2025-10-18 15:39:11 +01:00

265 lines
7.1 KiB
C++

/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2025, Tom Charlesworth, Michael Pohoreski
AppleWin 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.
AppleWin 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 AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
Breakpoint card
---------------
For debugger breakpoint regression tests.
C0n0 (R) : Status
b7: BP match
b6: BP mismatch
b1: FIFO full
b0: FIFO empty
C0n1 (R) : Cmd: Reset
Flush FIFO
NB. don't change intercept mode
C0n2 (R) : Cmd: Intercept BP by card
C0n3 (R) : Cmd: Intercept BP by debugger
C0nX (R) : ID byte $0X (except when X=0)
C0nX (W) : FIFO
FIFO is 32 x 6 bytes deep
where: 6-byte BP-set = {type.b, addrStart.w, addrEnd.w, access.b}
Any r/w clears the IRQ
RESET clears the IRQ, flushes FIFO and sets: Intercept BP by card
Operation:
. Cmd:Reset flushes FIFO & sets Status.empty=1 (all other bits are zero)
. Cmd:Intercept BP by card
. Write FIFO with multiple BP-sets for test code
- these are the expected BP hit results
. Status.full=1 after writing 32 BP-sets
. Execute test code
. AppleWin debugger:
- Cmd:Intercept BP by card: when active, then debugger doesn't break on BP match
- Instead debugger hands off BP to Breakpoint card (only 'bp' & 'bpm[r|w]')
. Remove BP-set from front of FIFO
- Check if it matches/mismatches and set status accordingly
. Delay 1 cycle & assert IRQ
. CPU vectors to IRQ handler
. Reading Status clears IRQ
*/
#include "StdAfx.h"
#include "CardManager.h"
#include "Core.h"
#include "CPU.h"
#include "Debug.h"
#include "BreakpointCard.h"
#include "Memory.h"
#include "YamlHelper.h"
BreakpointCard::BreakpointCard(UINT slot) :
Card(CT_BreakpointCard, slot),
m_syncEvent(slot, 0, SyncEventCallback) // use slot# as "unique" id for Disk2InterfaceCards
{
Reset(true);
}
BreakpointCard::~BreakpointCard()
{
if (m_syncEvent.m_active)
g_SynchronousEventMgr.Remove(m_syncEvent.m_id);
}
void BreakpointCard::Reset(const bool powerCycle)
{
m_interceptBPByCard = false;
InterceptBreakpoints(m_slot, nullptr);
ResetState();
if (!powerCycle) // if called by ctor: CriticalSection not created yet
CpuIrqDeassert(IS_BREAKPOINTCARD);
}
void BreakpointCard::InitializeIO(LPBYTE pCxRomPeripheral)
{
RegisterIoHandler(m_slot, &BreakpointCard::IORead, &BreakpointCard::IOWrite, IO_Null, IO_Null, this, NULL);
}
BYTE __stdcall BreakpointCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
const UINT slot = ((addr & 0xff) >> 4) - 8;
BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);
CpuIrqDeassert(IS_BREAKPOINTCARD);
const BYTE reg = addr & 0xf;
if (reg == 0)
{
uint8_t otherStatus = pCard->m_BP_FIFO.empty() ? kEmpty : 0;
return pCard->m_status | otherStatus;
}
else if (reg == 1) // Cmd: Reset
{
pCard->ResetState();
}
else if (reg == 2) // Cmd: Intercept BP by card
{
pCard->m_interceptBPByCard = true;
InterceptBreakpoints(slot, BreakpointCard::CbFunction);
}
else if (reg == 3) // Cmd: Intercept BP by debugger
{
pCard->m_interceptBPByCard = false;
InterceptBreakpoints(slot, nullptr);
}
return addr & 0x0f; // ID bytes [$01-$0F]
}
BYTE __stdcall BreakpointCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
const UINT slot = ((addr & 0xff) >> 4) - 8;
BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);
CpuIrqDeassert(IS_BREAKPOINTCARD);
pCard->m_BPSet[pCard->m_BPSetIdx++] = value;
if (pCard->m_BPSetIdx == kNumParams)
{
pCard->m_BPSetIdx = 0;
if (!(pCard->m_status & kFull))
{
INTERCEPTBREAKPOINT bpSet;
bpSet.type = pCard->m_BPSet[0];
bpSet.addrStart = (pCard->m_BPSet[2] << 8) | pCard->m_BPSet[1];
bpSet.addrEnd = (pCard->m_BPSet[4] << 8) | pCard->m_BPSet[3];
bpSet.access = pCard->m_BPSet[5];
pCard->m_BP_FIFO.push(bpSet);
}
if (pCard->m_BP_FIFO.size() == kFIFO_SIZE)
pCard->m_status |= kFull;
}
return 0;
}
void BreakpointCard::CbFunction(uint8_t slot, INTERCEPTBREAKPOINT interceptBreakpoint)
{
BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);
pCard->m_deferred = interceptBreakpoint;
// Defer processing the breakpoint by 1 opcode (ie. 1 cycle), otherwise this happens:
// . BP occurs at <addr>, but opcode hasn't executed yet
// . Breakpoint card aserts IRQ, and IRQ is taken by 6502
// . 6502 executes ISR, and returns to <addr>
// . BP occurs at <addr>...
pCard->m_syncEvent.m_cyclesRemaining = 1; // Next opcode
g_SynchronousEventMgr.Insert(&pCard->m_syncEvent);
}
int BreakpointCard::SyncEventCallback(int id, int cycles, ULONG uExecutedCycles)
{
BreakpointCard& card = dynamic_cast<BreakpointCard&>(GetCardMgr().GetRef(id));
card.Deferred(card.m_deferred.type, card.m_deferred.addrStart, card.m_deferred.addrEnd, card.m_deferred.access);
return 0; // Don't repeat event
}
void BreakpointCard::Deferred(uint8_t type, uint16_t addrStart, uint16_t addrEnd, uint8_t access)
{
m_status &= ~(kMatch | kMismatch);
CpuIrqAssert(IS_BREAKPOINTCARD);
if (m_BP_FIFO.empty())
{
m_status |= kMismatch;
return;
}
const INTERCEPTBREAKPOINT bpSet = m_BP_FIFO.front();
m_BP_FIFO.pop();
m_status &= ~kFull;
if (bpSet.type != type)
{
m_status |= kMismatch;
return;
}
switch (type)
{
case BPTYPE_PC:
case BPTYPE_MEM:
if ((bpSet.addrStart != addrStart) || (bpSet.access != access))
{
m_status |= kMismatch;
break;
}
m_status |= kMatch;
break;
case BPTYPE_DMA:
{
bool validAddr = ((addrStart >= bpSet.addrStart && addrStart < bpSet.addrEnd) ||
(bpSet.addrStart >= addrStart && bpSet.addrStart < addrEnd));
if (!validAddr || (bpSet.access != access))
{
m_status |= kMismatch;
break;
}
m_status |= kMatch;
}
break;
case BPTYPE_UNKNOWN:
default:
m_status |= kMismatch;
break;
}
}
//===========================================================================
static const UINT kUNIT_VERSION = 1;
const std::string& BreakpointCard::GetSnapshotCardName(void)
{
static const std::string name("Breakpoint");
return name;
}
void BreakpointCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION);
YamlSaveHelper::Label unit(yamlSaveHelper, "%s: null\n", SS_YAML_KEY_STATE);
// NB. No state for this card
}
bool BreakpointCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
{
if (version < 1 || version > kUNIT_VERSION)
ThrowErrorInvalidVersion(version);
return true;
}