First public release

Squashed for release at v0.1
This commit is contained in:
Matt Evans 2024-03-31 02:29:28 +01:00
commit 563626a382
27 changed files with 4390 additions and 0 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "external/Musashi"]
path = external/Musashi
url = https://github.com/evansm7/Musashi.git

89
Makefile Normal file
View file

@ -0,0 +1,89 @@
# Makefile for umac
#
# Builds Musashi as submodule, unix_main as SDL2 test application.
#
# Copyright 2024 Matt Evans
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
DEBUG ?= 0
SOURCES = $(wildcard src/*.c)
MUSASHI = external/Musashi/
MUSASHI_SRC = $(MUSASHI)/m68kcpu.c $(MUSASHI)/m68kdasm.c $(MUSASHI)/m68kops.c $(MUSASHI)/softfloat/softfloat.c
MY_OBJS = $(patsubst %.c, %.o, $(SOURCES))
MUSASHI_OBJS = $(patsubst %.c, %.o, $(MUSASHI_SRC))
OBJS = $(MY_OBJS) $(MUSASHI_OBJS)
SDL_CFLAGS = $(shell sdl2-config --cflags)
SDL_LIBS = $(shell sdl2-config --libs)
LINKFLAGS =
LIBS = $(SDL_LIBS) -lm
INCLUDEFLAGS = -Iinclude/ -I$(MUSASHI) $(SDL_CFLAGS) -DMUSASHI_CNF=\"../include/m68kconf.h\"
INCLUDEFLAGS += -DENABLE_DASM=1
CFLAGS = $(INCLUDEFLAGS) -Wall -Wextra -pedantic -DSIM
ifeq ($(DEBUG),1)
CFLAGS += -Og -g -ggdb -DDEBUG
endif
all: main
$(MUSASHI_SRC): $(MUSASHI)/m68kops.h
$(MUSASHI)/m68kops.c $(MUSASHI)/m68kops.h:
make -C $(MUSASHI) m68kops.c m68kops.h && ./tools/decorate_ops.py $(MUSASHI)/m68kops.c tools/fn_hot200.txt
prepare: $(MUSASHI)/m68kops.c $(MUSASHI)/m68kops.h
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
main: $(OBJS)
@echo Linking $(OBJS)
$(CC) $(LINKFLAGS) $^ $(LIBS) -o $@
clean:
make -C $(MUSASHI) clean
rm -f $(MY_OBJS) main
################################################################################
# Mac driver sources (no need to generally rebuild
# Needs a m68k-linux-gnu binutils, but not GCC.
M68K_CROSS ?= m68k-linux-gnu-
M68K_AS = $(M68K_CROSS)as
M68K_LD = $(M68K_CROSS)ld
M68K_OBJCOPY = $(M68K_CROSS)objcopy
include/sonydrv.h: sonydrv.bin
xxd -i < $< > $@
.PHONY: sonydrv.bin
sonydrv.bin: macsrc/sonydrv.S
@# Yum hacky
cpp $< | $(M68K_AS) -o sonydrv.o
$(M68K_LD) sonydrv.o -o sonydrv.elf -Ttext=0
$(M68K_OBJCOPY) sonydrv.elf -O binary --keep-section=.text $@

273
README.md Normal file
View file

@ -0,0 +1,273 @@
# Micro Mac (umac)
v0.1 15 June 2024
This is a minimalist Apple Macintosh 128K/512K emulator. It uses the
_Musashi_ 68K interpreter and lashes the absolute minimum in hardware
emulation around it so as to boot and run basic apps. It's been
tested with System 2.0 up to System 3.2, and runs MacWrite, MacDraw, and
Missile Command.
You can write, draw, and unwind with an apocalyptic game. Live well.
![Desktop screenshot](doc/umac_sys32_desktop.png)
This was written as one of the old "hey I wonder how hard it'd be to"
exercises; bit of fun, but not intended as a replacement for existing
emulators. But, it might inspire others working on similar (better)
projects, and as a basis to explore these early but innovative
machines.
This grew without a plan, just playing around with the Mac128K ROM: it
turns out that there's _very_ little HW emulation required to get to
an Unhappy Mac screen, or even an attempt to boot via FDD
(disc-question-mark screen). Then, you discover IWM is way painful to
emulate, and spend 80% of the time working around that almost all
Mac emulators immediately patch the ROM to insert a paravirt block
driver over the top of the IWM driver, circumventing the problem.
That's what _MicroMac_ does, too.
This emulates the following hardware:
* VIA A/B GPIO ports, and IRQs (1Hz and Vsync)
* VIA shift register for keyboard
* SCC DCD pin change interrupts, for mouse
* Paravirtualised disc storage
* Defaults to 128K of RAM, but will run as a Mac 512K by changing
a `#define` (`RAM_SIZE`).
There's no emulation for:
* IWM/realistic floppy drives
* More than one disc, or runtime image-switching
* Sound (a lot of work for a beep)
* VIA timers (Space Invaders runs too fast, probably because of this)
* Serial/printer/Appletalk
* Framebuffer switching: the Mac supports double-buffering by moving
the base of screen memory via the VIA (ha), but I haven't seen
anything using it. Easy to add.
* Disc writes (easy to enable, untested). I didn't need to save my
MacWrite essays so far.
The emulator is structured so as to be easily embeddable in other
projects. You initialise it, and pass in UI events (such as
keyboard/mouse), and read video from the framebuffer:
```
umac_init(pointer_to_ram, pointer_to_patched_rom,
pointer_to_struct_describing_mmaped_disc_images);
while (happy) {
if (one_second_passed)
umac_1hz_event();
if (vsync_happened_60Hz_kthx) {
umac_vsync_event();
update_UI_video_from(pointer_to_ram + umac_get_fb_offset());
}
if (keyboard_event_happened)
umac_kbd_event(mac_scancode);
if (mouse_movement_happened)
umac_mouse(delta_x, delta_y, button_state);
umac_loop();
}
```
A simple SDL2-based frontend builds on Linux.
# Prerequisites
To build on Linux, you'll need `SDL2` installed (packaged as
`libsdl2-dev` on Ubuntu).
Musashi is included as a submodule, currently a custom branch with
some optimisations for static/tiny/fast builds. `git submodule update --init`
You'll then need some Mac artefacts. Because we need to patch the ROM
a little, we require specific versions. Currently the only supported
ROM is _Mac Plus V3 ROM_, checksum `4d1f8172`.
(Note this makes this a _Mac 128Ke_, fancy!)
Then, get a boot disk. Any System up to 3.2 should work fine (though
I don't think I've tried 1.0). I just tested a random German System 3.2 disc
from WinWorld and it works fine. You might want to use Mini vMac or
Basilisk (i.e. a proper emulator) to prepare a disc image with some
apps to run... MacDraw!
It doesn't have to be a specific size. A 400K or 800K floppy image
works. Make sure it's a raw image; the first two bytes should be the
chars 'LK'. Some emulators append a header (which can be `dd`'d off).
# Build
```
make
```
No surprises here. No autoconf either. :D You can add a `DEBUG=1` to
make to compile in debug spew.
This will configure and build _Musashi_, umac, and `unix_main.c` as
the SDL2 frontend. The _Musashi_ build generates a few files
internally:
* `m68kops.c` is generated from templates in `m68k_in.c`: this
"multiplies out" N instructions by the 200,000 addressing modes of
68K, generating specialised code for each individual opcode.
* Using a custom/new Musashi build option, a large (64K pointers)
opcode lookup table is generated, 16-bit 68K opcode is generated
at build time, as `m68ki_static_instruction_jump_table`. This was
previously generated at runtime, i.e. used up RAM.
After the _Musashi_ prepare step, `tools/decorate_ops.py` does an
in-place update of `m68kops.c` to decorate some of the opcode
functions with `M68K_FAST_FUNC`. This macro does nothing by default,
but can be defined to apply a function attribute. If this project is
being built in the RP2040 Pico environment, the functions gain an
attribute to place them in RAM instead of flash making them much
faster.
Some low-quality (so uncommitted) and undocumented profiling code was
used to generate the `tools/fn_hot200.txt` list of the 200 most
frequently-used 68K opcodes. This was generated by profiling a System
3.2 boot, using MacWrite and Missile Command for a bit. :D Out of
1967 opcodes, these hottest 200 opcodes represent 98% of the dynamic
execution. (See _RISC_.)
# Running
```
./main -r <path_to_MacPlusV3_rom> -d <path_to_disc_image>
```
The RAM is actually a memory-mapped file, which can be useful for
(basic) debugging: as the emulator runs, you can access the file and
see the current state. For example, you can capture screenshots from
screen memory (see `tools/mem2scr.c`).
For a `DEBUG` build, add `-i` to get a disassembly trace of execution.
Finally, the `-W <file>` parameter writes out the ROM image after
patches are applied. This can be useful to prepare a ROM image for
embedded builds, so as to avoid having to patch the ROM at runtime.
That then means the ROM can be in immutable storage (e.g. flash),
saving precious RAM space.
# Hacks/Technical details
If you're writing an emulator for an olden Mac, some
pitfalls/observations:
* The ROM overlay at reset changes the memory map, and the address
decoding for read/write functions has to consider this. Overlay
is on for only a handful of instructions setting up RAM exception
vector tables so, for performance to avoid checking on every
access, there should be two versions of memory read/write
functions that are selected when the overlay changes. This has
been implemented only for instruction/opcode fetch.
* IWM is a total pig to emulate, it turns out. There's some kind of
servo loop controling the variable rotation speed via PWM (a DAC!),
and the driver/IWM vary it for a particular track until the syncs
look about right... too little fun for a Sunday.
* The disc device emulation is a cut-down version of Basilisk II's
disc emulation code: A custom 68K driver (in `macsrc/sonydrv.S`,
based on B2's driver code) is patched into the ROM, and makes
accesses to a magic `PV_SONY_ADDR` address. These are then
trapped so that when the Mac OS makes a driver call
(e.g. `Open()`, `Prime()`, `Control()`, `Status()`) the call is
routed to host-side C code in `disc.c`. The emulation code
doesn't support any of the advanced things a driver can be asked
to do, such as formatting just read/[not yet write] of a block.
When the disc is asked to be ejected, a `umac` callback is called;
currently this just exits the emulator. The beginnings of
multi-disc support are there, but not enabled again, bare
minimum to get the thing to boot.
* The high-precision VIA timers aren't generally used by the OS,
only by sound (not supported) and the IWM driver (not used).
They're not emulated.
* The OS's keyboard ISR is easy to confuse by sending bytes too fast,
because a fast response's IRQ will race with the ISR exit path and
get lost. The `main.c` keyboard emulation paces replies
(`kbd_check_work()`, `kbd_rx()`) so as to happen a short time
after the Mac sends an inquiry request.
* Mouse: The 8530 SCC is super-complicated. It's easy to think of
the 1980s as a time of simple hardware, but that really applies
only to CPUs: to compensate, the peripheral hardware was often
complex, and SCC has a lot of offloads for packetisation/framing
of serial streams. It is this chip that enables AppleTalk,
relatively high-speed packet networking over RS422 serial cables.
Two spare pins (for port A/B DCD detect) are used by the mouse;
the Mac 128K is a nose-to-tail design, no part of the animal is
wasted. Uh, anyway, only enough of the SCC is supported to make
the mouse work: IRQ system for DCD-change, driven from one half of
a quadrature pair on X/Y. The ISR for those lines then samples
the VIA PORTB pins for the corresponding other half of the pair.
Finally, the emulator interface takes a movement delta dx:dy for
convenience; a quadrature step is performed for each unit over a
period of time.
* I didn't use the original Mac128 ROM. First, Steve Chamberlin has
done a very useful disassembly of the MacPlus ROM (handy to
debug!) and secondly I misguidedly thought that more stuff in ROM
meant more RAM free. No, the 128K MacPlus ROM uses more RAM (for
extra goodies?) than the original 64K Mac 128K ROM. It does,
however, have some bug fixes. Anyway: the MacPlus ROM runs on
512K and 128K Macs, and was used as the 'e' in the Mac 512Ke.
# See also
* <https://github.com/kstenerud/Musashi>
* <https://www.bigmessowires.com/rom-adapter/plus-rom-listing.asm>
# License(s)
The basis for the 68K `sonydrv.S` and host-side disc driver code in
`disc.c`/`b2_macos_util.h` (as detailed in those files) is from
Basilisk II, Copyright 1997-2008 Christian Bauer, and released under
GPLv2.
The `keymap.h` and `keymap_sdl.h` headers are based on Mini vMac
OSGLUSDL.c Copyright (C) 2012 Paul C. Pratt, Manuel Alfayate, and
OSGLUAAA.h Copyright (C) 2006 Philip Cummins, Richard F. Bannister,
Paul C. Pratt, released under GPLv2.
Some small portions of `main.c` (debug, interrupts) are from the
_Musashi_ project's `example/sim.c`. _Musashi_ is Copyright 1998-2002
Karl Stenerud, and released under the MIT licence.
The remainder of the code is released under the MIT licence:
Copyright (c) 2024 Matt Evans
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
doc/umac_sys32_desktop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

1
external/Musashi vendored Submodule

@ -0,0 +1 @@
Subproject commit 0b85a7a648e7b4d279b41e096c4d67ad354a82f7

282
include/b2_macos_util.h Normal file
View file

@ -0,0 +1,282 @@
/*
* macos_util.h - MacOS definitions/utility functions
*
* Basilisk II (C) 1997-2008 Christian Bauer
*
* 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef MACOS_UTIL_H
#define MACOS_UTIL_H
/*
* Queues
*/
enum { // Queue types
dummyType = 0,
vType = 1,
ioQType = 2,
drvQType = 3,
evType = 4,
fsQType = 5,
sIQType = 6,
dtQType = 7,
nmType = 8
};
enum { // QElem struct
qLink = 0,
qType = 4,
qData = 6
};
enum { // QHdr struct
qFlags = 0,
qHead = 2,
qTail = 6
};
/*
* Definitions for Device Manager
*/
// Error codes
enum {
noErr = 0,
controlErr = -17,
statusErr = -18,
readErr = -19,
writErr = -20,
badUnitErr = -21,
unitEmptyErr = -22,
openErr = -23,
closErr = -24,
abortErr = -27,
notOpenErr = -28,
dskFulErr = -34,
nsvErr = -35,
ioErr = -36,
bdNamErr = -37,
fnOpnErr = -38,
eofErr = -39,
posErr = -40,
tmfoErr = -42,
fnfErr = -43,
wPrErr = -44,
fLckdErr = -45,
fBsyErr = -47,
dupFNErr = -48,
paramErr = -50,
rfNumErr = -51,
permErr = -54,
nsDrvErr = -56,
extFSErr = -58,
noDriveErr = -64,
offLinErr = -65,
noNybErr = -66,
noAdrMkErr = -67,
dataVerErr = -68,
badCksmErr = -69,
badBtSlpErr = -70,
noDtaMkErr = -71,
badDCksum = -72,
badDBtSlp = -73,
wrUnderrun = -74,
cantStepErr = -75,
tk0BadErr = -76,
initIWMErr = -77,
twoSideErr = -78,
spdAdjErr = -79,
seekErr = -80,
sectNFErr = -81,
fmt1Err = -82,
fmt2Err = -83,
verErr = -84,
memFullErr = -108,
dirNFErr = -120
};
// Misc constants
enum {
goodbye = -1,
ioInProgress = 1,
aRdCmd = 2,
aWrCmd = 3,
asyncTrpBit = 10,
noQueueBit = 9,
dReadEnable = 0,
dWritEnable = 1,
dCtlEnable = 2,
dStatEnable = 3,
dNeedGoodBye = 4,
dNeedTime = 5,
dNeedLock = 6,
dOpened = 5,
dRAMBased = 6,
drvrActive = 7,
rdVerify = 64,
fsCurPerm = 0,
fsRdPerm = 1,
fsWrPerm = 2,
fsRdWrPerm = 3,
fsRdWrShPerm = 4,
fsAtMark = 0,
fsFromStart = 1,
fsFromLEOF = 2,
fsFromMark = 3,
sony = 0,
hard20 = 1
};
enum { // Large volume constants
kWidePosOffsetBit = 8,
kMaximumBlocksIn4GB = 0x007fffff
};
enum { // IOParam struct
ioTrap = 6,
ioCmdAddr = 8,
ioCompletion = 12,
ioResult = 16,
ioNamePtr = 18,
ioVRefNum = 22,
ioRefNum = 24,
ioVersNum = 26,
ioPermssn = 27,
ioMisc = 28,
ioBuffer = 32,
ioReqCount = 36,
ioActCount = 40,
ioPosMode = 44,
ioPosOffset = 46,
ioWPosOffset = 46, // Wide positioning offset when ioPosMode has kWidePosOffsetBit set
SIZEOF_IOParam = 50
};
enum { // CntrlParam struct
csCode = 26,
csParam = 28
};
enum { // DrvSts struct
dsTrack = 0,
dsWriteProt = 2,
dsDiskInPlace = 3,
dsInstalled = 4,
dsSides = 5,
dsQLink = 6,
dsQType = 10,
dsQDrive = 12,
dsQRefNum = 14,
dsQFSID = 16,
dsTwoSideFmt = 18,
dsNewIntf = 19,
dsDiskErrs = 20,
dsMFMDrive = 22,
dsMFMDisk = 23,
dsTwoMegFmt = 24
};
enum { // DrvSts2 struct
dsDriveSize = 18,
dsDriveS1 = 20,
dsDriveType = 22,
dsDriveManf = 24,
dsDriveChar = 26,
dsDriveMisc = 28,
SIZEOF_DrvSts = 30
};
enum { // DCtlEntry struct
dCtlDriver = 0,
dCtlFlags = 4,
dCtlQHdr = 6,
dCtlPosition = 16,
dCtlStorage = 20,
dCtlRefNum = 24,
dCtlCurTicks = 26,
dCtlWindow = 30,
dCtlDelay = 34,
dCtlEMask = 36,
dCtlMenu = 38,
dCtlSlot = 40,
dCtlSlotId = 41,
dCtlDevBase = 42,
dCtlOwner = 46,
dCtlExtDev = 50,
dCtlFillByte = 51,
dCtlNodeID = 52
};
/*
* Definitions for Deferred Task Manager
*/
enum { // DeferredTask struct
dtFlags = 6,
dtAddr = 8,
dtParam = 12,
dtReserved = 16
};
// Definitions for DebugUtil() Selector
enum {
duDebuggerGetMax = 0,
duDebuggerEnter = 1,
duDebuggerExit = 2,
duDebuggerPoll = 3,
duGetPageState = 4,
duPageFaultFatal = 5,
duDebuggerLockMemory = 6,
duDebuggerUnlockMemory = 7,
duEnterSupervisorMode = 8
};
/*
// Functions
extern void EnqueueMac(uint32 elem, uint32 list); // Enqueue QElem in list
extern int FindFreeDriveNumber(int num); // Find first free drive number, starting at "num"
extern void MountVolume(void *fh); // Mount volume with given file handle (see sys.h)
extern void FileDiskLayout(loff_t size, uint8 *data, loff_t &start_byte, loff_t &real_size); // Calculate disk image file layout given file size and first 256 data bytes
extern uint32 DebugUtil(uint32 Selector); // DebugUtil() Replacement
extern uint32 TimeToMacTime(time_t t); // Convert time_t value to MacOS time
extern time_t MacTimeToTime(uint32 t); // Convert MacOS time to time_t value
// Construct four-character-code
#define FOURCC(a,b,c,d) (((uint32)(a) << 24) | ((uint32)(b) << 16) | ((uint32)(c) << 8) | (uint32)(d))
// Emulator identification codes (4 and 2 characters)
const uint32 EMULATOR_ID_4 = 0x62617369; // 'basi'
const uint16 EMULATOR_ID_2 = 0x6261; // 'ba'
// Test if basic MacOS initializations (of the ROM) are done
static inline bool HasMacStarted(void)
{
return ReadMacInt32(0xcfc) == FOURCC('W','L','S','C'); // Mac warm start flag
}
*/
#endif

54
include/cpu_cb.h Normal file
View file

@ -0,0 +1,54 @@
/*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef CPU_CB_H
#define CPU_CB_H
#include <inttypes.h>
#include "machw.h"
/* Note unsigned int instead of uint32_t, to make types exactly match
* Musashi ;(
*/
unsigned int cpu_read_byte(unsigned int address);
unsigned int cpu_read_word(unsigned int address);
unsigned int cpu_read_long(unsigned int address);
void cpu_write_byte(unsigned int address, unsigned int value);
void cpu_write_word(unsigned int address, unsigned int value);
void cpu_write_long(unsigned int address, unsigned int value);
void cpu_pulse_reset(void);
void cpu_set_fc(unsigned int fc);
int cpu_irq_ack(int level);
void cpu_instr_callback(int pc);
extern unsigned int (*cpu_read_instr)(unsigned int address);
/* This is special: an aligned 16b opcode, and will never act on MMIO.
*/
static inline unsigned int cpu_read_instr_word(unsigned int address)
{
return cpu_read_instr(address);
}
#endif

47
include/disc.h Normal file
View file

@ -0,0 +1,47 @@
/*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef DISC_H
#define DISC_H
#include <inttypes.h>
typedef struct {
uint8_t *base;
unsigned int size;
int read_only;
} disc_descr_t;
#define DISC_NUM_DRIVES 2
/* Passed an array of descriptors of disc data:
* FIXME: provide callbacks to fops->read/write etc. instead of needing
* a flat array.
*
* Contents copied, pointer is not stored.
*/
void disc_init(disc_descr_t discs[DISC_NUM_DRIVES]);
int disc_pv_hook(uint8_t opcode);
#endif

158
include/keymap.h Normal file
View file

@ -0,0 +1,158 @@
/* Mac keyboard scancodes
*
* From Mini vMac's src/OSGLUAAA.h which is Copyright (C) 2006 Philip
* Cummins, Richard F. Bannister, Paul C. Pratt, and released under
* GPLv2.
*/
#ifndef KEYMAP_H
#define KEYMAP_H
#define MKC_A 0x00
#define MKC_B 0x0B
#define MKC_C 0x08
#define MKC_D 0x02
#define MKC_E 0x0E
#define MKC_F 0x03
#define MKC_G 0x05
#define MKC_H 0x04
#define MKC_I 0x22
#define MKC_J 0x26
#define MKC_K 0x28
#define MKC_L 0x25
#define MKC_M 0x2E
#define MKC_N 0x2D
#define MKC_O 0x1F
#define MKC_P 0x23
#define MKC_Q 0x0C
#define MKC_R 0x0F
#define MKC_S 0x01
#define MKC_T 0x11
#define MKC_U 0x20
#define MKC_V 0x09
#define MKC_W 0x0D
#define MKC_X 0x07
#define MKC_Y 0x10
#define MKC_Z 0x06
#define MKC_1 0x12
#define MKC_2 0x13
#define MKC_3 0x14
#define MKC_4 0x15
#define MKC_5 0x17
#define MKC_6 0x16
#define MKC_7 0x1A
#define MKC_8 0x1C
#define MKC_9 0x19
#define MKC_0 0x1D
#define MKC_Command 0x37
#define MKC_Shift 0x38
#define MKC_CapsLock 0x39
#define MKC_Option 0x3A
#define MKC_Space 0x31
#define MKC_Return 0x24
#define MKC_BackSpace 0x33
#define MKC_Tab 0x30
#define MKC_Left /* 0x46 */ 0x7B
#define MKC_Right /* 0x42 */ 0x7C
#define MKC_Down /* 0x48 */ 0x7D
#define MKC_Up /* 0x4D */ 0x7E
#define MKC_Minus 0x1B
#define MKC_Equal 0x18
#define MKC_BackSlash 0x2A
#define MKC_Comma 0x2B
#define MKC_Period 0x2F
#define MKC_Slash 0x2C
#define MKC_SemiColon 0x29
#define MKC_SingleQuote 0x27
#define MKC_LeftBracket 0x21
#define MKC_RightBracket 0x1E
#define MKC_Grave 0x32
#define MKC_Clear 0x47
#define MKC_KPEqual 0x51
#define MKC_KPDevide 0x4B
#define MKC_KPMultiply 0x43
#define MKC_KPSubtract 0x4E
#define MKC_KPAdd 0x45
#define MKC_Enter 0x4C
#define MKC_KP1 0x53
#define MKC_KP2 0x54
#define MKC_KP3 0x55
#define MKC_KP4 0x56
#define MKC_KP5 0x57
#define MKC_KP6 0x58
#define MKC_KP7 0x59
#define MKC_KP8 0x5B
#define MKC_KP9 0x5C
#define MKC_KP0 0x52
#define MKC_Decimal 0x41
/* these aren't on the Mac Plus keyboard */
#define MKC_Control 0x3B
#define MKC_Escape 0x35
#define MKC_F1 0x7a
#define MKC_F2 0x78
#define MKC_F3 0x63
#define MKC_F4 0x76
#define MKC_F5 0x60
#define MKC_F6 0x61
#define MKC_F7 0x62
#define MKC_F8 0x64
#define MKC_F9 0x65
#define MKC_F10 0x6d
#define MKC_F11 0x67
#define MKC_F12 0x6f
#define MKC_Home 0x73
#define MKC_End 0x77
#define MKC_PageUp 0x74
#define MKC_PageDown 0x79
#define MKC_Help 0x72 /* = Insert */
#define MKC_ForwardDel 0x75
#define MKC_Print 0x69
#define MKC_ScrollLock 0x6B
#define MKC_Pause 0x71
#define MKC_AngleBracket 0x0A /* found on german keyboard */
/*
Additional codes found in Apple headers
#define MKC_RightShift 0x3C
#define MKC_RightOption 0x3D
#define MKC_RightControl 0x3E
#define MKC_Function 0x3F
#define MKC_VolumeUp 0x48
#define MKC_VolumeDown 0x49
#define MKC_Mute 0x4A
#define MKC_F16 0x6A
#define MKC_F17 0x40
#define MKC_F18 0x4F
#define MKC_F19 0x50
#define MKC_F20 0x5A
#define MKC_F13 MKC_Print
#define MKC_F14 MKC_ScrollLock
#define MKC_F15 MKC_Pause
*/
/* not Apple key codes, only for Mini vMac */
#define MKC_CM 0x80
#define MKC_real_CapsLock 0x81
/*
for use in platform specific code
when CapsLocks need special handling.
*/
#define MKC_None 0xFF
#endif

131
include/keymap_sdl.h Normal file
View file

@ -0,0 +1,131 @@
/* Keyboard scancode mapping from SDL to Mac codes
*
* From Mini vMac's OSGLUSDL.c, which is Copyright (C) 2012 Paul
* C. Pratt, Manuel Alfayate, and released under GPLv2.
*/
#ifndef KEYMAP_SDL_H
#define KEYMAP_SDL_H
#include "keymap.h"
static inline int SDLScan2MacKeyCode(SDL_Scancode i)
{
int v = MKC_None;
switch (i) {
case SDL_SCANCODE_BACKSPACE: v = MKC_BackSpace; break;
case SDL_SCANCODE_TAB: v = MKC_Tab; break;
case SDL_SCANCODE_CLEAR: v = MKC_Clear; break;
case SDL_SCANCODE_RETURN: v = MKC_Return; break;
case SDL_SCANCODE_PAUSE: v = MKC_Pause; break;
case SDL_SCANCODE_ESCAPE: v = MKC_Escape; break;
case SDL_SCANCODE_SPACE: v = MKC_Space; break;
case SDL_SCANCODE_APOSTROPHE: v = MKC_SingleQuote; break;
case SDL_SCANCODE_COMMA: v = MKC_Comma; break;
case SDL_SCANCODE_MINUS: v = MKC_Minus; break;
case SDL_SCANCODE_PERIOD: v = MKC_Period; break;
case SDL_SCANCODE_SLASH: v = MKC_Slash; break;
case SDL_SCANCODE_0: v = MKC_0; break;
case SDL_SCANCODE_1: v = MKC_1; break;
case SDL_SCANCODE_2: v = MKC_2; break;
case SDL_SCANCODE_3: v = MKC_3; break;
case SDL_SCANCODE_4: v = MKC_4; break;
case SDL_SCANCODE_5: v = MKC_5; break;
case SDL_SCANCODE_6: v = MKC_6; break;
case SDL_SCANCODE_7: v = MKC_7; break;
case SDL_SCANCODE_8: v = MKC_8; break;
case SDL_SCANCODE_9: v = MKC_9; break;
case SDL_SCANCODE_SEMICOLON: v = MKC_SemiColon; break;
case SDL_SCANCODE_EQUALS: v = MKC_Equal; break;
case SDL_SCANCODE_LEFTBRACKET: v = MKC_LeftBracket; break;
case SDL_SCANCODE_BACKSLASH: v = MKC_BackSlash; break;
case SDL_SCANCODE_RIGHTBRACKET: v = MKC_RightBracket; break;
case SDL_SCANCODE_GRAVE: v = MKC_Grave; break;
case SDL_SCANCODE_A: v = MKC_A; break;
case SDL_SCANCODE_B: v = MKC_B; break;
case SDL_SCANCODE_C: v = MKC_C; break;
case SDL_SCANCODE_D: v = MKC_D; break;
case SDL_SCANCODE_E: v = MKC_E; break;
case SDL_SCANCODE_F: v = MKC_F; break;
case SDL_SCANCODE_G: v = MKC_G; break;
case SDL_SCANCODE_H: v = MKC_H; break;
case SDL_SCANCODE_I: v = MKC_I; break;
case SDL_SCANCODE_J: v = MKC_J; break;
case SDL_SCANCODE_K: v = MKC_K; break;
case SDL_SCANCODE_L: v = MKC_L; break;
case SDL_SCANCODE_M: v = MKC_M; break;
case SDL_SCANCODE_N: v = MKC_N; break;
case SDL_SCANCODE_O: v = MKC_O; break;
case SDL_SCANCODE_P: v = MKC_P; break;
case SDL_SCANCODE_Q: v = MKC_Q; break;
case SDL_SCANCODE_R: v = MKC_R; break;
case SDL_SCANCODE_S: v = MKC_S; break;
case SDL_SCANCODE_T: v = MKC_T; break;
case SDL_SCANCODE_U: v = MKC_U; break;
case SDL_SCANCODE_V: v = MKC_V; break;
case SDL_SCANCODE_W: v = MKC_W; break;
case SDL_SCANCODE_X: v = MKC_X; break;
case SDL_SCANCODE_Y: v = MKC_Y; break;
case SDL_SCANCODE_Z: v = MKC_Z; break;
case SDL_SCANCODE_KP_0: v = MKC_KP0; break;
case SDL_SCANCODE_KP_1: v = MKC_KP1; break;
case SDL_SCANCODE_KP_2: v = MKC_KP2; break;
case SDL_SCANCODE_KP_3: v = MKC_KP3; break;
case SDL_SCANCODE_KP_4: v = MKC_KP4; break;
case SDL_SCANCODE_KP_5: v = MKC_KP5; break;
case SDL_SCANCODE_KP_6: v = MKC_KP6; break;
case SDL_SCANCODE_KP_7: v = MKC_KP7; break;
case SDL_SCANCODE_KP_8: v = MKC_KP8; break;
case SDL_SCANCODE_KP_9: v = MKC_KP9; break;
case SDL_SCANCODE_KP_PERIOD: v = MKC_Decimal; break;
case SDL_SCANCODE_KP_DIVIDE: v = MKC_KPDevide; break;
case SDL_SCANCODE_KP_MULTIPLY: v = MKC_KPMultiply; break;
case SDL_SCANCODE_KP_MINUS: v = MKC_KPSubtract; break;
case SDL_SCANCODE_KP_PLUS: v = MKC_KPAdd; break;
case SDL_SCANCODE_KP_ENTER: v = MKC_Enter; break;
case SDL_SCANCODE_KP_EQUALS: v = MKC_KPEqual; break;
case SDL_SCANCODE_UP: v = MKC_Up; break;
case SDL_SCANCODE_DOWN: v = MKC_Down; break;
case SDL_SCANCODE_RIGHT: v = MKC_Right; break;
case SDL_SCANCODE_LEFT: v = MKC_Left; break;
case SDL_SCANCODE_INSERT: v = MKC_Help; break;
case SDL_SCANCODE_HOME: v = MKC_Home; break;
case SDL_SCANCODE_END: v = MKC_End; break;
case SDL_SCANCODE_PAGEUP: v = MKC_PageUp; break;
case SDL_SCANCODE_PAGEDOWN: v = MKC_PageDown; break;
/* FIXME, case SDL_SCANCODE_CAPSLOCK and MKC_formac_CapsLock */
case SDL_SCANCODE_RSHIFT:
case SDL_SCANCODE_LSHIFT: v = MKC_Shift; break;
case SDL_SCANCODE_RCTRL:
case SDL_SCANCODE_LCTRL: v = MKC_Control; break;
case SDL_SCANCODE_RALT:
case SDL_SCANCODE_LALT: v = MKC_Option; break;
case SDL_SCANCODE_RGUI:
case SDL_SCANCODE_LGUI: v = MKC_Command; break;
case SDL_SCANCODE_KP_A: v = MKC_A; break;
case SDL_SCANCODE_KP_B: v = MKC_B; break;
case SDL_SCANCODE_KP_C: v = MKC_C; break;
case SDL_SCANCODE_KP_D: v = MKC_D; break;
case SDL_SCANCODE_KP_E: v = MKC_E; break;
case SDL_SCANCODE_KP_F: v = MKC_F; break;
case SDL_SCANCODE_KP_BACKSPACE: v = MKC_BackSpace; break;
case SDL_SCANCODE_KP_CLEAR: v = MKC_Clear; break;
case SDL_SCANCODE_KP_COMMA: v = MKC_Comma; break;
case SDL_SCANCODE_KP_DECIMAL: v = MKC_Decimal; break;
default:
break;
}
return v;
}
#endif

252
include/m68kconf.h Normal file
View file

@ -0,0 +1,252 @@
/* ======================================================================== */
/* ========================= LICENSING & COPYRIGHT ======================== */
/* ======================================================================== */
/*
* MUSASHI
* Version 3.32
*
* A portable Motorola M680x0 processor emulation engine.
* Copyright Karl Stenerud. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef M68KCONF__HEADER
#define M68KCONF__HEADER
/* Configuration switches.
* Use OPT_SPECIFY_HANDLER for configuration options that allow callbacks.
* OPT_SPECIFY_HANDLER causes the core to link directly to the function
* or macro you specify, rather than using callback functions whose pointer
* must be passed in using m68k_set_xxx_callback().
*/
#define OPT_OFF 0
#define OPT_ON 1
#define OPT_SPECIFY_HANDLER 2
/* ======================================================================== */
/* ============================== MAME STUFF ============================== */
/* ======================================================================== */
/* If you're compiling this for MAME, only change M68K_COMPILE_FOR_MAME
* to OPT_ON and use m68kmame.h to configure the 68k core.
*/
#ifndef M68K_COMPILE_FOR_MAME
#define M68K_COMPILE_FOR_MAME OPT_OFF
#endif /* M68K_COMPILE_FOR_MAME */
#if M68K_COMPILE_FOR_MAME == OPT_OFF
/* ======================================================================== */
/* ============================= CONFIGURATION ============================ */
/* ======================================================================== */
/* Turn ON if you want to use the following M68K variants */
#define M68K_EMULATE_010 OPT_OFF
#define M68K_EMULATE_EC020 OPT_OFF
#define M68K_EMULATE_020 OPT_OFF
#define M68K_EMULATE_040 OPT_OFF
/* If ON, the CPU will call m68k_read_immediate_xx() for immediate addressing
* and m68k_read_pcrelative_xx() for PC-relative addressing.
* If off, all read requests from the CPU will be redirected to m68k_read_xx()
*/
#define M68K_SEPARATE_READS OPT_OFF
/* If ON, the CPU will call m68k_write_32_pd() when it executes move.l with a
* predecrement destination EA mode instead of m68k_write_32().
* To simulate real 68k behavior, m68k_write_32_pd() must first write the high
* word to [address+2], and then write the low word to [address].
*/
#define M68K_SIMULATE_PD_WRITES OPT_OFF
/* If ON, CPU will call the interrupt acknowledge callback when it services an
* interrupt.
* If off, all interrupts will be autovectored and all interrupt requests will
* auto-clear when the interrupt is serviced.
*/
#define M68K_EMULATE_INT_ACK OPT_SPECIFY_HANDLER
#define M68K_INT_ACK_CALLBACK(A) cpu_irq_ack(A)
/* If ON, CPU will call the breakpoint acknowledge callback when it encounters
* a breakpoint instruction and it is running a 68010+.
*/
#define M68K_EMULATE_BKPT_ACK OPT_OFF
#define M68K_BKPT_ACK_CALLBACK() your_bkpt_ack_handler_function()
/* If ON, the CPU will monitor the trace flags and take trace exceptions
*/
#define M68K_EMULATE_TRACE OPT_OFF
/* If ON, CPU will call the output reset callback when it encounters a reset
* instruction.
*/
#define M68K_EMULATE_RESET OPT_SPECIFY_HANDLER
#define M68K_RESET_CALLBACK() cpu_pulse_reset()
/* If ON, CPU will call the callback when it encounters a cmpi.l #v, dn
* instruction.
*/
#define M68K_CMPILD_HAS_CALLBACK OPT_OFF
#define M68K_CMPILD_CALLBACK(v,r) your_cmpild_handler_function(v,r)
/* If ON, CPU will call the callback when it encounters a rte
* instruction.
*/
#define M68K_RTE_HAS_CALLBACK OPT_OFF
#define M68K_RTE_CALLBACK() your_rte_handler_function()
/* If ON, CPU will call the callback when it encounters a tas
* instruction.
*/
#define M68K_TAS_HAS_CALLBACK OPT_OFF
#define M68K_TAS_CALLBACK() your_tas_handler_function()
/* If ON, CPU will call the callback when it encounters an illegal instruction
* passing the opcode as argument. If the callback returns 1, then it's considered
* as a normal instruction, and the illegal exception in canceled. If it returns 0,
* the exception occurs normally.
* The callback looks like int callback(int opcode)
* You should put OPT_SPECIFY_HANDLER here if you cant to use it, otherwise it will
* use a dummy default handler and you'll have to call m68k_set_illg_instr_callback explicitely
*/
#define M68K_ILLG_HAS_CALLBACK OPT_OFF
#define M68K_ILLG_CALLBACK(opcode) op_illg(opcode)
/* If ON, CPU will call the set fc callback on every memory access to
* differentiate between user/supervisor, program/data access like a real
* 68000 would. This should be enabled and the callback should be set if you
* want to properly emulate the m68010 or higher. (moves uses function codes
* to read/write data from different address spaces)
*/
#define M68K_EMULATE_FC OPT_OFF
#define M68K_SET_FC_CALLBACK(A) cpu_set_fc(A)
/* If ON, CPU will call the pc changed callback when it changes the PC by a
* large value. This allows host programs to be nicer when it comes to
* fetching immediate data and instructions on a banked memory system.
*/
#define M68K_MONITOR_PC OPT_OFF
#define M68K_SET_PC_CALLBACK(A) your_pc_changed_handler_function(A)
/* If ON, CPU will call the instruction hook callback before every
* instruction.
*/
#ifdef ENABLE_DASM
#define M68K_INSTRUCTION_HOOK OPT_SPECIFY_HANDLER
#define M68K_INSTRUCTION_CALLBACK(pc) cpu_instr_callback(pc)
#else
#define M68K_INSTRUCTION_HOOK OPT_OFF
#endif
/* If ON, the CPU will emulate the 4-byte prefetch queue of a real 68000 */
#define M68K_EMULATE_PREFETCH OPT_OFF
/* If ON, the CPU will generate address error exceptions if it tries to
* access a word or longword at an odd address.
* NOTE: This is only emulated properly for 68000 mode.
*/
#define M68K_EMULATE_ADDRESS_ERROR OPT_OFF
/* Turn ON to enable logging of illegal instruction calls.
* M68K_LOG_FILEHANDLE must be #defined to a stdio file stream.
* Turn on M68K_LOG_1010_1111 to log all 1010 and 1111 calls.
*/
#define M68K_LOG_ENABLE OPT_OFF
#define M68K_LOG_1010_1111 OPT_OFF
#define M68K_LOG_FILEHANDLE some_file_handle
/* Build in the disassembler. If disabled, the API still exists
* but does nothing. Turn off to save text/bss space.
*/
#define M68K_DASM_ENABLE ENABLE_DASM
/* Use dynamically-created tables for decode and cyclecounts (if
* enabled by CYCLE_COUNTING), as opposed to tables created at build
* time. Using the dynamic version of the decode jumptable adds 256KB
* to RAM usage but makes the binary smaller. Using the static
* version adds 256KB to the binary and no RAM, but doesn't currently
* support instruction cycle counts.
*/
#define M68K_DYNAMIC_INSTR_TABLES OPT_OFF
/* Count instruction cycles. This costs a table (created at runtime
* in RAM if DYNAMIC_INSTR_TABLES is on).
*
* NOTE: This is not currently supported when DYNAMIC_INSTR_TABLES is
* off.
*/
#define M68K_CYCLE_COUNTING OPT_OFF
#define M68K_FIXED_CPU_TYPE CPU_TYPE_000
#define M68K_BUS_ERR_ENABLE OPT_OFF
/* ----------------------------- COMPATIBILITY ---------------------------- */
/* The following options set optimizations that violate the current ANSI
* standard, but will be compliant under the forthcoming C9X standard.
*/
/* If ON, the enulation core will use 64-bit integers to speed up some
* operations.
*/
#define M68K_USE_64_BIT OPT_ON
#include "cpu_cb.h"
#define m68k_read_memory_8(A) cpu_read_byte(A)
#define m68k_read_memory_16(A) cpu_read_word(A)
#define m68k_read_memory_32(A) cpu_read_long(A)
#define m68k_read_instr_16(A) cpu_read_instr_word(A)
#define m68k_read_disassembler_16(A) cpu_read_word_dasm(A)
#define m68k_read_disassembler_32(A) cpu_read_long_dasm(A)
#define m68k_write_memory_8(A, V) cpu_write_byte(A, V)
#define m68k_write_memory_16(A, V) cpu_write_word(A, V)
#define m68k_write_memory_32(A, V) cpu_write_long(A, V)
#ifdef PICO
#include "pico.h"
#define M68K_FAST_FUNC(x) __not_in_flash_func(x)
#endif
#endif /* M68K_COMPILE_FOR_MAME */
/* ======================================================================== */
/* ============================== END OF FILE ============================= */
/* ======================================================================== */
#endif /* M68KCONF__HEADER */

129
include/machw.h Normal file
View file

@ -0,0 +1,129 @@
/*
* Copyright 2024 Matt Evans
*
* Portions of the {READ,WRITE}_{BYTE,WORD,LONG} macros are from
* Musashi, Copyright 1998-2002 Karl Stenerud.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef MACHW_H
#define MACHW_H
#include "rom.h"
#define ROM_ADDR 0x400000 /* Regular base (and 0, when overlay=0 */
#define RAM_SIZE (0x20000*1)
#define RAM_HIGH_ADDR 0x600000
#define PV_SONY_ADDR 0xc00069 /* Magic address for replacement driver PV ops */
#define DISP_WIDTH 512
#define DISP_HEIGHT 342
////////////////////////////////////////////////////////////////////////////////
// RAM accessors
//
// This isn't a wonderful place, but RAM access is needed in various files.
extern uint8_t *_ram_base;
extern uint8_t *_rom_base;
static inline uint8_t *ram_get_base()
{
return _ram_base;
}
static inline uint8_t *rom_get_base()
{
return _rom_base;
}
extern int overlay;
#define ADR24(x) ((x) & 0xffffff)
/* Let's do it like this:
*
* When overlay=1: (reset)
* - ROM is at 0-0x10_0000 (hm, to 0x40_0000 pretty much) and 0x40_0000-0x50_0000
* - RAM is at 0x60_0000-0x80_0000
*
* When overlay=0:
* - ROM is at 0x40_0000-0x50_0000
* - RAM is at 0-0x40_0000
* - manuals say 0x60_0000-0x80_00000 is "unassigned", but may as well make RAM here too
*
* i.e. RAM is 60-80, or !overlay and 0. And ROM is 40-50, or overlay and 0.
*/
#define IS_ROM(x) (((ADR24(x) & 0xf00000) == ROM_ADDR) || (overlay && (ADR24(x) & 0xf00000) == 0))
/* RAM: always at 0x600000-0x7fffff, sometimes at 0 (0 most likely so check first!) */
#define IS_RAM(x) ((!overlay && ((ADR24(x) & 0xc00000) == 0)) || ((ADR24(x) & 0xe00000) == RAM_HIGH_ADDR))
#define IS_VIA(x) ((ADR24(x) & 0xe80000) == 0xe80000)
#define IS_IWM(x) ((ADR24(x) >= 0xdfe1ff) && (ADR24(x) < (0xdfe1ff + 0x2000)))
#define IS_SCC_RD(x) ((ADR24(x) & 0xf00000) == 0x900000)
#define IS_SCC_WR(x) ((ADR24(x) & 0xf00000) == 0xb00000)
#define IS_DUMMY(x) (((ADR24(x) >= 0x800000) && (ADR24(x) < 0x9ffff8)) || ((ADR24(x) & 0xf00000) == 0x500000))
#define IS_TESTSW(x) (ADR24(x) >= 0xf00000)
/* Unaligned/BE read/write macros from Mushashi: */
#define READ_BYTE(BASE, ADDR) (BASE)[ADDR]
#define READ_WORD(BASE, ADDR) (((BASE)[ADDR]<<8) | \
(BASE)[(ADDR)+1])
#define READ_LONG(BASE, ADDR) (((BASE)[ADDR]<<24) | \
((BASE)[(ADDR)+1]<<16) | \
((BASE)[(ADDR)+2]<<8) | \
(BASE)[(ADDR)+3])
#define READ_WORD_AL(BASE, ADDR) (__builtin_bswap16(*(uint16_t *)&(BASE)[(ADDR)]))
#define WRITE_BYTE(BASE, ADDR, VAL) do { \
(BASE)[ADDR] = (VAL)&0xff; \
} while(0)
#define WRITE_WORD(BASE, ADDR, VAL) do { \
(BASE)[ADDR] = ((VAL)>>8) & 0xff; \
(BASE)[(ADDR)+1] = (VAL)&0xff; \
} while(0)
#define WRITE_LONG(BASE, ADDR, VAL) do { \
(BASE)[ADDR] = ((VAL)>>24) & 0xff; \
(BASE)[(ADDR)+1] = ((VAL)>>16)&0xff; \
(BASE)[(ADDR)+2] = ((VAL)>>8)&0xff; \
(BASE)[(ADDR)+3] = (VAL)&0xff; \
} while(0)
/* Specific RAM/ROM access: */
#define RAM_RD8(addr) READ_BYTE(_ram_base, addr)
#define RAM_RD16(addr) READ_WORD(_ram_base, addr)
#define RAM_RD_ALIGNED_BE16(addr) READ_WORD_AL(_ram_base, addr)
#define RAM_RD32(addr) READ_LONG(_ram_base, addr)
#define RAM_WR8(addr, val) WRITE_BYTE(_ram_base, addr, val)
#define RAM_WR16(addr, val) WRITE_WORD(_ram_base, addr, val)
#define RAM_WR32(addr, val) WRITE_LONG(_ram_base, addr, val)
#define ROM_RD8(addr) READ_BYTE(_rom_base, addr)
#define ROM_RD16(addr) READ_WORD(_rom_base, addr)
#define ROM_RD_ALIGNED_BE16(addr) READ_WORD_AL(_rom_base, addr)
#define ROM_RD32(addr) READ_LONG(_rom_base, addr)
#endif

