First public release
Squashed for release at v0.1
This commit is contained in:
commit
563626a382
27 changed files with 4390 additions and 0 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "external/Musashi"]
|
||||
path = external/Musashi
|
||||
url = https://github.com/evansm7/Musashi.git
|
||||
89
Makefile
Normal file
89
Makefile
Normal 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
273
README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
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
BIN
doc/umac_sys32_desktop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
1
external/Musashi
vendored
Submodule
1
external/Musashi
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0b85a7a648e7b4d279b41e096c4d67ad354a82f7
|
||||
282
include/b2_macos_util.h
Normal file
282
include/b2_macos_util.h
Normal 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
54
include/cpu_cb.h
Normal 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
47
include/disc.h
Normal 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
158
include/keymap.h
Normal 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
131
include/keymap_sdl.h
Normal 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
252
include/m68kconf.h
Normal 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
129
include/machw.h
Normal 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
34
include/rom.h
Normal 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
40
include/scc.h
Normal 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
13
include/sonydrv.h
Normal 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
56
include/umac.h
Normal 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
48
include/via.h
Normal 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
102
macsrc/sonydrv.S
Normal 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
504
src/disc.c
Normal 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
719
src/main.c
Normal 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
120
src/rom.c
Normal 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
303
src/scc.c
Normal 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
330
src/unix_main.c
Normal 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
326
src/via.c
Normal 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
74
tools/decorate_ops.py
Executable 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
200
tools/fn_hot200.txt
Normal 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
102
tools/mem2scr.c
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue