From 563626a38273656ff08d13a4799fa5d1028b215d Mon Sep 17 00:00:00 2001 From: Matt Evans Date: Sun, 31 Mar 2024 02:29:28 +0100 Subject: [PATCH] First public release Squashed for release at v0.1 --- .gitmodules | 3 + Makefile | 89 +++++ README.md | 273 ++++++++++++++ doc/umac_sys32_desktop.png | Bin 0 -> 2971 bytes external/Musashi | 1 + include/b2_macos_util.h | 282 +++++++++++++++ include/cpu_cb.h | 54 +++ include/disc.h | 47 +++ include/keymap.h | 158 ++++++++ include/keymap_sdl.h | 131 +++++++ include/m68kconf.h | 252 +++++++++++++ include/machw.h | 129 +++++++ include/rom.h | 34 ++ include/scc.h | 40 +++ include/sonydrv.h | 13 + include/umac.h | 56 +++ include/via.h | 48 +++ macsrc/sonydrv.S | 102 ++++++ src/disc.c | 504 ++++++++++++++++++++++++++ src/main.c | 719 +++++++++++++++++++++++++++++++++++++ src/rom.c | 120 +++++++ src/scc.c | 303 ++++++++++++++++ src/unix_main.c | 330 +++++++++++++++++ src/via.c | 326 +++++++++++++++++ tools/decorate_ops.py | 74 ++++ tools/fn_hot200.txt | 200 +++++++++++ tools/mem2scr.c | 102 ++++++ 27 files changed, 4390 insertions(+) create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 README.md create mode 100644 doc/umac_sys32_desktop.png create mode 160000 external/Musashi create mode 100644 include/b2_macos_util.h create mode 100644 include/cpu_cb.h create mode 100644 include/disc.h create mode 100644 include/keymap.h create mode 100644 include/keymap_sdl.h create mode 100644 include/m68kconf.h create mode 100644 include/machw.h create mode 100644 include/rom.h create mode 100644 include/scc.h create mode 100644 include/sonydrv.h create mode 100644 include/umac.h create mode 100644 include/via.h create mode 100644 macsrc/sonydrv.S create mode 100644 src/disc.c create mode 100644 src/main.c create mode 100644 src/rom.c create mode 100644 src/scc.c create mode 100644 src/unix_main.c create mode 100644 src/via.c create mode 100755 tools/decorate_ops.py create mode 100644 tools/fn_hot200.txt create mode 100644 tools/mem2scr.c diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c71bba3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/Musashi"] + path = external/Musashi + url = https://github.com/evansm7/Musashi.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..176a8f1 --- /dev/null +++ b/Makefile @@ -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 $@ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1064cff --- /dev/null +++ b/README.md @@ -0,0 +1,273 @@ +# Micro Mac (umac) + +v0.1 15 June 2024 + +This is a minimalist Apple Macintosh 128K/512K emulator. It uses the +_Musashi_ 68K interpreter and lashes the absolute minimum in hardware +emulation around it so as to boot and run basic apps. It's been +tested with System 2.0 up to System 3.2, and runs MacWrite, MacDraw, and +Missile Command. + +You can write, draw, and unwind with an apocalyptic game. Live well. + +![Desktop screenshot](doc/umac_sys32_desktop.png) + +This was written as one of the old "hey I wonder how hard it'd be to" +exercises; bit of fun, but not intended as a replacement for existing +emulators. But, it might inspire others working on similar (better) +projects, and as a basis to explore these early but innovative +machines. + +This grew without a plan, just playing around with the Mac128K ROM: it +turns out that there's _very_ little HW emulation required to get to +an Unhappy Mac screen, or even an attempt to boot via FDD +(disc-question-mark screen). Then, you discover IWM is way painful to +emulate, and spend 80% of the time working around that – almost all +Mac emulators immediately patch the ROM to insert a paravirt block +driver over the top of the IWM driver, circumventing the problem. +That's what _MicroMac_ does, too. + +This emulates the following hardware: + + * VIA A/B GPIO ports, and IRQs (1Hz and Vsync) + * VIA shift register for keyboard + * SCC DCD pin change interrupts, for mouse + * Paravirtualised disc storage + * Defaults to 128K of RAM, but will run as a Mac 512K by changing + a `#define` (`RAM_SIZE`). + +There's no emulation for: + + * IWM/realistic floppy drives + * More than one disc, or runtime image-switching + * Sound (a lot of work for a beep) + * VIA timers (Space Invaders runs too fast, probably because of this) + * Serial/printer/Appletalk + * Framebuffer switching: the Mac supports double-buffering by moving + the base of screen memory via the VIA (ha), but I haven't seen + anything using it. Easy to add. + * Disc writes (easy to enable, untested). I didn't need to save my + MacWrite essays so far. + +The emulator is structured so as to be easily embeddable in other +projects. You initialise it, and pass in UI events (such as +keyboard/mouse), and read video from the framebuffer: + +``` + umac_init(pointer_to_ram, pointer_to_patched_rom, + pointer_to_struct_describing_mmaped_disc_images); + + while (happy) { + if (one_second_passed) + umac_1hz_event(); + + if (vsync_happened_60Hz_kthx) { + umac_vsync_event(); + + update_UI_video_from(pointer_to_ram + umac_get_fb_offset()); + } + + if (keyboard_event_happened) + umac_kbd_event(mac_scancode); + + if (mouse_movement_happened) + umac_mouse(delta_x, delta_y, button_state); + + umac_loop(); + } +``` + +A simple SDL2-based frontend builds on Linux. + + +# Prerequisites + +To build on Linux, you'll need `SDL2` installed (packaged as +`libsdl2-dev` on Ubuntu). + +Musashi is included as a submodule, currently a custom branch with +some optimisations for static/tiny/fast builds. `git submodule update --init` + +You'll then need some Mac artefacts. Because we need to patch the ROM +a little, we require specific versions. Currently the only supported +ROM is _Mac Plus V3 ROM_, checksum `4d1f8172`. + +(Note this makes this a _Mac 128Ke_, fancy!) + +Then, get a boot disk. Any System up to 3.2 should work fine (though +I don't think I've tried 1.0). I just tested a random German System 3.2 disc +from WinWorld and it works fine. You might want to use Mini vMac or +Basilisk (i.e. a proper emulator) to prepare a disc image with some +apps to run... MacDraw! + +It doesn't have to be a specific size. A 400K or 800K floppy image +works. Make sure it's a raw image; the first two bytes should be the +chars 'LK'. Some emulators append a header (which can be `dd`'d off). + + +# Build + +``` +make +``` + +No surprises here. No autoconf either. :D You can add a `DEBUG=1` to +make to compile in debug spew. + +This will configure and build _Musashi_, umac, and `unix_main.c` as +the SDL2 frontend. The _Musashi_ build generates a few files +internally: + + * `m68kops.c` is generated from templates in `m68k_in.c`: this + "multiplies out" N instructions by the 200,000 addressing modes of + 68K, generating specialised code for each individual opcode. + * Using a custom/new Musashi build option, a large (64K pointers) + opcode lookup table is generated, 16-bit 68K opcode is generated + at build time, as `m68ki_static_instruction_jump_table`. This was + previously generated at runtime, i.e. used up RAM. + +After the _Musashi_ prepare step, `tools/decorate_ops.py` does an +in-place update of `m68kops.c` to decorate some of the opcode +functions with `M68K_FAST_FUNC`. This macro does nothing by default, +but can be defined to apply a function attribute. If this project is +being built in the RP2040 Pico environment, the functions gain an +attribute to place them in RAM instead of flash – making them much +faster. + +Some low-quality (so uncommitted) and undocumented profiling code was +used to generate the `tools/fn_hot200.txt` list of the 200 most +frequently-used 68K opcodes. This was generated by profiling a System +3.2 boot, using MacWrite and Missile Command for a bit. :D Out of +1967 opcodes, these hottest 200 opcodes represent 98% of the dynamic +execution. (See _RISC_.) + + +# Running + +``` +./main -r -d +``` + +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 ` 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 + + * + * + + +# 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. diff --git a/doc/umac_sys32_desktop.png b/doc/umac_sys32_desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..d5d69abe04c6e92aa96240fc6446054e272420ab GIT binary patch literal 2971 zcmZ{mXHb)g7KXo+k2HlWAfS{)rGzG-*M$V>NR=iv)J2K{MhHD5pui%jh0RT~w06M;lojyBNebBZ^qT2$YW7=GZmD zeUM%XmBsZnYeu|To|5++q7@TViq9CREDl`1oFFbI%ms!S$xE0ptMk-CF>L#!kyN!h zwEZLa3%`lv!J}{63#(CE&({ndkweN27p4}Xx5f%$BXsp!1FTD2=cS&aYTgN@MEaU$@Lbl@5LLW$;5z93rgSg22V2lR`Y5Pt2e3QLfy$d#zI; zf>!~;B_|ef#)krELt!y*Z#b3;@6juzUl?d`buFc0`4cs8LXoN}uJ2NC*hGjYe(5$( z0u$0H^3ieeAs_YzG(UhOGVy*0naO=TM^GdY67iU+d;2v#D~8U)#d6Vxm~#k;wO^sS zOEXZ;r3W4TMy%pOCG?-N*YWKNQ@&)^W8fap<-GE_eH+3xI0qYvq)cx9$lVqgceb`tHU)R+s^7^ZhphZQ z?=!=8xq@glK9oZz;CFITLZCWovik$)elOqF57Geq%_p5-3zf?SQST-Q$Pt1ZZL+6> z!V2|b&wIr0hYGD6t7i@GjCeQ(SMDV{v0{>ZB;Z3><+xytk z!?)G1WypilMJr(Ui)@BL(?5#uygBDVi3yTMvXmh|F=+=UkU1Io@LgV8%52k@?yB!z zCJDZ*I<+1js676(EEWZxS3?x62Hx3B2%aD(WTp z;P7kFg8U4p%tXj*TcCn;7=I+68B028vV3{-)cla(2?75I6gV<|)rRxDMPKB-nQcA5 zLF()bCXD6E!G7_+Y>pp5$9eJ}V#0f>UkHv7(OjOSY$GCdatDX2+gr6mv8U@_Cj42$ z&lnbN{`lkKIk0@4;+e&Pb_BlFA{C@me6cCdpO|i5fT?oavq%$LD$nfm_8Z~s>->X zz0@#mP!NN36x7~i1l^fA47E8%)fkFz$WOVB&$|#nL@McAVEtXORHlDtSrw_2d#@vA z&Q|V<6c)>f#f@be30r0AZ;*q-T+v8Rlx?z&Q}&@r(4-`j)Cc2MC-I7>g6Ec8TMBu& zJ7-F>OhuTYUB}?bxrD*X>4Is`D7qA$d^ZS_V0T0iHKhhK7Q_K zzaFldQu?Zhe7~9|&kt{VI@9^5)prEfNX9kQa9@|a_w=wUHfH)S8v*d0V8?oHynpa6 zb=KG|!o}r1T}r6?CGlP{{0g0?;oiZWU z_`cJ{SEV?YJV?2uKtonNT%>DyVAdi}xczRD@*$=*REGa3d{QVgDhNb+ z6k-Y5e1^C_$LXEQJw4}JJc8lHzV=UBqKOfoU=Q(bjvtQHnEH`jdd3gA4*muN) z=0GoZ@QShQ>4uULo1)OsAz2|tk#;0|_tWe}U)bNWdz*`AM~xiKARgW2h+o<}y2;{_ zE>HGi1mokP14hef7%r-3zHI!)7`|#Oqp5awwBtprMyvYWsJQk~&8=stx@D4niyxWg z)J}8d)l7ne)Z#~&JjdTb{I~Mp?M%FFTnDLLU023_*pBk^ICq&$9@^j)@NbcW_1G!sXbRS>#)=(7T{v5%Oy5s!p`~xNh%X2PL8Z+VKOMFYoz&m{7Hott-lfA z!Qbb<3BzR*l+e=e2^^GJAG0ZNbF;dP#7*jF?jH9CvZ0Z!#r?Nd;lgFW#$zRZm2;RW z!wg-=Zob)L`YdM*b@6Y0z?>a!Ms9Z>uo0)!fU;DEl@%XyW0#t16C&z_X=|l};tcs# zdXhNfi{Dx6F)Cy)9|M#Xc6BqP2=$GgXlgT(%xq-oka!Obpqt!F_}liCo!QOj!B^)& zs{ypYxd)*7Sj4N>Aor`rkO1Yq@Mo^pzS6X&0%>Irj7$90cZlL0XQA!HaQQuY82_8L zYf?j=lI8oTyBuF-_S|tDwv`2#vq_OUd*KAD29eZ3^)$x2Z9~1FXX+Cf?Q7|I^cyMG zg>9iwI<|;39QJwC$`jqBV^6S#ZQR=-NNDV)SQ5*2i)!}R_&~P6bS-TPdP?2AW|}u5 z-w=)mmw`gQJ)6jVboXy05k@XY+9G5^qPfx41Nb||gdN>$JaY-6AkC;%^wsea%3|PT za0UI~qmZz?DGjj=FSL%B1wMUd7qB$M-KT_D^Chocoq6_IR^)b%1`%X3@8GM@*=2r$ zyvT>@F4yreP+3HV<&5`_#g`gD6PAvN9k{|>a6JghdEEtN-wa2)Z7si}Rjsm7Sjaw@ zu%ut^-Aju+Y;67TGg1!v70jq(O&0O +#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 diff --git a/include/disc.h b/include/disc.h new file mode 100644 index 0000000..f58fafb --- /dev/null +++ b/include/disc.h @@ -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 + +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 diff --git a/include/keymap.h b/include/keymap.h new file mode 100644 index 0000000..94af625 --- /dev/null +++ b/include/keymap.h @@ -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 diff --git a/include/keymap_sdl.h b/include/keymap_sdl.h new file mode 100644 index 0000000..6998b85 --- /dev/null +++ b/include/keymap_sdl.h @@ -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 diff --git a/include/m68kconf.h b/include/m68kconf.h new file mode 100644 index 0000000..6be8da4 --- /dev/null +++ b/include/m68kconf.h @@ -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 */ diff --git a/include/machw.h b/include/machw.h new file mode 100644 index 0000000..424a517 --- /dev/null +++ b/include/machw.h @@ -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 diff --git a/include/rom.h b/include/rom.h new file mode 100644 index 0000000..3f15f44 --- /dev/null +++ b/include/rom.h @@ -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 + +int rom_patch(uint8_t *rom_base); + +#endif diff --git a/include/scc.h b/include/scc.h new file mode 100644 index 0000000..1817d4f --- /dev/null +++ b/include/scc.h @@ -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 + diff --git a/include/sonydrv.h b/include/sonydrv.h new file mode 100644 index 0000000..2f5e0a3 --- /dev/null +++ b/include/sonydrv.h @@ -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 diff --git a/include/umac.h b/include/umac.h new file mode 100644 index 0000000..88af686 --- /dev/null +++ b/include/umac.h @@ -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 diff --git a/include/via.h b/include/via.h new file mode 100644 index 0000000..ac9fb3c --- /dev/null +++ b/include/via.h @@ -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 + +/* 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 diff --git a/macsrc/sonydrv.S b/macsrc/sonydrv.S new file mode 100644 index 0000000..86505e6 --- /dev/null +++ b/macsrc/sonydrv.S @@ -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 diff --git a/src/disc.c b/src/disc.c new file mode 100644 index 0000000..ca8f1a2 --- /dev/null +++ b/src/disc.c @@ -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 +#include +#include +#include + +#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: + // 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); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..4f693fe --- /dev/null +++ b/src/main.c @@ -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 +#include +#include +#include +#include + +#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< 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< 0;g_int_controller_highest_int--) + if(g_int_controller_pending & (1<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; +} + diff --git a/src/rom.c b/src/rom.c new file mode 100644 index 0000000..c6f718f --- /dev/null +++ b/src/rom.c @@ -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 +#include +#include +#include +#include + +#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; +} + diff --git a/src/scc.c b/src/scc.c new file mode 100644 index 0000000..38c21ae --- /dev/null +++ b/src/scc.c @@ -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 +#include + +#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; +} + diff --git a/src/unix_main.c b/src/unix_main.c new file mode 100644 index 0000000..8935cb0 --- /dev/null +++ b/src/unix_main.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#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 \n" + "\t-r \t\tDefault 'rom.bin'\n" + "\t-W \tDump ROM after patching\n" + "\t-d \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; +} diff --git a/src/via.c b/src/via.c new file mode 100644 index 0000000..c6864e7 --- /dev/null +++ b/src/via.c @@ -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 +#include + +#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]); + } +} diff --git a/tools/decorate_ops.py b/tools/decorate_ops.py new file mode 100755 index 0000000..a0eb2f0 --- /dev/null +++ b/tools/decorate_ops.py @@ -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 " % (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) diff --git a/tools/fn_hot200.txt b/tools/fn_hot200.txt new file mode 100644 index 0000000..0e47be1 --- /dev/null +++ b/tools/fn_hot200.txt @@ -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 diff --git a/tools/mem2scr.c b/tools/mem2scr.c new file mode 100644 index 0000000..c40065c --- /dev/null +++ b/tools/mem2scr.c @@ -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 +#include +#include +#include +#include +#include + + +int main(int argc, char *argv[]) +{ + int fd; + struct stat sb; + + if (argc != 2) { + printf("Syntax: %s \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; +}