34
include/rom.h Normal file
View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef ROM_H
#define ROM_H
#define ROM_SIZE 0x20000 /* Real mapping size */
#include <inttypes.h>
int rom_patch(uint8_t *rom_base);
#endif

40
include/scc.h Normal file
View file

@ -0,0 +1,40 @@
/*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef SCC_H
#define SCC_H
/* Callbacks for various SCC events: */
struct scc_cb {
void (*irq_set)(int status);
};
void scc_init(struct scc_cb *cb);
void scc_write(unsigned int address, uint8_t data);
uint8_t scc_read(unsigned int address);
/* Set a new state for the DCD pins: */
void scc_set_dcd(int a, int b);
#endif

13
include/sonydrv.h Normal file
View file

@ -0,0 +1,13 @@
0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x4c,
0x00, 0x56, 0x00, 0x68, 0x00, 0x94, 0x05, 0x2e, 0x53, 0x6f, 0x6e, 0x79,
0x48, 0xe7, 0x00, 0xc0, 0x10, 0x3c, 0x00, 0x1e, 0xa7, 0x1e, 0x24, 0x48,
0x4c, 0xdf, 0x03, 0x00, 0xb5, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00,
0x00, 0x18, 0x26, 0x7a, 0x00, 0x64, 0x16, 0xbc, 0x00, 0x00, 0x20, 0x3c,
0x00, 0x01, 0xff, 0xfb, 0x20, 0x4a, 0x5c, 0x88, 0xa0, 0x4e, 0x4e, 0x75,
0x70, 0xe9, 0x4e, 0x75, 0x24, 0x7a, 0x00, 0x4a, 0x14, 0xbc, 0x00, 0x01,
0x60, 0x1a, 0x24, 0x7a, 0x00, 0x40, 0x14, 0xbc, 0x00, 0x02, 0x0c, 0x68,
0x00, 0x01, 0x00, 0x1a, 0x66, 0x0a, 0x4e, 0x75, 0x24, 0x7a, 0x00, 0x2e,
0x14, 0xbc, 0x00, 0x03, 0x32, 0x28, 0x00, 0x06, 0x08, 0x01, 0x00, 0x09,
0x67, 0x0c, 0x4a, 0x40, 0x6f, 0x02, 0x42, 0x40, 0x31, 0x40, 0x00, 0x10,
0x4e, 0x75, 0x4a, 0x40, 0x6f, 0x04, 0x42, 0x40, 0x4e, 0x75, 0x2f, 0x38,
0x08, 0xfc, 0x4e, 0x75, 0x70, 0xe8, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00

