lawless-legends/Platform/Apple/tools/jace/src/main/java/jace/core/RAM.java

390 lines
13 KiB
Java

/**
* Copyright 2024 Brendan Robert
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package jace.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import jace.Emulator;
import jace.apple2e.SoftSwitches;
import jace.config.Reconfigurable;
/**
* RAM is a 64K address space of paged memory. It also manages sets of memory
* listeners, used by I/O as well as emulator add-ons (and cheats). RAM also
* manages cards in the emulator because they are tied into the MMU memory
* bankswitch logic.
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public abstract class RAM implements Reconfigurable {
public PagedMemory activeRead;
public PagedMemory activeWrite;
private final Set<RAMListener> listeners = new ConcurrentSkipListSet<>();
@SuppressWarnings("unchecked")
private final Set<RAMListener>[] listenerMap = (Set<RAMListener>[]) new Set[256];
@SuppressWarnings("unchecked")
private final Set<RAMListener>[] ioListenerMap = (Set<RAMListener>[]) new Set[256];
@SuppressWarnings("unchecked")
public Optional<Card>[] cards = (Optional<Card>[]) new Optional[8];
// card 0 = 80 column card firmware / system rom
public int activeSlot = 0;
/**
* Creates a new instance of RAM
*
* @param computer
*/
public RAM() {
for (int i = 0; i < 8; i++) {
cards[i] = Optional.empty();
}
refreshListenerMap();
}
public void setActiveCard(int slot) {
if (activeSlot != slot) {
activeSlot = slot;
configureActiveMemory();
} else if (!SoftSwitches.CXROM.getState()) {
configureActiveMemory();
}
}
public int getActiveSlot() {
return activeSlot;
}
public Optional<Card>[] getAllCards() {
return cards;
}
public Optional<Card> getCard(int slot) {
if (slot >= 1 && slot <= 7) {
return cards[slot];
}
return Optional.empty();
}
public void addCard(Card c, int slot) {
removeCard(slot);
cards[slot] = Optional.of(c);
c.setSlot(slot);
c.attach();
configureActiveMemory();
}
public void removeCard(Card c) {
removeCard(c.getSlot());
}
public void removeCard(int slot) {
cards[slot].ifPresent(Card::suspend);
cards[slot].ifPresent(Card::detach);
cards[slot] = Optional.empty();
}
abstract public void configureActiveMemory();
public void copyFrom(RAM other) {
cards = other.cards;
activeSlot = other.activeSlot;
listeners.addAll(other.listeners);
refreshListenerMap();
configureActiveMemory();
}
public void write(int address, byte b, boolean generateEvent, boolean requireSynchronization) {
byte[] page = activeWrite.getMemoryPage(address);
if (page == null) {
if (generateEvent) {
callListener(RAMEvent.TYPE.WRITE, address, 0, b, requireSynchronization);
}
} else {
int offset = address & 0x0FF;
byte old = page[offset];
if (generateEvent) {
page[offset] = callListener(RAMEvent.TYPE.WRITE, address, old, b, requireSynchronization);
} else {
page[offset] = b;
}
}
}
public void writeWord(int address, int w, boolean generateEvent, boolean requireSynchronization) {
write(address, (byte) (w & 0x0ff), generateEvent, requireSynchronization);
write(address + 1, (byte) ((w >> 8) & 0x0ff), generateEvent, requireSynchronization);
}
public byte readRaw(int address) {
// if (address >= 65536) return 0;
return activeRead.getMemoryPage(address)[address & 0x0FF];
}
public byte read(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSyncronization) {
// if (address >= 65536) return 0;
byte value = activeRead.getMemoryPage(address)[address & 0x0FF];
// if (triggerEvent || ((address & 0x0FF00) == 0x0C000)) {
// if (triggerEvent || (address & 0x0FFF0) == 0x0c030) {
if (triggerEvent) {
value = callListener(eventType, address, value, value, requireSyncronization);
}
return value;
}
public int readWordRaw(int address) {
int lsb = 0x00ff & readRaw(address);
int msb = (0x00ff & readRaw(address + 1)) << 8;
return msb + lsb;
}
public int readWord(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSynchronization) {
int lsb = 0x00ff & read(address, eventType, triggerEvent, requireSynchronization);
int msb = (0x00ff & read(address + 1, eventType, triggerEvent, requireSynchronization)) << 8;
return msb + lsb;
}
private synchronized void mapListener(RAMListener l, int address) {
if ((address & 0x0FF00) == 0x0C000) {
int index = address & 0x0FF;
Set<RAMListener> ioListeners = ioListenerMap[index];
if (ioListeners == null) {
ioListeners = new ConcurrentSkipListSet<>();
ioListenerMap[index] = ioListeners;
}
ioListeners.add(l);
} else {
int index = (address >> 8) & 0x0FF;
Set<RAMListener> otherListeners = listenerMap[index];
if (otherListeners == null) {
otherListeners = new ConcurrentSkipListSet<>();
listenerMap[index] = otherListeners;
}
otherListeners.add(l);
}
}
private void addListenerRange(RAMListener l) {
if (l.getScope() == RAMEvent.SCOPE.ADDRESS) {
mapListener(l, l.getScopeStart());
} else {
int start = l.getScopeStart();
int end = l.getScopeEnd();
if (l.getScope() == RAMEvent.SCOPE.ANY) {
start = 0;
end = 0x0FFFF;
}
for (int i = start; i <= end; i++) {
mapListener(l, i);
}
}
}
private void refreshListenerMap() {
// Wipe out existing maps
for (int i = 0; i < 256; i++) {
listenerMap[i] = null;
ioListenerMap[i] = null;
}
listeners.forEach(this::addListenerRange);
}
public RAMListener observeOnce(String observerationName, RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
}
@Override
protected void doEvent(RAMEvent e) {
handler.handleEvent(e);
unregister();
}
});
}
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
}
@Override
protected void doEvent(RAMEvent e) {
handler.handleEvent(e);
}
});
}
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
}
@Override
protected void doEvent(RAMEvent e) {
if (isAuxFlagCorrect(e, auxFlag)) {
handler.handleEvent(e);
}
}
});
}
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(addressStart);
setScopeEnd(addressEnd);
}
@Override
protected void doEvent(RAMEvent e) {
handler.handleEvent(e);
}
});
}
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(addressStart);
setScopeEnd(addressEnd);
}
@Override
protected void doEvent(RAMEvent e) {
if (isAuxFlagCorrect(e, auxFlag)) {
handler.handleEvent(e);
}
}
});
}
private boolean isAuxFlagCorrect(RAMEvent e, Boolean auxFlag) {
if (e.getAddress() < 0x0100) {
return SoftSwitches.AUXZP.getState() == auxFlag;
} else if (e.getAddress() >= 0x0C000 && e.getAddress() <= 0x0CFFF) {
// I/O page doesn't care about the aux flag
return true;
} else return auxFlag == null || SoftSwitches.RAMRD.getState() == auxFlag;
}
public RAMListener addListener(final RAMListener l) {
if (l == null) {
return l;
}
if (listeners.contains(l)) {
removeListener(l);
}
listeners.add(l);
Emulator.whileSuspended((c)->addListenerRange(l));
return l;
}
public RAMListener addExecutionTrap(String observerationName, int address, Consumer<RAMEvent> handler) {
RAMListener listener = new RAMListener(observerationName, RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
}
@Override
protected void doEvent(RAMEvent e) {
handler.accept(e);
}
};
addListener(listener);
return listener;
}
public void removeListener(final RAMListener l) {
if (l == null) {
return;
}
if (!listeners.contains(l)) {
return;
}
listeners.remove(l);
Emulator.whileSuspended(c->refreshListenerMap());
}
private byte _callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue) {
Set<RAMListener> activeListeners;
if ((address & 0x0FF00) == 0x0C000) {
activeListeners = ioListenerMap[address & 0x0FF];
if (activeListeners == null && t.isRead()) {
return Emulator.withComputer(c->c.getVideo().getFloatingBus(), (byte) 0);
}
} else {
activeListeners = listenerMap[(address >> 8) & 0x0ff];
}
if (activeListeners != null && !activeListeners.isEmpty()) {
RAMEvent e = new RAMEvent(t, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, address, oldValue, newValue);
activeListeners.forEach((l) -> l.handleEvent(e));
if (!e.isIntercepted() && (address & 0x0FF00) == 0x0C000) {
return Emulator.withComputer(c->c.getVideo().getFloatingBus(), (byte) 0);
}
return (byte) e.getNewValue();
}
return (byte) newValue;
}
public byte callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue, boolean requireSyncronization) {
if (requireSyncronization && Emulator.withComputer(c->c.getMotherboard().isDeviceThread(), false)) {
AtomicInteger returnValue = new AtomicInteger();
Emulator.withComputer(computer -> {
computer.getCpu().whileSuspended(()-> returnValue.set(_callListener(t, address, oldValue, newValue)));
_callListener(t, address, oldValue, newValue);
});
return (byte) returnValue.get();
} else {
return _callListener(t, address, oldValue, newValue);
}
}
abstract protected void loadRom(String path) throws IOException;
abstract public void attach();
public void detach() {
detachListeners.forEach(Runnable::run);
}
abstract public void performExtendedCommand(int i);
abstract public String getState();
abstract public void resetState();
List<Runnable> detachListeners = new ArrayList<>();
public void onDetach(Runnable r) {
detachListeners.add(r);
}
}