56
include/umac.h Normal file
View file

@ -0,0 +1,56 @@
/*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef UMAC_H
#define UMAC_H
#include "disc.h"
#include "via.h"
#include "machw.h"
int umac_init(void *_ram_base, void *_rom_base, disc_descr_t discs[DISC_NUM_DRIVES]);
int umac_loop();
void umac_reset();
void umac_opt_disassemble(int enable);
void umac_mouse(int deltax, int deltay, int button);
void umac_kbd_event(uint8_t scancode, int down);
static inline void umac_vsync_event()
{
via_caX_event(2);
}
static inline void umac_1hz_event()
{
via_caX_event(1);
}
/* Return the offset into RAM of the current display buffer */
static inline unsigned int umac_get_fb_offset()
{
/* FIXME: Implement VIA RA6/vid.pg2 */
return RAM_SIZE - 0x5900;
}
#endif

48
include/via.h Normal file
View file

@ -0,0 +1,48 @@
/*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef VIA_H
#define VIA_H
#include <stdio.h>
/* Callbacks for various VIA events: */
struct via_cb {
void (*ra_change)(uint8_t val);
void (*rb_change)(uint8_t val);
uint8_t (*ra_in)();
uint8_t (*rb_in)();
void (*sr_tx)(uint8_t val);
void (*irq_set)(int status);
};
void via_init(struct via_cb *cb);
void via_write(unsigned int address, uint8_t data);
uint8_t via_read(unsigned int address);
void via_tick(uint64_t time);
/* Trigger an event on CA1 or CA2: */
void via_caX_event(int ca);
void via_sr_rx(uint8_t val);
#endif

102
macsrc/sonydrv.S Normal file
View file

@ -0,0 +1,102 @@
/* Replacement paravirt Sony driver stubs
*
* This is the Basilisk "sony_driver" from the binary code in rom_patches.cpp,
* except instead of using custom opcodes this driver stores magic values
* to a magic address.
*
* The original code: Basilisk II (C) Christian Bauer
* Extensions: Copyright 2024 Matt Evans
*
* GPLv2
*/
#define SONY_REF_NUM -5
#define SONY_STATUS_SIZE 30
.org 0
.globl _start
_start:
.globl header
header:
.word 0x6f00 // flags
.word 0 // ticks between actions
.word 0 // desk accessory event mask
.word 0 // menu ID
.word open
.word prime
.word control
.word status
.word close
name:
.byte 5
.ascii ".Sony"
open:
// Open()
// Allocate drive status block and pass to Open routine:
movem.l %a0-%a1, -(%sp)
move.b #SONY_STATUS_SIZE, %d0
.word 0xa71e // NewPtrSysClear
move.l %a0, %a2
movem.l (%sp)+, %a0-%a1
cmp.l #0, %a2
beq 2f // lolfail
move.l (faulting_address), %a3
move.b #0, (%a3) // Fault! Open: op 0
// FIXME: Support variable number of drives
// Now add the drive:
move.l #(0x00010000 + (0xffff & SONY_REF_NUM)), %d0
move.l %a2, %a0
add.l #6, %a0
.word 0xa04e // AddDrive
1: rts
2: moveq #-23, %d0
rts
prime:
// Prime()
move.l (faulting_address), %a2
move.b #1, (%a2) // Prime: op 1
bra.b IOReturn
control:
// Control()
move.l (faulting_address), %a2
move.b #2, (%a2) // Control: op 2
cmp.w #1, 0x1a(%a0)
bne.b IOReturn
rts
status:
// Status()
move.l (faulting_address), %a2
move.b #3, (%a2) // Status: op 3
// Fall through
IOReturn:
// IOReturn
move.w 6(%a0),%d1 // IO trap?
btst #9,%d1 // Test queue bit
beq.b 1f
tst.w %d0 // Not queued; test async result
ble.b 2f
clr.w %d0 // In progress
2: move.w %d0, 0x10(%a0) // ImmedRTS
rts
1: tst.w %d0 // Queued
ble.b 3f
clr.w %d0
rts
3: move.l 0x8fc,-(%sp) // IODone vector
rts
close:
// Close()
moveq #-24, %d0 // closErr
rts
.balign 4
faulting_address:
.long 0

504
src/disc.c Normal file
View file

@ -0,0 +1,504 @@
/* umac disc emulation
*
* Contains a PV wrapper around a cut-down version of Basilisk II's
* sony.cpp disc driver, with copyright/licence as shown inline below.
*
* Remaining (top) code is Copyright 2024 Matt Evans.
*
*/
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include "disc.h"
#include "m68k.h"
#include "machw.h"
#ifdef DEBUG
#define DDBG(...) printf(__VA_ARGS__)
#else
#define DDBG(...) do {} while(0)
#endif
#define DERR(...) fprintf(stderr, __VA_ARGS__)
extern void umac_disc_ejected();
// B2 decls:
static int16_t SonyOpen(uint32_t pb, uint32_t dce, uint32_t status);
static int16_t SonyPrime(uint32_t pb, uint32_t dce);
static int16_t SonyControl(uint32_t pb, uint32_t dce);
static int16_t SonyStatus(uint32_t pb, uint32_t dce);
static void SonyInit(disc_descr_t discs[DISC_NUM_DRIVES]);
void disc_init(disc_descr_t discs[DISC_NUM_DRIVES])
{
SonyInit(discs);
}
/* This is the entrypoint redirected from the PV .Sony replacement driver.
* Largely re-uses code from Basilisk!
*/
int disc_pv_hook(uint8_t opcode)
{
uint32_t a0 = m68k_get_reg(NULL, M68K_REG_A0);
uint32_t a1 = m68k_get_reg(NULL, M68K_REG_A1);
uint32_t a2 = m68k_get_reg(NULL, M68K_REG_A2);
uint32_t d0 = 0;
switch(opcode) {
case 0: // Open
DDBG("[Disc: OPEN]\n");
d0 = SonyOpen(ADR24(a0), ADR24(a1), ADR24(a2));
break;
case 1: // Prime
DDBG("[Disc: PRIME]\n");
d0 = SonyPrime(ADR24(a0), ADR24(a1));
break;
case 2: // Control
DDBG("[Disc: CONTROL]\n");
d0 = SonyControl(ADR24(a0), ADR24(a1));
break;
case 3: // Status
DDBG("[Disc: STATUS]\n");
d0 = SonyStatus(ADR24(a0), ADR24(a1));
break;
default:
DERR("[Disc PV op %02x unhandled!]\n", opcode);
return -1;
};
// Return val:
m68k_set_reg(M68K_REG_D0, d0);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// Basilisk II code follows
#define WriteMacInt32(addr, val) RAM_WR32(addr, val)
#define WriteMacInt16(addr, val) RAM_WR16(addr, val)
#define WriteMacInt8(addr, val) RAM_WR8(addr, val)
#define ReadMacInt32(addr) RAM_RD32(addr)
#define ReadMacInt16(addr) RAM_RD16(addr)
#define ReadMacInt8(addr) RAM_RD8(addr)
#define Mac2HostAddr(addr) (ram_get_base() + (addr))
// These access host pointers, in BE/unaligned:
#define WriteBEInt32(addr, val) WRITE_LONG(((uint8_t *)(addr)), 0, val)
#define WriteBEInt16(addr, val) WRITE_WORD(((uint8_t *)(addr)), 0, val)
#define WriteBEInt8(addr, val) WRITE_BYTE(((uint8_t *)(addr)), 0, val)
/*
* sony.cpp - Replacement .Sony driver (floppy drives)
*
* Basilisk II (C) 1997-2008 Christian Bauer
*
* 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* SEE ALSO
* Inside Macintosh: Devices, chapter 1 "Device Manager"
* Technote DV 05: "Drive Queue Elements"
* Technote DV 07: "Forcing Floppy Disk Size to be Either 400K or 800K"
* Technote DV 17: "Sony Driver: What Your Sony Drives For You"
* Technote DV 23: "Driver Education"
* Technote FL 24: "Don't Look at ioPosOffset for Devices"
*/
#include "b2_macos_util.h"
// Struct for each drive
typedef struct sony_drive_info {
int num; // Drive number
uint8_t *data;
unsigned int size;
int to_be_mounted; // Flag: drive must be mounted in accRun
int read_only; // Flag: force write protection
uint32_t status; // Mac address of drive status record
} sony_drinfo_t;
// List of drives handled by this driver
static sony_drinfo_t drives[DISC_NUM_DRIVES];
/*
* Get reference to drive info or drives.end() if not found
*/
static sony_drinfo_t *get_drive_info(int num)
{
for (int i = 0; i < DISC_NUM_DRIVES; i++) {
if (drives[i].num == num)
return &drives[i];
}
return 0;
}
/*
* Initialization
*/
void SonyInit(disc_descr_t discs[DISC_NUM_DRIVES])
{
drives[0].num = 0;
drives[0].to_be_mounted = 1;
drives[0].read_only = discs[0].read_only;
drives[0].data = discs[0].base;
drives[0].size = discs[0].size;
// FIXME: Disc 2
}
/*
* Set error code in DskErr
*/
static int16_t set_dsk_err(int16_t err)
{
DDBG("set_dsk_err(%d)\n", err);
WriteMacInt16(0x142, err);
return err;
}
/*
* Find first free drive number, starting at num
*/
static int is_drive_number_free(int num)
{
uint32_t e = ReadMacInt32(0x308 + qHead);
while (e) {
uint32_t d = e - dsQLink;
if ((int)ReadMacInt16(d + dsQDrive) == num)
return 0;
e = ReadMacInt32(e + qLink);
}
return 1;
}
int FindFreeDriveNumber(int num)
{
while (!is_drive_number_free(num))
num++;
return num;
}
const int SonyRefNum = -5; // RefNum of driver
/*
* Driver Open() routine
*/
int16_t SonyOpen(uint32_t pb, uint32_t dce, uint32_t status)
{
DDBG("SonyOpen\n");
// Set up DCE
WriteMacInt32(dce + dCtlPosition, 0);
WriteMacInt16(dce + dCtlQHdr + qFlags, (ReadMacInt16(dce + dCtlQHdr + qFlags) & 0xff00) | 3); // Version number, must be >=3 or System 8 will replace us
// Set up fake SonyVars
WriteMacInt32(0x134, 0xdeadbeef);
// Clear DskErr
set_dsk_err(0);
// Install drives
//for (int drnum = 0; drnum < NUM_DRIVES; drnum++) {
const int drnum = 0;
sony_drinfo_t *info = &drives[drnum];
info->num = FindFreeDriveNumber(1); // ? 1 for internal, 2 for external
info->to_be_mounted = 0;
// Original code allocated drive status record here (invoked
// trap to NewPtrSysClear), but our driver does this instead
// (it's passed in via status parameter), to avoid having to
// implement invocation of 68K traps/upcalls from sim env.
info->status = status;
DDBG(" DrvSts at %08x\n", info->status);
// Set up drive status
// ME: do 800K, double sided (see IM)
WriteMacInt16(info->status + dsQType, sony);
WriteMacInt8(info->status + dsInstalled, 1);
WriteMacInt8(info->status + dsSides, 0xff); // 2 sides
WriteMacInt8(info->status + dsTwoSideFmt, 0xff); //
//WriteMacInt8(info->status + dsNewIntf, 0xff);
WriteMacInt8(info->status + dsMFMDrive, 0); // 0 = 400/800K GCR drive)
WriteMacInt8(info->status + dsMFMDisk, 0);
//WriteMacInt8(info->status + dsTwoMegFmt, 0xff); // 1.44MB (0 = 720K)
// If disk in drive...
WriteMacInt8(info->status + dsDiskInPlace, 1); // Inserted removable disk
WriteMacInt8(info->status + dsWriteProt, info->read_only ? 0xff : 0);
DDBG(" disk inserted, flagging for mount\n");
info->to_be_mounted = 1;
// Original code ddded drive to drive queue here (invoked trap
// to AddDrive), but our driver does this after this PV call returns.
// FIXME: In future return a bitmap of drives to add.
(void)pb;
return noErr;
}
/*
* Driver Prime() routine
*/
int16_t SonyPrime(uint32_t pb, uint32_t dce)
{
DDBG("Disc: PRIME %08x %08x\n", pb, dce);
WriteMacInt32(pb + ioActCount, 0);
// Drive valid and disk inserted?
sony_drinfo_t *info = get_drive_info(ReadMacInt16(pb + ioVRefNum));
DDBG("- info %p (ref %d)\n", (void *)info, ReadMacInt16(pb + ioVRefNum));
if (!info)
return set_dsk_err(nsDrvErr);
if (!ReadMacInt8(info->status + dsDiskInPlace))
return set_dsk_err(offLinErr);
WriteMacInt8(info->status + dsDiskInPlace, 2); // Disk accessed
// Get parameters
void *buffer = Mac2HostAddr(ReadMacInt32(pb + ioBuffer)); // FIXME
size_t length = ReadMacInt32(pb + ioReqCount);
uint32_t position = ReadMacInt32(dce + dCtlPosition);
if ((length & 0x1ff) || (position & 0x1ff)) {
DDBG("- Bad param: length 0x%lx, pos 0x%x\n", length, position);
return set_dsk_err(paramErr);
}
if ((position + length) > info->size) {
DDBG("- Off end: length 0x%lx, pos 0x%x\n", length, position);
return set_dsk_err(paramErr);
}
size_t actual = 0;
if ((ReadMacInt16(pb + ioTrap) & 0xff) == aRdCmd) {
DDBG("DISC: READ %ld from +0x%x\n", length, position);
DDBG(" (Read buffer: %p)\n", (void *)&info->data[position]);
memcpy(buffer, &info->data[position], length);
// Clear TagBuf
WriteMacInt32(0x2fc, 0);
WriteMacInt32(0x300, 0);
WriteMacInt32(0x304, 0);
} else {
DDBG("DISC: WRITE %ld to +0x%x (not performed)\n", length, position);
// Write
if (info->read_only)
return set_dsk_err(wPrErr);
// FIXME: Add write code here, if required.
return set_dsk_err(wPrErr);
}
// Update ParamBlock and DCE
WriteMacInt32(pb + ioActCount, actual);
WriteMacInt32(dce + dCtlPosition, ReadMacInt32(dce + dCtlPosition) + actual);
return set_dsk_err(noErr);
}
/*
* Driver Control() routine
*/
int16_t SonyControl(uint32_t pb, uint32_t dce)
{
uint16_t code = ReadMacInt16(pb + csCode);
DDBG("SonyControl %d\n", code);
// General codes
switch (code) {
case 1: // KillIO (not supported)
return set_dsk_err(-1);
case 9: // Track cache control (ignore, assume that host OS does the caching)
return set_dsk_err(noErr);
case 65: { // Periodic action (accRun, "insert" disks on startup)
static int complained_yet = 0;
if (!complained_yet) {
DERR("SonyControl:accRun: Not supported!\n");
complained_yet = 1;
}
// The original emulation code hooked this to mount_mountable_volumes,
// which called back to PostEvent(diskEvent).
return set_dsk_err(-1);
}
}
// Drive valid?
sony_drinfo_t *info = get_drive_info(ReadMacInt16(pb + ioVRefNum));
if (!info)
return set_dsk_err(nsDrvErr);
// Drive-specific codes
int16_t err = noErr;
switch (code) {
case 5: // Verify disk
if (ReadMacInt8(info->status + dsDiskInPlace) <= 0) {
err = offLinErr;
}
break;
case 6: // Format disk
if (info->read_only) {
err = wPrErr;
/* } else if (ReadMacInt8(info->status + dsDiskInPlace) > 0) {
if (!SysFormat(info->fh))
err = writErr;
*/
} else
err = offLinErr;
break;
case 7: // Eject
if (ReadMacInt8(info->status + dsDiskInPlace) > 0) {
DERR("DISC: EJECT\n");
// SysEject(info->fh);
WriteMacInt8(info->status + dsDiskInPlace, 0);
umac_disc_ejected();
}
break;
case 8: // Set tag buffer (ignore, not supported)
break;
#ifdef FUTURE_EXTRA_STUFF
case 21: // Get drive icon
WriteMacInt32(pb + csParam, SonyDriveIconAddr);
break;
case 22: // Get disk icon
WriteMacInt32(pb + csParam, SonyDiskIconAddr);
break;
#endif
case 23: // Get drive info
if (info->num == 1) {
WriteMacInt32(pb + csParam, 0x0004); // Internal SuperDrive
} else {
WriteMacInt32(pb + csParam, 0x0104); // External SuperDrive
}
break;
#ifdef FUTURE_EXTRA_STUFF
case 0x4350: // Enable/disable retries ('CP') (not supported)
break;
case 0x4744: // Get raw track data ('GD') (not supported)
break;
case 0x5343: // Format and write to disk ('SC') in one pass, used by DiskCopy to speed things up
if (!ReadMacInt8(info->status + dsDiskInPlace)) {
err = offLinErr;
} else if (info->read_only) {
err = wPrErr;
} else {
// Assume that the disk is already formatted and only write the data
void *data = Mac2HostAddr(ReadMacInt32(pb + csParam + 2));
size_t actual = Sys_write(info->fh, data, 0, 2880*512);
if (actual != 2880*512)
err = writErr;
}
break;
#endif
default:
DERR("WARNING: Unknown SonyControl(%d)\n", code);
err = controlErr;
break;
}
(void)dce;
return set_dsk_err(err);
}
/*
* Driver Status() routine
*/
int16_t SonyStatus(uint32_t pb, uint32_t dce)
{
uint16_t code = ReadMacInt16(pb + csCode);
DDBG("SonyStatus %d\n", code);
// Drive valid?
sony_drinfo_t *info = get_drive_info(ReadMacInt16(pb + ioVRefNum));
if (!info)
return set_dsk_err(nsDrvErr);
int16_t err = noErr;
switch (code) {
case 6: // Return list of supported disk formats
if (ReadMacInt16(pb + csParam) > 0) { // At least one entry requested?
uint32_t adr = ReadMacInt32(pb + csParam + 2);
WriteMacInt16(pb + csParam, 1); // 1 format supported
WriteMacInt32(adr, 2880); // 2880 sectors
WriteMacInt32(adr + 4, 0xd2120050); // DD, 2 heads, 18 secs/track, 80 tracks
// Upper byte of format flags:
// bit #7: number of tracks, sectors, and heads is valid
// bit #6: current disk has this format
// bit #5: <unused>
// bit #4: double density
// bits #3..#0: number of heads
} else {
err = paramErr;
}
break;
case 8: // Get drive status
memcpy(pb + csParam + ram_get_base(), ram_get_base() + info->status, 22);
break;
case 10: // Get disk type and MFM info
DERR("**** FIXME status op 10\n");
// Hack!
WriteMacInt32(pb + csParam, 0xfe);
//ReadMacInt32(info->status + dsMFMDrive) & 0xffffff00 | 0xfe); // 0xfe = SWIM2 controller
break;
#ifdef FUTURE_EXTRA_STUFF
case 0x4350: // Measure disk speed at a given track ('CP') (not supported)
break;
case 0x4456: // Duplicator (DiskCopy) version supported ('DV'), enables the 'SC' control code above
WriteMacInt16(pb + csParam, 0x0410); // Version 4.1 and later
break;
case 0x5250: // Get floppy info record ('RP') (not supported)
break;
#endif
case 0x5343: // Get address header format byte ('SC')
WriteMacInt8(pb + csParam, 0x02); // 500 kbit/s (HD) MFM
break;
default:
DERR("WARNING: Unknown SonyStatus(%d)\n", code);
err = statusErr;
break;
}
(void)dce;
return set_dsk_err(err);
}

719
src/main.c Normal file
View file

@ -0,0 +1,719 @@
/* umac
*
* Micro Mac 128K emulator
*
* Main file with:
* - umac_ entry points,
* - main loop,
* - address decoding/memory map, dispatch to VIA/SCC/disc
* - Keyboard/mouse event dispatch
*
* Copyright 2024 Matt Evans
*
* Small portions of m68k interrupt code, error handling taken from
* Musashi, which is Copyright 1998-2002 Karl Stenerud.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <setjmp.h>
#include "machw.h"
#include "m68k.h"
#include "via.h"
#include "scc.h"
#include "rom.h"
#include "disc.h"
#ifdef PICO
#include "pico.h"
#define FAST_FUNC(x) __not_in_flash_func(x)
#else
#define FAST_FUNC(x) x
#endif
#ifdef DEBUG
#define MDBG(...) printf(__VA_ARGS__)
#else
#define MDBG(...) do {} while(0)
#endif
#define MERR(...) fprintf(stderr, __VA_ARGS__)
/* Data */
static unsigned int g_int_controller_pending = 0; /* list of pending interrupts */
static unsigned int g_int_controller_highest_int = 0; /* Highest pending interrupt */
uint8_t *_ram_base;
uint8_t *_rom_base;
int overlay = 1;
static uint64_t global_time_us = 0;
static int sim_done = 0;
static jmp_buf main_loop_jb;
static int disassemble = 0;
#define UMAC_EXECLOOP_QUANTUM 5000
static void update_overlay_layout();
////////////////////////////////////////////////////////////////////////////////
static int m68k_dump_regs(char *buf, int len)
{
int r;
int orig_len = len;
for (int i = 0; i < 8; i++) {
r = snprintf(buf, len, "D%d: %08x ", i, m68k_get_reg(NULL, M68K_REG_D0 + i));
buf += r;
len -= r;
}
*buf++ = '\n';
len--;
for (int i = 0; i < 8; i++) {
r = snprintf(buf, len, "A%d: %08x ", i, m68k_get_reg(NULL, M68K_REG_A0 + i));
buf += r;
len -= r;
}
*buf++ = '\n';
len--;
r = snprintf(buf, len, "SR: %08x SP: %08x USP: %08x ISP: %08x MSP: %08x\n",
m68k_get_reg(NULL, M68K_REG_SR),
m68k_get_reg(NULL, M68K_REG_SP),
m68k_get_reg(NULL, M68K_REG_USP),
m68k_get_reg(NULL, M68K_REG_ISP),
m68k_get_reg(NULL, M68K_REG_MSP));
return orig_len - len;
}
/* Exit with an error message. Use printf syntax. */
void exit_error(char* fmt, ...)
{
static int guard_val = 0;
char buff[500];
unsigned int pc;
va_list args;
if(guard_val)
return;
else
guard_val = 1;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
pc = m68k_get_reg(NULL, M68K_REG_PPC);
m68k_disassemble(buff, pc, M68K_CPU_TYPE_68000);
fprintf(stderr, "At %04x: %s\n", pc, buff);
m68k_dump_regs(buff, 500);
fprintf(stderr, "%s", buff);
sim_done = 1;
longjmp(main_loop_jb, 1);
}
////////////////////////////////////////////////////////////////////////////////
// VIA-related controls
static void via_ra_changed(uint8_t val)
{
static uint8_t oldval = 0x10;
// 7 = scc w/req,a,b (in, indicates RX pending, w/o IRQ)
// 6 = vid.pg2 (screen buffer select)
// 5 = hd.sel (SEL line, select head)
// 4 = overlay
// 3 = snd.pg2 (sound buffer select)
// [2:0] = sound volume
overlay = !!(val & 0x10);
if ((oldval ^ val) & 0x10) {
MDBG("OVERLAY CHANGING\n");
update_overlay_layout();
}
oldval = val;
}
static void via_rb_changed(uint8_t val)
{
// 7 = sndres (sound enable/disable)
// 6 = hblank
// 5 = mouse8 (in, mouse Y2)
// 4 = mouse4 (in, mouse X2)
// 3 = mouse7 (in, 0 = button pressed)
// [2:0] = RTC controls
(void)val;
}
static uint8_t via_ra_in()
{
return 0;
}
// Quadrature bits read from B[5:4] (Y=5, X=4)
static uint8_t via_quadbits = 0;
static uint8_t via_mouse_pressed = 0;
static uint8_t via_rb_in()
{
uint8_t v = via_quadbits;
// Mouse not pressed!
if (!via_mouse_pressed)
v |= (1 << 3);
return v;
}
/* Keyboard interface:
*
* Very roughly, it uses CB2 as bidirectional data and CB1 as clock
* always from keyboard. There's a handshake with the mac driving
* data low as a "request to start clocking", with the kbd receiving a
* byte (clocking out from SR) after that. The mac does this by
* "transmitting a byte" of all zeroes, which looks like pulling data
* low.
*
* The VIA SR has a sequence of interrupts as follows:
* - Mac pulls data low (transmits zero) then immediately loads SR
* with the data to TX (a command such as Inquiry)
* - The VIA asserts SR IRQ when the command's transmitted (the kbd
* has woken and clocked it out).
* - The keyboard -- some time later, importantly -- responds with
* a byte in SR, and VIA asserts SR IRQ again.
*
* The keyboard does nothing except for respond to commands from the
* host (i.e. there's nothing proactively transmitted).
*/
#define KBD_CMD_GET_MODEL 0x16
#define KBD_CMD_INQUIRY 0x10
#define KBD_MODEL 5
#define KBD_RSP_NULL 0x7b
static int kbd_last_cmd = 0;
static uint64_t kbd_last_cmd_time = 0;
static void via_sr_tx(uint8_t data)
{
if (kbd_last_cmd) {
MDBG("KBD: Oops, transmitting %02x whilst cmd %02x pending!\n",
data, kbd_last_cmd);
}
kbd_last_cmd = data;
kbd_last_cmd_time = global_time_us;
}
static int kbd_pending_evt = -1;
/* Emulate the keyboard: receive commands (such as an inquiry, polling
* for keypresses) and respond using via_sr_rx().
*/
static void kbd_rx(uint8_t data)
{
/* Respond to requests with potted keyboard banter */
switch (data) {
case KBD_CMD_GET_MODEL:
via_sr_rx(0x01 | (KBD_MODEL << 1));
break;
case KBD_CMD_INQUIRY:
if (kbd_pending_evt == -1) {
via_sr_rx(KBD_RSP_NULL);
} else {
via_sr_rx(kbd_pending_evt);
kbd_pending_evt = -1;
}
break;
default:
MERR("KBD: Unhandled TX %02x\n", data);
}
}
static void kbd_check_work()
{
/* Process a keyboard command a little later than the transmit
* time (i.e. not immediately, which makes the mac feel rushed
* and causes it to ignore the response to punish our
* hastiness).
*/
if (kbd_last_cmd &&
((global_time_us - kbd_last_cmd_time) > UMAC_EXECLOOP_QUANTUM)) {
MDBG("KBD: got cmd 0x%x\n", kbd_last_cmd);
kbd_rx(kbd_last_cmd);
kbd_last_cmd = 0;
}
}
void umac_kbd_event(uint8_t scancode, int down)
{
if (kbd_pending_evt >= 0) {
MDBG("KBD: Received event %02x with event %02x pending!\n",
scancode, kbd_pending_evt);
/* FIXME: Add a queue */
}
kbd_pending_evt = scancode | (down ? 0 : 0x80);
}
// VIA IRQ output hook:
static void via_irq_set(int status)
{
MDBG("[IRQ: VIA IRQ %d]\n", status);
if (status) {
// IRQ is asserted
m68k_set_virq(1, 1);
} else {
// IRQ de-asserted
m68k_set_virq(1, 0);
}
}
// Ditto, for SCC
static int scc_irq_state = 0;
static void scc_irq_set(int status)
{
MDBG("[IRQ: SCC IRQ %d]\n", status);
if (status) {
m68k_set_virq(2, 1);
} else {
m68k_set_virq(2, 0);
}
scc_irq_state = status;
}
////////////////////////////////////////////////////////////////////////////////
// IWM
static uint8_t iwm_regs[16];
void iwm_write(unsigned int address, uint8_t data)
{
unsigned int r = (address >> 9) & 0xf;
MDBG("[IWM: WR %02x -> %d]\n", data, r);
switch (r) {
default:
MDBG("[IWM: unhandled WR %02x to reg %d]\n", data, r);
}
iwm_regs[r] = data;
}
uint8_t iwm_read(unsigned int address)
{
unsigned int r = (address >> 9) & 0xf;
uint8_t data = iwm_regs[r];
switch (r) {
case 8:
data = 0xff;
break;
case 14:
data = 0x1f;
break;
default:
MDBG("[IWM: unhandled RD of reg %d]\n", r);
}
MDBG("[IWM: RD %d <- %02x]\n", r, data);
return data;
}
////////////////////////////////////////////////////////////////////////////////
static unsigned int FAST_FUNC(cpu_read_instr_normal)(unsigned int address)
{
/* Can check for 0x400000 (ROM) and otherwise RAM */
if ((address & 0xf00000) != ROM_ADDR)
return RAM_RD_ALIGNED_BE16(address & (RAM_SIZE - 1));
else
return ROM_RD_ALIGNED_BE16(address & (ROM_SIZE - 1));
}
static unsigned int FAST_FUNC(cpu_read_instr_overlay)(unsigned int address)
{
/* Need to check for both 0=ROM, 0x400000=ROM, and RAM at 0x600000...
*/
if (IS_ROM(address))
return ROM_RD_ALIGNED_BE16(address & (ROM_SIZE - 1));
else /* RAM */
return RAM_RD_ALIGNED_BE16(address & (RAM_SIZE - 1));
}
unsigned int (*cpu_read_instr)(unsigned int address) = cpu_read_instr_overlay;
/* Read data from RAM, ROM, or a device */
unsigned int FAST_FUNC(cpu_read_byte)(unsigned int address)
{
/* Most likely a RAM access, followed by a ROM access, then I/O */
if (IS_RAM(address))
return RAM_RD8(address & (RAM_SIZE - 1));
if (IS_ROM(address))
return ROM_RD8(address & (ROM_SIZE - 1));
// decode IO etc
if (IS_VIA(address))
return via_read(address);
if (IS_IWM(address))
return iwm_read(address);
if (IS_SCC_RD(address))
return scc_read(address);
if (IS_DUMMY(address))
return 0;
printf("Attempted to read byte from address %08x\n", address);
return 0;
}
unsigned int FAST_FUNC(cpu_read_word)(unsigned int address)
{
if (IS_RAM(address))
return RAM_RD16(address & (RAM_SIZE - 1));
if (IS_ROM(address))
return ROM_RD16(address & (ROM_SIZE - 1));
if (IS_TESTSW(address))
return 0;
exit_error("Attempted to read word from address %08x", address);
return 0;
}
unsigned int FAST_FUNC(cpu_read_long)(unsigned int address)
{
if (IS_RAM(address))
return RAM_RD32(address & (RAM_SIZE - 1));
if (IS_ROM(address))
return ROM_RD32(address & (ROM_SIZE - 1));
if (IS_TESTSW(address))
return 0;
exit_error("Attempted to read long from address %08x", address);
return 0;
}
unsigned int cpu_read_word_dasm(unsigned int address)
{
if (IS_RAM(address))
return RAM_RD16(address & (RAM_SIZE - 1));
if (IS_ROM(address))
return ROM_RD16(address & (ROM_SIZE - 1));
exit_error("Disassembler attempted to read word from address %08x", address);
return 0;
}
unsigned int cpu_read_long_dasm(unsigned int address)
{
if (IS_RAM(address))
return RAM_RD32(address & (RAM_SIZE - 1));
if (IS_ROM(address))
return ROM_RD32(address & (ROM_SIZE - 1));
exit_error("Dasm attempted to read long from address %08x", address);
return 0;
}
/* Write data to RAM or a device */
void FAST_FUNC(cpu_write_byte)(unsigned int address, unsigned int value)
{
if (IS_RAM(address)) {
RAM_WR8(address & (RAM_SIZE - 1), value);
return;
}
// decode IO
if (IS_VIA(address)) {
via_write(address, value);
return;
}
if (IS_IWM(address)) {
iwm_write(address, value);
return;
}
if (IS_SCC_WR(address)) {
scc_write(address, value);
return;
}
if (IS_DUMMY(address))
return;
if (address == PV_SONY_ADDR) {
int r = disc_pv_hook(value);
if (r)
exit_error("Disc PV hook failed (%02x)", value);
return;
}
printf("Ignoring write %02x to address %08x\n", value&0xff, address);
}
void FAST_FUNC(cpu_write_word)(unsigned int address, unsigned int value)
{
if (IS_RAM(address)) {
RAM_WR16(address & (RAM_SIZE - 1), value);
return;
}
printf("Ignoring write %04x to address %08x\n", value&0xffff, address);
}
void FAST_FUNC(cpu_write_long)(unsigned int address, unsigned int value)
{
if (IS_RAM(address)) {
RAM_WR32(address & (RAM_SIZE - 1), value);
return;
}
printf("Ignoring write %08x to address %08x\n", value, address);
}
/* Update function pointers for memory accessors based on overlay state/memory map layout */
static void update_overlay_layout()
{
if (overlay) {
cpu_read_instr = cpu_read_instr_overlay;
} else {
cpu_read_instr = cpu_read_instr_normal;
}
}
/* Called when the CPU pulses the RESET line */
void cpu_pulse_reset(void)
{
/* Reset IRQs etc. */
}
/* Called when the CPU acknowledges an interrupt */
int cpu_irq_ack(int level)
{
(void)level;
/* Level really means line, so do an ack per device */
return M68K_INT_ACK_AUTOVECTOR;
}
/* Implementation for the interrupt controller */
void int_controller_set(unsigned int value)
{
unsigned int old_pending = g_int_controller_pending;
g_int_controller_pending |= (1<<value);
if(old_pending != g_int_controller_pending && value > g_int_controller_highest_int)
{
g_int_controller_highest_int = value;
m68k_set_irq(g_int_controller_highest_int);
}
}
void int_controller_clear(unsigned int value)
{
g_int_controller_pending &= ~(1<<value);
for(g_int_controller_highest_int = 7;g_int_controller_highest_int > 0;g_int_controller_highest_int--)
if(g_int_controller_pending & (1<<g_int_controller_highest_int))
break;
m68k_set_irq(g_int_controller_highest_int);
}
/* Disassembler */
static void make_hex(char* buff, unsigned int pc, unsigned int length)
{
char* ptr = buff;
for(;length>0;length -= 2)
{
sprintf(ptr, "%04x", cpu_read_word_dasm(pc));
pc += 2;
ptr += 4;
if(length > 2)
*ptr++ = ' ';
}
}
void cpu_instr_callback(int pc)
{
static char buff[100];
static char buff2[100];
static unsigned int instr_size;
if (!disassemble)
return;
instr_size = m68k_disassemble(buff, pc, M68K_CPU_TYPE_68000);
make_hex(buff2, pc, instr_size);
MDBG("E %03x: %-20s: %s\n", pc, buff2, buff);
fflush(stdout);
}
int umac_init(void *ram_base, void *rom_base, disc_descr_t discs[DISC_NUM_DRIVES])
{
_ram_base = ram_base;
_rom_base = rom_base;
m68k_init();
m68k_set_cpu_type(M68K_CPU_TYPE_68000);
m68k_pulse_reset();
struct via_cb vcb = { .ra_change = via_ra_changed,
.rb_change = via_rb_changed,
.ra_in = via_ra_in,
.rb_in = via_rb_in,
.sr_tx = via_sr_tx,
.irq_set = via_irq_set,
};
via_init(&vcb);
struct scc_cb scb = { .irq_set = scc_irq_set,
};
scc_init(&scb);
disc_init(discs);
return 0;
}
void umac_opt_disassemble(int enable)
{
disassemble = enable;
}
#define MOUSE_MAX_PENDING_PIX 30
static int pending_mouse_deltax = 0;
static int pending_mouse_deltay = 0;
/* Provide mouse input (movement, button) data.
*
* X is positive going right; Y is positive going upwards.
*/
void umac_mouse(int deltax, int deltay, int button)
{
pending_mouse_deltax += deltax;
pending_mouse_deltay += deltay;
/* Clamp if the UI has flooded with lots and lots of steps!
*/
if (pending_mouse_deltax > MOUSE_MAX_PENDING_PIX)
pending_mouse_deltax = MOUSE_MAX_PENDING_PIX;
if (pending_mouse_deltax < -MOUSE_MAX_PENDING_PIX)
pending_mouse_deltax = -MOUSE_MAX_PENDING_PIX;
if (pending_mouse_deltay > MOUSE_MAX_PENDING_PIX)
pending_mouse_deltay = MOUSE_MAX_PENDING_PIX;
if (pending_mouse_deltay < -MOUSE_MAX_PENDING_PIX)
pending_mouse_deltay = -MOUSE_MAX_PENDING_PIX;
/* FIXME: The movement might take a little time, but this
* posts the button status immediately. Probably OK, but the
* mismatch might be perceptible.
*/
via_mouse_pressed = button;
}
static void mouse_tick()
{
/* Periodically, check if the mouse X/Y deltas are non-zero.
* If a movement is required, encode one step in X and/or Y
* and deduct from the pending delta.
*
* The step ultimately posts an SCC IRQ, so we _don't_ try to
* make any more steps while an IRQ is currently pending.
* (Currently, that means a previous step's DCD IRQ event
* hasn't yet been consumed by the OS handler. In future, if
* SCC is extended with other IRQ types, then just checking
* the IRQ status is technically too crude, but should still
* be fine given the timeframes.)
*/
if (pending_mouse_deltax == 0 && pending_mouse_deltay == 0)
return;
if (scc_irq_state == 1)
return;
static int old_dcd_a = 0;
static int old_dcd_b = 0;
/* Mouse X/Y quadrature signals are wired to:
* VIA Port B[4] & SCC DCD_A for X
* VIA Port B[5] & SCC DCD_B for Y
*
* As VIA mouse signals aren't sampled until IRQ, can do this
* in one step, toggling existing DCD states and setting VIA
* either equal or opposite to DCD:
*/
int dcd_a = old_dcd_a;
int dcd_b = old_dcd_b;
int deltax = pending_mouse_deltax;
int deltay = pending_mouse_deltay;
uint8_t qb = via_quadbits;
if (deltax) {
dcd_a = !dcd_a;
qb = (qb & ~0x10) | ((deltax < 0) == dcd_a ? 0x10 : 0);
pending_mouse_deltax += (deltax > 0) ? -1 : 1;
MDBG(" px %d, oldpx %d", pending_mouse_deltax, deltax);
}
if (deltay) {
dcd_b = !dcd_b;
qb = (qb & ~0x20) | ((deltay < 0) == dcd_b ? 0x20 : 0);
pending_mouse_deltay += (deltay > 0) ? -1 : 1;
MDBG(" py %d, oldpy %d", pending_mouse_deltay, deltay);
}
MDBG("\n");
via_quadbits = qb;
old_dcd_a = dcd_a;
old_dcd_b = dcd_b;
scc_set_dcd(dcd_a, dcd_b);
}
void umac_reset()
{
overlay = 1;
m68k_pulse_reset();
}
/* Called by the disc code when an eject op happens. */
void umac_disc_ejected()
{
#ifdef SIM
exit(1);
#else
umac_reset();
#endif
}
/* Run the emulator for about a frame.
* Returns 0 for not-done, 1 when an exit/done condition arises.
*/
int umac_loop()
{
setjmp(main_loop_jb);
const int us = UMAC_EXECLOOP_QUANTUM;
m68k_execute(us*8);
global_time_us += us;
// Device polling
via_tick(global_time_us);
mouse_tick();
kbd_check_work();
return sim_done;
}

120
src/rom.c Normal file
View file

@ -0,0 +1,120 @@
/* umac ROM-patching code
*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <inttypes.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include "machw.h"
#include "rom.h"
#ifdef DEBUG
#define RDBG(...) printf(__VA_ARGS__)
#else
#define RDBG(...) do {} while(0)
#endif
#define RERR(...) fprintf(stderr, __VA_ARGS__)
#define ROM_PLUSv3_VERSION 0x4d1f8172
#define ROM_PLUSv3_SONYDRV 0x17d30
#define M68K_INST_NOP 0x4e71
////////////////////////////////////////////////////////////////////////////////
// Replacement drivers to thwack over the ROM
static const uint8_t sony_driver[] = {
#include "sonydrv.h"
};
////////////////////////////////////////////////////////////////////////////////
static uint32_t rom_get_version(uint8_t *rom_base)
{
#if BYTE_ORDER == LITTLE_ENDIAN
return __builtin_bswap32(*(uint32_t *)rom_base);
#else
return *(uint32_t *)rom_base);
#endif
}
/* Not perf-critical, so open-coding to support BE _and_ unaligned access */
#define ROM_WR32(offset, data) do { \
rom_base[(offset)+0] = ((data) >> 24) & 0xff; \
rom_base[(offset)+1] = ((data) >> 16) & 0xff; \
rom_base[(offset)+2] = ((data) >> 8) & 0xff; \
rom_base[(offset)+3] = (data) & 0xff; \
} while (0)
#define ROM_WR16(offset, data) do { \
rom_base[(offset)+0] = ((data) >> 8) & 0xff; \
rom_base[(offset)+1] = (data) & 0xff; \
} while (0)
static void rom_patch_plusv3(uint8_t *rom_base)
{
/* Inspired by patches in BasiliskII!
*/
/* Disable checksum check by bodging out the comparison, an "eor.l d3, d1",
* into a simple eor.l d1,d1:
*/
ROM_WR16(0xd92, 0xb381 /* eor.l d1, d1 */); // Checksum compares 'same' kthx
/* Replace .Sony driver: */
memcpy(rom_base + ROM_PLUSv3_SONYDRV, sony_driver, sizeof(sony_driver));
/* Register the FaultyRegion for the Sony driver: */
ROM_WR32(ROM_PLUSv3_SONYDRV + sizeof(sony_driver) - 4, PV_SONY_ADDR);
/* To do:
*
* - No IWM init
* - new Sound?
*/
}
int rom_patch(uint8_t *rom_base)
{
uint32_t v = rom_get_version(rom_base);
int r = -1;
/* See https://docs.google.com/spreadsheets/d/1wB2HnysPp63fezUzfgpk0JX_b7bXvmAg6-Dk7QDyKPY/edit#gid=840977089
*/
switch(v) {
case ROM_PLUSv3_VERSION:
rom_patch_plusv3(rom_base);
r = 0;
break;
default:
RERR("Unknown ROM version %08x, no patching", v);
}
return r;
}

303
src/scc.c Normal file
View file

@ -0,0 +1,303 @@
/* SCC 85C30 model: just enough to model DCD interrupts.
* God, I hate this chip, it's fugly AF.
*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <inttypes.h>
#include "scc.h"
#ifdef DEBUG
#define SDBG(...) printf(__VA_ARGS__)
#else
#define SDBG(...) do {} while(0)
#endif
#define SERR(...) fprintf(stderr, __VA_ARGS__)
////////////////////////////////////////////////////////////////////////////////
// SCC
static uint8_t scc_reg_ptr = 0;
static uint8_t scc_mie = 0;
static uint8_t scc_read_acks = 0;
static uint8_t scc_status_hi = 0;
static uint8_t scc_ie[2] = {0};
#define SCC_IE_ZEROCOUNT 0x02
#define SCC_IE_DCD 0x08
#define SCC_IE_SYNCHUNT 0x10
#define SCC_IE_CTS 0x20
#define SCC_IE_TXUNDER 0x40
#define SCC_IE_ABORT 0x80
static uint8_t scc_irq_pending = 0;
#define SCC_IP_B_EXT 0x01
#define SCC_IP_A_EXT 0x08
static uint8_t scc_vec = 0;
static uint8_t scc_irq = 0;
static struct scc_cb scc_callbacks;
static uint8_t scc_dcd_pins = 0; // 0 = A, 1 = B
static uint8_t scc_dcd_a_changed = 0;
static uint8_t scc_dcd_b_changed = 0;
static void scc_assess_irq();
////////////////////////////////////////////////////////////////////////////////
void scc_init(struct scc_cb *cb)
{
if (cb)
scc_callbacks = *cb;
}
void scc_set_dcd(int a, int b)
{
uint8_t v = (a ? 1 : 0) | (b ? 2 : 0);
if ((v ^ scc_dcd_pins) & 1)
scc_dcd_a_changed = 1;
if ((v ^ scc_dcd_pins) & 2)
scc_dcd_b_changed = 1;
scc_dcd_pins = v;
scc_assess_irq();
}
////////////////////////////////////////////////////////////////////////////////
// WR0: Reg pointers, command
static void scc_wr0(uint8_t data)
{
scc_reg_ptr = data & 7;
if (data & 0xc0) {
// Reset commands, e.g. CRC generators, EOM latch
}
int cmd = (data & 0x38) >> 3;
switch (cmd) {
case 0: // Null
break;
case 1: // Point high
scc_reg_ptr |= 8;
break;
// 2: reset Ext/Status IRQs
// enables RR0 status to be re-latched (cause IRQ again if sometihng's pending?)
default:
SDBG("[ SCC WR0: Command %d unhandled]\n", cmd);
}
}
// WR2: Interrupt vector
static void scc_wr2(uint8_t data)
{
scc_vec = data;
}
// WR9: Master Interrupt control and reset commands
static void scc_wr9(uint8_t data)
{
// 7:8 = Various reset commands, channel A/B/HW reset
if (data & 0xc0) {
}
scc_mie = !!(data & 0x08);
scc_read_acks = !!(data & 0x20);
scc_status_hi = !!(data & 0x10);
}
// WR15: External status interrupt enable control
static void scc_wr15(int AnB, uint8_t data)
{
scc_ie[AnB] = data;
}
// RR0: Transmit and Receive buffer status and external status
static uint8_t scc_rr0(int AnB)
{
uint8_t v = 0;
// [3]: If IE[channel].DCD = 0, reports /DCD pin state. Else, reports
// state as of last pin change (?).. samples as of IRQ?
//
// FIXME: sample it?
if (AnB) {
v = (scc_dcd_pins & 1) ? 0x08 : 0;
} else {
v = (scc_dcd_pins & 2) ? 0x08 : 0;
}
return v;
}
// RR2:
// Some special behaviour; if scc_read_acks, then a read will do an ack, de-assert IRQ
// If read from A, raw vector. If read from B, "modified vector" == ?
static uint8_t scc_rr2(int AnB)
{
if (AnB)
return scc_vec;
//status high shift << 3
// B external = 001
// A external = 101
uint8_t v = 0;
if (scc_irq_pending & SCC_IP_A_EXT) {
v = 5;
scc_irq_pending &= ~SCC_IP_A_EXT;
} else if (scc_irq_pending & SCC_IP_B_EXT) {
v = 1;
scc_irq_pending &= ~SCC_IP_B_EXT;
} else {
v = 0; // 3:1 011 or 6:4 110
}
// VIS
// FIXME: consume/clear in pending..?
//
if (scc_status_hi)
v = (scc_vec & 0x8f) | (v << 4);
else
v = (scc_vec & 0xf1) | (v << 1);
return v;
}
// RR3: Interrupt Pending Register (A only)
static uint8_t scc_rr3(int AnB)
{
if (!AnB)
return 0;
return scc_irq_pending;
}
// RR15: Reflects WR15 (interrupt enables)
static uint8_t scc_rr15(int AnB)
{
return scc_ie[AnB] & 0xfa;
}
////////////////////////////////////////////////////////////////////////////////
// Call after a state change: checks MIE, interrupt enables,
// and (de)asserts IRQ output if necessary
static void scc_assess_irq()
{
if (scc_dcd_a_changed && (scc_ie[1] & SCC_IE_DCD)) {
scc_irq_pending |= SCC_IP_A_EXT;
scc_dcd_a_changed = 0;
}
if (scc_dcd_b_changed && (scc_ie[0] & SCC_IE_DCD)) {
scc_irq_pending |= SCC_IP_B_EXT;
scc_dcd_b_changed = 0;
}
if (scc_irq_pending && scc_mie) {
if (!scc_irq) {
if (scc_callbacks.irq_set)
scc_callbacks.irq_set(1);
scc_irq = 1;
}
} else {
// No IRQ. Drop line if it was asserted:
if (scc_irq) {
if (scc_callbacks.irq_set)
scc_callbacks.irq_set(0);
scc_irq = 0;
}
}
}
void scc_write(unsigned int address, uint8_t data)
{
unsigned int r = (address >> 1) & 0x3;
unsigned int AnB = !!(r & 1);
unsigned int DnC = !!(r & 2);
if (DnC) {
SDBG("[ SCC: Data write (%c) ignored]\n", 'B' - AnB);
} else {
SDBG("[SCC: WR %02x -> WR%d%c]\n",
data, scc_reg_ptr, 'B' - AnB);
switch (scc_reg_ptr) {
case 0:
scc_wr0(data);
break;
case 2:
scc_wr2(data);
scc_reg_ptr = 0;
break;
case 9:
scc_wr9(data);
scc_reg_ptr = 0;
break;
case 15:
scc_wr15(AnB, data);
scc_reg_ptr = 0;
break;
default:
SDBG("[SCC: unhandled WR %02x to reg %d]\n",
data, scc_reg_ptr);
scc_reg_ptr = 0;
}
}
scc_assess_irq();
}
uint8_t scc_read(unsigned int address)
{
uint8_t data = 0;
unsigned int r = (address >> 1) & 0x3;
unsigned int AnB = !!(r & 1);
unsigned int DnC = !!(r & 2);
if (DnC) {
SDBG("[ SCC: Data read (%c) ignored]\n", 'B' - AnB);
} else {
SDBG("[SCC: RD <- RR%d%c = ",
scc_reg_ptr, 'B' - AnB);
switch (scc_reg_ptr) {
case 0:
data = scc_rr0(AnB);
break;
case 2:
data = scc_rr2(AnB);
break;
case 3:
data = scc_rr3(AnB);
break;
case 15:
data = scc_rr15(AnB);
break;
default:
SDBG("(unhandled!) ");
data = 0;
}
SDBG("%02x]\n", data);
}
// Reads always reset the pointer
scc_reg_ptr = 0;
return data;
}

330
src/unix_main.c Normal file
View file

@ -0,0 +1,330 @@
/* umac SDL2 main application
*
* Opens an SDL2 window, allocates RAM/loads and patches ROM, routes
* mouse/keyboard updates to umac, and blits framebuffer to the
* display.
*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include "SDL.h"
#include "rom.h"
#include "umac.h"
#include "machw.h"
#include "disc.h"
#include "keymap_sdl.h"
static void print_help(char *n)
{
printf("Syntax: %s <options>\n"
"\t-r <rom path>\t\tDefault 'rom.bin'\n"
"\t-W <rom dump path>\tDump ROM after patching\n"
"\t-d <disc path>\n"
"\t-i\t\t\tDisassembled instruction trace\n", n);
}
#define DISP_SCALE 2
static uint32_t framebuffer[DISP_WIDTH*DISP_HEIGHT];
/* Blit a 1bpp FB to a 32BPP RGBA output. SDL2 doesn't appear to support
* bitmap/1bpp textures, so expand.
*/
static void copy_fb(uint32_t *fb_out, uint8_t *fb_in)
{
// Output L-R, big-endian shorts, with bits in MSB-LSB order:
for (int y = 0; y < DISP_HEIGHT; y++) {
for (int x = 0; x < DISP_WIDTH; x += 16) {
uint8_t plo = fb_in[x/8 + (y * 512/8) + 0];
uint8_t phi = fb_in[x/8 + (y * 512/8) + 1];
for (int i = 0; i < 8; i++) {
*fb_out++ = (plo & (0x80 >> i)) ? 0 : 0xffffffff;
}
for (int i = 0; i < 8; i++) {
*fb_out++ = (phi & (0x80 >> i)) ? 0 : 0xffffffff;
}
}
}
}
/**********************************************************************/
/* The emulator core expects to be given ROM and RAM pointers,
* with ROM already pre-patched. So, load the file & use the
* helper to patch it, then pass it in.
*
* In an embedded scenario the ROM is probably const and in flash,
* and so ought to be pre-patched.
*/
int main(int argc, char *argv[])
{
void *ram_base;
void *rom_base;
void *disc_base;
char *rom_filename = "rom.bin";
char *rom_dump_filename = NULL;
char *ram_filename = "ram.bin";
char *disc_filename = NULL;
int ofd;
int ch;
int opt_disassemble = 0;
////////////////////////////////////////////////////////////////////////
// Args
while ((ch = getopt(argc, argv, "r:d:W:ih")) != -1) {
switch (ch) {
case 'r':
rom_filename = strdup(optarg);
break;
case 'i':
opt_disassemble = 1;
break;
case 'd':
disc_filename = strdup(optarg);
break;
case 'W':
rom_dump_filename = strdup(optarg);
break;
case 'h':
default:
print_help(argv[0]);
return 1;
}
}
////////////////////////////////////////////////////////////////////////
// Load memories/discs
printf("Opening ROM '%s'\n", rom_filename);
ofd = open(rom_filename, O_RDONLY);
if (ofd < 0) {
perror("ROM");
return 1;
}
struct stat sb;
fstat(ofd, &sb);
off_t _rom_size = sb.st_size;
rom_base = mmap(0, _rom_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, ofd, 0);
if (rom_base == MAP_FAILED) {
printf("Can't mmap ROM!\n");
return 1;
}
if (rom_patch(rom_base)) {
printf("Failed to patch ROM\n");
return 1;
}
if (rom_dump_filename) {
int rfd = open(rom_dump_filename, O_CREAT | O_TRUNC | O_RDWR, 0655);
if (rfd < 0) {
perror("ROM dump");
return 1;
}
ssize_t written = write(rfd, rom_base, _rom_size);
if (written < 0) {
perror("ROM dump write");
return 1;
}
if (written < _rom_size) {
printf("*** WARNING: Short write to %s!\n",
rom_dump_filename);
} else {
printf("Dumped ROM to %s\n", rom_dump_filename);
}
close(rfd);
}
/* Set up RAM, shared file map: */
ofd = open(ram_filename, O_CREAT | O_TRUNC | O_RDWR, 0644);
if (ofd < 0) {
perror("RAM");
return 1;
}
if (ftruncate(ofd, RAM_SIZE)) {
perror("RAM ftruncate");
return 1;
}
ram_base = mmap(0, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, ofd, 0);
if (ram_base == MAP_FAILED) {
perror("RAM mmap");
return 1;
}
printf("RAM mapped at %p\n", (void *)ram_base);
disc_descr_t discs[DISC_NUM_DRIVES] = {0};
if (disc_filename) {
printf("Opening disc '%s'\n", disc_filename);
// FIXME: R/W, >1 disc
ofd = open(disc_filename, O_RDONLY);
if (ofd < 0) {
perror("Disc");
return 1;
}
fstat(ofd, &sb);
size_t disc_size = sb.st_size;
disc_base = mmap(0, disc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, ofd, 0);
if (disc_base == MAP_FAILED) {
printf("Can't mmap disc!\n");
return 1;
}
printf("Disc mapped at %p, size %ld\n", (void *)disc_base, disc_size);
discs[0].base = disc_base;
discs[0].read_only = 1;
discs[0].size = disc_size;
}
////////////////////////////////////////////////////////////////////////
// SDL/UI init
SDL_Renderer *renderer;
SDL_Texture *texture;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window = SDL_CreateWindow("umac",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
DISP_WIDTH * DISP_SCALE,
DISP_HEIGHT * DISP_SCALE,
0);
if (!window) {
perror("SDL window");
return 1;
}
SDL_SetWindowGrab(window, SDL_TRUE);
SDL_SetRelativeMouseMode(SDL_TRUE);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
perror("SDL renderer");
return 1;
}
texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
DISP_WIDTH,
DISP_HEIGHT);
if (!texture) {
perror("SDL texture");
return 1;
}
////////////////////////////////////////////////////////////////////////
// Emulator init
umac_init(ram_base, rom_base, discs);
umac_opt_disassemble(opt_disassemble);
////////////////////////////////////////////////////////////////////////
// Main loop
int done = 0;
uint64_t loops = 0;
int mouse_button = 0;
uint64_t last_vsync = 0;
uint64_t last_1hz = 0;
do {
struct timeval tv_now;
SDL_Event event;
int mousex = 0;
int mousey = 0;
loops++;
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
done = 1;
break;
case SDL_KEYDOWN:
case SDL_KEYUP: {
int c = SDLScan2MacKeyCode(event.key.keysym.scancode);
c = (c << 1) | 1;
printf("Key 0x%x -> 0x%x\n", event.key.keysym.scancode, c);
if (c != MKC_None)
umac_kbd_event(c, (event.type == SDL_KEYDOWN));
} break;
case SDL_MOUSEMOTION:
mousex = event.motion.xrel;
mousey = -event.motion.yrel;
break;
case SDL_MOUSEBUTTONDOWN:
mouse_button = 1;
break;
case SDL_MOUSEBUTTONUP:
mouse_button = 0;
break;
}
}
umac_mouse(mousex, mousey, mouse_button);
done |= umac_loop();
gettimeofday(&tv_now, NULL);
uint64_t now_usec = (tv_now.tv_sec * 1000000) + tv_now.tv_usec;
/* Passage of time: */
if ((now_usec - last_vsync) >= 16667) {
umac_vsync_event();
last_vsync = now_usec;
/* Cheapo framerate limiting: */
copy_fb(framebuffer, ram_get_base() + umac_get_fb_offset());
SDL_UpdateTexture(texture, NULL, framebuffer,
DISP_WIDTH * sizeof (Uint32));
/* Scales texture up to window size */
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
if ((now_usec - last_1hz) >= 1000000) {
umac_1hz_event();
last_1hz = now_usec;
}
} while (!done);
return 0;
}

326
src/via.c Normal file
View file

@ -0,0 +1,326 @@
/* umac VIA emulation
*
* Bare minimum support for ports A/B, shift register, and IRQs.
* A couple of Mac-specific assumptions in here, as per comments...
*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <inttypes.h>
#include <stdio.h>
#include "via.h"
#ifdef DEBUG
#define VDBG(...) printf(__VA_ARGS__)
#else
#define VDBG(...) do {} while(0)
#endif
#define VERR(...) fprintf(stderr, __VA_ARGS__)
#define VIA_RB 0
#define VIA_RA 1
#define VIA_DDRB 2
#define VIA_DDRA 3
#define VIA_T1CL 4
#define VIA_T1CH 5
#define VIA_T1LL 6
#define VIA_T1LH 7
#define VIA_T2CL 8
#define VIA_T2CH 9
#define VIA_SR 10
#define VIA_ACR 11
#define VIA_PCR 12
#define VIA_IFR 13
#define VIA_IRQ_CA 0x01
#define VIA_IRQ_CB 0x02
#define VIA_IRQ_SR 0x04
#define VIA_IER 14
#define VIA_RA_ALT 15 // No-handshake version
static const char *dbg_regnames[] = {
"VIA_RB",
"VIA_RA",
"VIA_DDRB",
"VIA_DDRA",
"VIA_T1CL",
"VIA_T1CH",
"VIA_T1LL",
"VIA_T1LH",
"VIA_T2CL",
"VIA_T2CH",
"VIA_SR",
"VIA_ACR",
"VIA_PCR",
"VIA_IFR",
"VIA_IER",
"VIA_RA_ALT",
};
static uint8_t via_regs[16];
static struct via_cb via_callbacks;
static int irq_status = 0;
static uint8_t irq_active = 0;
static uint8_t irq_enable = 0;
void via_init(struct via_cb *cb)
{
for (int i = 0; i < 16; i++)
via_regs[i] = 0;
via_regs[VIA_RA] = 0x10; // Overlay, FIXME
if (cb)
via_callbacks = *cb;
}
static void via_update_rega(uint8_t data)
{
if ((via_regs[VIA_RA] ^ data) && via_callbacks.ra_change)
via_callbacks.ra_change(data);
}
static void via_update_regb(uint8_t data)
{
if ((via_regs[VIA_RB] ^ data) && via_callbacks.rb_change)
via_callbacks.rb_change(data);
}
static int sr_tx_pending = -1;
static void via_update_sr(uint8_t data)
{
/* Mac assumption: SR active when ACR SR control selects
* external clock:
*/
if ((via_regs[VIA_ACR] & 0x1c) == 0x1c) {
if (sr_tx_pending >= 0) {
/* Doh! */
VDBG("[VIA: SR send whilst send (%02x) active!]\n", sr_tx_pending);
}
/* When SR's written, the ROM'll wait for the IRQ
* indicating the byte's transmitted. At that point,
* it'll expect a response (but not too soon). So,
* flag that the TX occurred, and mark tbe byte
* pending to deal with in the runloop "a little bit
* later" -- the response seems to get lost if it's
* reflected back too soon.
*/
sr_tx_pending = data;
irq_active |= VIA_IRQ_SR;
} else if ((via_regs[VIA_ACR] & 0x1c) == 0x18) {
/* Mac sends a byte of zeroes fuelled by phi2, as a
* method to pull KbdData low (to get the kbd's
* attention). The d/s implies SRMC=110 completion
* should also trigger IRQ vector 2, but empirically
* this screws things up and code doesn't seem to
* expect it. So don't trigger IRQ.
*/
VDBG("[VIA: SR send (val %02x)]\n", data);
via_regs[VIA_SR] = 0;
}
}
/* Called when the VIA_IRQ_SR interrupt is acknowledged, i.e. that the
* Mac's aware of the last TX/RX. We can then use it to pace out the
* transmit callback action -- the response then cannot race with the
* IRQ showing the TX has completed.
*/
static void via_sr_done()
{
if (sr_tx_pending >= 0) {
uint8_t data = (uint8_t)sr_tx_pending;
sr_tx_pending = -1;
if (via_callbacks.sr_tx)
via_callbacks.sr_tx(data);
}
}
// 6 Timer 1
// 5 Timer 2
// 4 Keyboard clock
// 3 Keyboard data bit
// 2 Keyboard data ready
// 1 CA2: Vertical blanking interrupt
// 0 CA1: One-second interrupt
static void via_assess_irq()
{
int irq = 0;
static uint8_t last_active = 0;
uint8_t active = irq_enable & irq_active & 0x7f;
irq = active != 0;
if (active != last_active) {
VDBG("[VIA: IRQ state now %02x]\n", active);
last_active = active;
}
if (irq != irq_status) {
via_callbacks.irq_set(irq);
irq_status = irq;
}
}
/* A[12:9] select regs */
void via_write(unsigned int address, uint8_t data)
{
unsigned int r = (address >> 9) & 0xf;
const char *rname = dbg_regnames[r];
(void)rname; // Unused if !DEBUG
VDBG("[VIA: WR %02x -> %s (0x%x)]\n", data, rname, r);
int dowrite = 1;
switch (r) {
case VIA_RA:
case VIA_RA_ALT:
via_update_rega(data);
r = VIA_RA;
break;
case VIA_RB:
via_update_regb(data);
break;
case VIA_DDRA:
break; // FIXME
case VIA_DDRB:
break; // FIXME
case VIA_SR:
via_update_sr(data);
dowrite = 0;
break;
case VIA_IER:
if (data & 0x80)
irq_enable |= data & 0x7f;
else
irq_enable &= ~(data & 0x7f);
break;
case VIA_IFR: {
int which_acked = (irq_active & data);
irq_active &= ~data;
/* If ISR is acking the SR IRQ, a TX or RX is complete,
* and we might want to then trigger other actions.
*/
if (which_acked & VIA_IRQ_SR)
via_sr_done();
} break;
case VIA_PCR:
VDBG("VIA PCR %02x\n", data);
break;
default:
VDBG("[VIA: unhandled WR %02x to %s (reg 0x%x)]\n", data, rname, r);
}
if (dowrite)
via_regs[r] = data;
via_assess_irq();
}
uint8_t via_read_ifr()
{
uint8_t active = irq_enable & irq_active & 0x7f;
return irq_active | (active ? 0x80 : 0);
}
static uint8_t via_read_rega()
{
uint8_t data = (via_callbacks.ra_in) ? via_callbacks.ra_in() : 0;
uint8_t ddr = via_regs[VIA_DDRA];
// DDR=1 is output, so take ORA version:
return (ddr & via_regs[VIA_RA]) | (~ddr & data);
}
static uint8_t via_read_regb()
{
uint8_t data = (via_callbacks.rb_in) ? via_callbacks.rb_in() : 0;
uint8_t ddr = via_regs[VIA_DDRB];
return (ddr & via_regs[VIA_RB]) | (~ddr & data);
}
uint8_t via_read(unsigned int address)
{
unsigned int r = (address >> 9) & 0xf;
const char *rname = dbg_regnames[r];
uint8_t data = via_regs[r];
(void)rname; // Unused if !DEBUG
switch (r) {
case VIA_RA:
case VIA_RA_ALT:
data = via_read_rega();
break;
case VIA_RB:
data = via_read_regb();
break;
case VIA_SR:
irq_active &= ~0x04;
break;
case VIA_IER:
data = 0x80 | irq_enable;
break;
case VIA_IFR:
data = via_read_ifr();
break;
default:
VDBG("[VIA: unhandled RD of %s (reg 0x%x)]\n", rname, r);
}
VDBG("[VIA: RD %02x <- %s (0x%x)]\n", data, rname, r);
via_assess_irq();
return data;
}
/* Time param in us */
void via_tick(uint64_t time)
{
(void)time;
// FIXME: support actual timers.....!
}
/* External world pipes CA1/CA2 events (passage of time) in here:
*/
void via_caX_event(int ca)
{
if (ca == 1) {
irq_active |= VIA_IRQ_CA;
} else if (ca == 2) {
irq_active |= VIA_IRQ_CB;
}
via_assess_irq();
}
void via_sr_rx(uint8_t val)
{
/* If SR config in ACR is external (yes! a Mac assumption!)
* then fill SR with value and trigger SR IRQ:
*/
VDBG("[VIAL sr_rx %02x (acr %02x)]\n", val, via_regs[VIA_ACR]);
if ((via_regs[VIA_ACR] & 0x1c) == 0x0c) {
via_regs[VIA_SR] = val;
irq_active |= VIA_IRQ_SR;
VDBG("[VIA sr_rx received, IRQ pending]\n");
via_assess_irq();
} else {
VDBG("[VIA ACR SR state %02x, not receiving]\n", via_regs[VIA_ACR]);
}
}

74
tools/decorate_ops.py Executable file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env python3
#
# Shittiest quick hack "Perlython" script to take a list of special
# (hot) function names and in-place edit the m68kops.c to decorate
# those function declarations with M68K_FAST_FUNC.
#
# Copyright 2024 Matt Evans
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
import os
import re
import sys
if len(sys.argv) != 3:
print("Syntax: %s <C source> <fn list file>" % (sys.argv[0]))
sys.exit(1)
cfile = sys.argv[1]
fnlistfile = sys.argv[2]
# Read function list first:
fns = []
with open(fnlistfile, 'r') as flf:
for l in flf:
fns.append(l.rstrip())
print("Read %d functions" % (len(fns)))
# Read entire C source, process, then write back out:
clines = []
with open(cfile, 'r') as cf:
for l in cf:
clines.append(l)
# Process the source, writing it out again:
# FIXME: From param/cmdline:
def decorate_func(fname):
return "M68K_FAST_FUNC(%s)" % (fname)
def fn_special(fname):
return fname in fns
with open(cfile, 'w') as cf:
for l in clines:
m = re.search(r'^static void ([^(]*)\(void\)$', l)
if m and fn_special(m.group(1)):
# Does function exist in special list?
cf.write("static void %s(void) /* In SRAM */\n" % (decorate_func(m.group(1))))
else:
cf.write(l)

200
tools/fn_hot200.txt Normal file
View file

@ -0,0 +1,200 @@
m68k_op_add_32_re_di
m68k_op_adda_32_a
m68k_op_cmpi_8_di
m68k_op_addq_32_d
m68k_op_sgt_8_d
m68k_op_move_32_pd_i
m68k_op_clr_8_ai
m68k_op_andi_8_d
m68k_op_bls_8
m68k_op_add_16_er_pi
m68k_op_move_16_d_ix
m68k_op_or_8_er_di
m68k_op_lsr_16_s
m68k_op_tst_8_d
m68k_op_cmpi_8_d
m68k_op_sub_16_er_di
m68k_op_move_32_di_pi
m68k_op_mulu_16_d
m68k_op_bset_32_s_d
m68k_op_neg_16_d
m68k_op_muls_16_di
m68k_op_move_32_di_ai
m68k_op_move_16_di_pi
m68k_op_cmpa_32_ai
m68k_op_move_32_pd_aw
m68k_op_seq_8_d
m68k_op_move_16_ai_d
m68k_op_addq_8_di
m68k_op_bclr_8_s_ai
m68k_op_pea_32_ix
m68k_op_clr_8_di
m68k_op_move_8_d_pi7
m68k_op_ext_16
m68k_op_bset_8_s_aw
m68k_op_and_16_er_aw
m68k_op_rol_16_s
m68k_op_pea_32_aw
m68k_op_move_32_pi_ai
m68k_op_eori_8_d
m68k_op_move_32_pd_ai
m68k_op_move_32_pd_ix
m68k_op_suba_32_a
m68k_op_lea_32_pcdi
m68k_op_move_8_ai_i
m68k_op_move_16_di_i
m68k_op_not_16_d
m68k_op_tst_16_aw
m68k_op_move_32_ai_ai
m68k_op_jsr_32_di
m68k_op_subq_32_d
m68k_op_bne_16
m68k_op_move_16_frs_pd
m68k_op_move_16_tos_pi
m68k_op_ori_16_tos
m68k_op_cmpi_16_di
m68k_op_clr_32_ai
m68k_op_ext_32
m68k_op_move_16_pi_di
m68k_op_move_8_d_ix
m68k_op_bset_8_r_ai
m68k_op_ror_8_s
m68k_op_move_8_di_aw
m68k_op_move_8_di_di
m68k_op_move_32_pd_pi
m68k_op_move_32_ai_d
m68k_op_move_32_di_i
m68k_op_addi_16_d
m68k_op_clr_16_aw
m68k_op_and_8_er_d
m68k_op_clr_16_d
m68k_op_tst_16_pi
m68k_op_move_8_d_ai
m68k_op_move_32_ai_a
m68k_op_clr_8_aw
m68k_op_move_16_pd_d
m68k_op_tst_32_ai
m68k_op_asl_16_s
m68k_op_sub_32_er_a
m68k_op_tst_16_di
m68k_op_clr_32_pi
m68k_op_tst_8_pi7
m68k_op_move_16_d_ai
m68k_op_cmpa_32_aw
m68k_op_cmpm_32
m68k_op_clr_16_ai
m68k_op_move_32_di_aw
m68k_op_adda_32_d
m68k_op_cmp_16_ai
m68k_op_beq_16
m68k_op_addq_32_a
m68k_op_bcc_8
m68k_op_move_32_di_di
m68k_op_cmpi_16_ai
m68k_op_move_32_di_d
m68k_op_bclr_32_s_d
m68k_op_jmp_32_pcdi
m68k_op_tst_16_ai
m68k_op_bset_8_s_ai
m68k_op_move_8_di_d
m68k_op_addq_8_d
m68k_op_cmp_32_di
m68k_op_tst_32_d
m68k_op_clr_32_d
m68k_op_move_16_pd_i
m68k_op_lsr_8_s
m68k_op_jmp_32_al
m68k_op_bra_16
m68k_op_move_32_ai_aw
m68k_op_adda_16_i
m68k_op_movea_32_ix
m68k_op_dbeq_16
m68k_op_move_32_d_aw
m68k_op_move_32_d_ai
m68k_op_sub_32_er_d
m68k_op_and_32_er_aw
m68k_op_move_16_pi_i
m68k_op_tst_8_ai
m68k_op_add_16_er_d
m68k_op_clr_16_di
m68k_op_move_16_pi_d
m68k_op_move_32_ai_di
m68k_op_clr_32_di
m68k_op_btst_8_s_di
m68k_op_move_16_di_d
m68k_op_clr_16_pd
m68k_op_tst_8_di
m68k_op_cmp_16_pi
m68k_op_move_8_d_pi
m68k_op_btst_32_s_d
m68k_op_ble_8
m68k_op_swap_32
m68k_op_move_8_d_di
m68k_op_cmp_16_d
m68k_op_blt_8
m68k_op_roxl_8_s
m68k_op_move_8_ai_d
m68k_op_subq_16_a
m68k_op_tst_8_aw
m68k_op_clr_32_pd
m68k_op_move_32_d_d
m68k_op_move_32_pi_d
m68k_op_jsr_32_ai
m68k_op_cmp_32_d
m68k_op_bmi_8
m68k_op_jsr_32_pcdi
m68k_op_cmp_16_di
m68k_op_add_16_re_pi
m68k_op_cmp_32_ai
m68k_op_move_32_pi_pi
m68k_op_movea_32_a
m68k_op_move_16_d_aw
m68k_op_addq_16_a
m68k_op_move_32_d_di
m68k_op_bge_8
m68k_op_bpl_8
m68k_op_addq_16_d
m68k_op_move_32_pd_d
m68k_op_adda_16_d
m68k_op_movea_32_d
m68k_op_pea_32_di
m68k_op_movea_32_aw
m68k_op_move_32_pd_di
m68k_op_lea_32_di
m68k_op_tst_16_d
m68k_op_move_32_d_a
m68k_op_move_32_pd_a
m68k_op_link_16
m68k_op_unlk_32
m68k_op_jmp_32_ai
m68k_op_move_32_ai_pi
m68k_op_bsr_8
m68k_op_add_32_er_d
m68k_op_bsr_16
m68k_op_bra_8
m68k_op_move_32_di_ix
m68k_op_movea_32_pi
m68k_op_1010
m68k_op_lsl_16_s
m68k_op_move_16_d_di
m68k_op_subq_32_a
m68k_op_lea_32_aw
m68k_op_moveq_32
m68k_op_move_16_d_d
m68k_op_move_32_di_a
m68k_op_movea_32_ai
m68k_op_cmp_32_aw
m68k_op_andi_16_d
m68k_op_subq_16_d
m68k_op_bne_8
m68k_op_move_16_d_pi
m68k_op_move_32_d_pi
m68k_op_movem_32_re_pd
m68k_op_movem_32_er_pi
m68k_op_movea_32_di
m68k_op_dbf_16
m68k_op_bgt_8
m68k_op_cmpi_16_d
m68k_op_bcs_8
m68k_op_rts_32
m68k_op_beq_8

102
tools/mem2scr.c Normal file
View file

@ -0,0 +1,102 @@
/* mem2scr: dump an XBM screencapture from a Mac 128/512 memory dump
*
* Copyright 2024 Matt Evans
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <inttypes.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
struct stat sb;
if (argc != 2) {
printf("Syntax: %s <ram image>\n", argv[0]);
return 1;
}
fd = open(argv[1], O_RDONLY);
if (fd < 0) {
printf("Can't open %s!\n", argv[1]);
return 1;
}
fstat(fd, &sb);
uint8_t *ram_base;
ram_base = mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ram_base == MAP_FAILED) {
printf("Can't mmap RAM!\n");
return 1;
}
uint8_t *scr_base = ram_base;
// ScrnBase = 0x01A700 (128K) or 0x07A700 (512K)
// OK so.... check RAM is expected size:
if (sb.st_size == 0x20000) {
scr_base += 0x1a700;
} else if (sb.st_size == 0x80000) {
scr_base += 0x7a700;
} else {
printf("RAM size (%" PRId64 ") should be 128 or 512K! Trying to continue...\n", sb.st_size);
scr_base += sb.st_size - 0x5900;
}
// Open out.xbm:
const char *outfname = "out.xbm";
FILE *outf = fopen(outfname, "w");
if (!outf) {
printf("Can't open %s!\n", outfname);
return 1;
}
fprintf(outf, "#define out_width 512\n"
"#define out_height 342\n"
"static char out_bits[] = {\n");
// Output L-R, big-endian shorts, with bits in MSB-LSB order:
for (int y = 0; y < 342; y++) {
for (int x = 0; x < 512; x += 16) {
uint8_t plo = scr_base[x/8 + (y * 512/8) + 0];
uint8_t phi = scr_base[x/8 + (y * 512/8) + 1];
uint8_t ob = 0;
for (int i = 0; i < 8; i++) {
ob |= (plo & (1 << i)) ? (0x80 >> i) : 0;
}
fprintf(outf, "%02x, ", ob);
ob = 0;
for (int i = 0; i < 8; i++) {
ob |= (phi & (1 << i)) ? (0x80 >> i) : 0;
}
fprintf(outf, "%02x, ", ob);
}
}
fprintf(outf, "};\n");
fclose(outf);
return 0;
}