Compare commits

...

17 commits

Author SHA1 Message Date
d44474c24e Enable absolute mouse (todo: add flag to turn it off)
&& fix(?) a crash if mouse events were delivered too early
2025-08-01 16:11:05 -05:00
d86ebf1bb8 Turbocharge mouse movement
.. by storing directly into the vars used by the mac plus ROM,
rather than working through the IRQ & I/O subsystems.
2025-08-01 15:41:43 -05:00
d7d30c5608 Add PV UART read (into emulator) 2025-08-01 15:30:54 -05:00
831a115881 Add a debug uart 2025-07-07 09:14:13 +01:00
884330872b I want chunky pixels please 2025-07-07 09:13:42 +01:00
5dd8f92ee0 Implement arrow keys
I don't know if this is correct, but it makes the arrow
keys work in the finder.

ringbuf is taken from micropython.
2025-07-07 09:13:25 +01:00
ac0ccec3f9 Try to mount a new disc always
.. even if the eject action didn't appear to eject anything
2025-04-16 19:39:08 +02:00
f34ec2a1b9 Initialize local variable 2025-04-16 19:37:58 +02:00
15d0a7b623 ensure to close disc after it's mmap'd 2025-04-04 21:27:52 +02:00
19f975ac6c fix mounting discs when called for
by not clearing the accrun flag, the dialog to insert a previously
ejected disc successfully causes the emulated mac to cycle to the
correct disc.

This worked in several cases I tested, though in the specific case
of an older OS (About Finder says 5.3) with a newer floppy
(HyperCardBootSystem7.img) the hypercard floppy keeps adding a new
desktop icon everytime it's ejected then re-loaded, and the request
to load it can never be satisfied.
2025-04-04 18:47:23 +02:00
b4e41a6739 multi discs
you can specify a directory as the second -d argument and then cycle
among them by dragging to trash. Using eject doesn't work very well,
because it leaves a ghost on the desktop and accessing it has no way
of completing.
2025-04-04 16:17:19 +02:00
46c886d9e5 WIP disc collections 2025-04-04 08:16:41 +02:00
cd98358391 Use emul_ret when calling a trap 2025-04-03 20:12:20 +02:00
a59b9fbeb3 manipulate the DiskInPlace flag better 2025-03-29 21:45:02 -05:00
24c8b1ebe6 It successfully mounted a 2nd disc!
with caveats
2025-03-29 21:22:53 -05:00
b12811d05b Add basic dependency analysis 2025-03-29 10:17:46 -05:00
a16d721ce8 unix_main: Support specification of multiple disc images.
This doesn't do anything yet, only the first disc image is
actually mounted.
2025-03-29 10:04:12 -05:00
16 changed files with 685 additions and 205 deletions

View file

@ -66,7 +66,7 @@ $(MUSASHI)/m68kops.c $(MUSASHI)/m68kops.h:
prepare: $(MUSASHI)/m68kops.c $(MUSASHI)/m68kops.h
%.o: %.c
$(CC) $(CFLAGS) $(CFLAGS_CFG) -c $< -o $@
$(CC) $(CFLAGS) $(CFLAGS_CFG) -c $< -o $@ -MMD -MF $*.d
main: $(OBJS)
@echo Linking $(OBJS)
@ -74,7 +74,7 @@ main: $(OBJS)
clean:
make -C $(MUSASHI) clean
rm -f $(MY_OBJS) main
rm -f $(MY_OBJS) $(OBJS:%.o=%.d) main
################################################################################
# Mac driver sources (no need to generally rebuild
@ -94,3 +94,5 @@ sonydrv.bin: macsrc/sonydrv.S
cpp $< | $(M68K_AS) -o sonydrv.o
$(M68K_LD) sonydrv.o -o sonydrv.elf -Ttext=0
$(M68K_OBJCOPY) sonydrv.elf -O binary --keep-section=.text $@
-include $(OBJS:%.o=%.d)

2
external/Musashi vendored

@ -1 +1 @@
Subproject commit b3144f12c1296d8fbb600eea13f5db71ffb1b8d8
Subproject commit 8dcac80091b745e9181281fbf63cdd04a95b963a

View file

@ -27,16 +27,21 @@
#include <inttypes.h>
typedef struct disc_desc disc_descr_t;
typedef int (*disc_op_read)(void *ctx, uint8_t *data, unsigned int offset, unsigned int len);
typedef int (*disc_op_write)(void *ctx, uint8_t *data, unsigned int offset, unsigned int len);
typedef struct {
typedef disc_descr_t *(*disc_op_next)(void *ctx);
struct disc_desc {
uint8_t *base;
unsigned int size;
int read_only;
void *op_ctx;
disc_op_read op_read;
disc_op_write op_write;
} disc_descr_t;
disc_op_next op_next;
};
#define DISC_NUM_DRIVES 2
@ -48,5 +53,5 @@ typedef struct {
*/
void disc_init(disc_descr_t discs[DISC_NUM_DRIVES]);
int disc_pv_hook(uint8_t opcode);
void disc_tick();
#endif

View file

@ -56,10 +56,10 @@
#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_Left /* 0x46 */ (0x06 | 0x40)
#define MKC_Right /* 0x42 */ (0x02 | 0x40)
#define MKC_Down /* 0x48 */ (0x08 | 0x40)
#define MKC_Up /* 0x4D */ (0x0d | 0x40)
#define MKC_Minus 0x1B
#define MKC_Equal 0x18

View file

@ -243,6 +243,13 @@
#define M68K_FAST_FUNC(x) __not_in_flash_func(x)
#endif
/* If ON, CPU will support "emul_op" instructions from 0x7100..0x713f by
* calling m68k_end_timeslice when they are encountered. The caller can (must)
* check the IR register and implement the operation.
* If off, these remain illegal instructions.
*/
#define M68K_EMUL_OP OPT_ON
#endif /* M68K_COMPILE_FOR_MAME */
/* ======================================================================== */

View file

@ -35,6 +35,7 @@
#define RAM_HIGH_ADDR 0x600000
#define PV_SONY_ADDR 0xc00069 /* Magic address for replacement driver PV ops */
#define PV_UART_ADDR 0xc0006A /* Magic address for debug uart */
////////////////////////////////////////////////////////////////////////////////
// RAM accessors

View file

@ -35,6 +35,8 @@ 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);
/* check if scc master interrupt is enabled */
int scc_get_mie();
#endif

View file

@ -1,13 +1,16 @@
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
0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x6a,
0x00, 0x74, 0x00, 0x86, 0x00, 0xb2, 0x05, 0x2e, 0x53, 0x6f, 0x6e, 0x79,
0xa0, 0x2f, 0x26, 0x7a, 0x00, 0x9c, 0x16, 0xbc, 0x00, 0x04, 0x48, 0xe7,
0x00, 0xc0, 0x10, 0x3c, 0x00, 0x3c, 0xa7, 0x1e, 0x24, 0x48, 0x4c, 0xdf,
0x03, 0x00, 0xb5, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x2c,
0x26, 0x7a, 0x00, 0x7a, 0x16, 0xbc, 0x00, 0x00, 0x76, 0x02, 0xd5, 0xfc,
0x00, 0x00, 0x00, 0x24, 0x20, 0x03, 0x48, 0x40, 0x00, 0x40, 0xff, 0xfb,
0x20, 0x4a, 0xa0, 0x4e, 0x95, 0xfc, 0x00, 0x00, 0x00, 0x1e, 0x53, 0x43,
0x66, 0x00, 0xff, 0xea, 0x4e, 0x75, 0x70, 0xe9, 0x4e, 0x75, 0x24, 0x7a,
0x00, 0x4c, 0x14, 0xbc, 0x00, 0x01, 0x60, 0x1a, 0x24, 0x7a, 0x00, 0x42,
0x14, 0xbc, 0x00, 0x02, 0x0c, 0x68, 0x00, 0x01, 0x00, 0x1a, 0x66, 0x0a,
0x4e, 0x75, 0x24, 0x7a, 0x00, 0x30, 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, 0x00, 0x00

View file

@ -34,6 +34,7 @@ int umac_loop(void);
void umac_reset(void);
void umac_opt_disassemble(int enable);
void umac_mouse(int deltax, int deltay, int button);
void umac_absmouse(int x, int y, int button);
void umac_kbd_event(uint8_t scancode, int down);
static inline void umac_vsync_event(void)

View file

@ -12,6 +12,7 @@
#define SONY_REF_NUM -5
#define SONY_STATUS_SIZE 30
#define SONY_DRIVE_COUNT 2
.org 0
.globl _start
@ -31,26 +32,41 @@ name:
.byte 5
.ascii ".Sony"
accrun:
.word 0xA02F // PostEvent
move.l (faulting_address), %a3
move.b #4, (%a3) // Fault! end timeslice
open:
// Open()
// Allocate drive status block and pass to Open routine:
movem.l %a0-%a1, -(%sp)
move.b #SONY_STATUS_SIZE, %d0
move.b #SONY_STATUS_SIZE*SONY_DRIVE_COUNT, %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
// Now add the drives:
mov.l #SONY_DRIVE_COUNT, %d3
add.l #6+SONY_STATUS_SIZE*(SONY_DRIVE_COUNT-1), %a2
3:
move.l %d3, %d0
swap %d0 // refnum in top half of d0, lower is 0000
ori.w #(0xffff & SONY_REF_NUM), %d0 // index & refnum in d0
move.l %a2, %a0
add.l #6, %a0
.word 0xa04e // AddDrive
sub.l #SONY_STATUS_SIZE, %a2 // next drive on next iteration
subq #1, %d3
bne 3b // more drives to add?
1: rts
2: moveq #-23, %d0
rts

View file

@ -16,6 +16,7 @@
#include "m68k.h"
#include "machw.h"
#undef DEBUG
#ifdef DEBUG
#define DDBG(...) printf(__VA_ARGS__)
#else
@ -26,6 +27,8 @@
extern void umac_disc_ejected(void);
static uint8_t accrun_flag;
// 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);
@ -66,6 +69,10 @@ int disc_pv_hook(uint8_t opcode)
DDBG("[Disc: STATUS]\n");
d0 = SonyStatus(ADR24(a0), ADR24(a1));
break;
case 4: // upcall helper
DDBG("[Disc: end timeslice]\n");
m68k_end_timeslice();
break;
default:
DERR("[Disc PV op %02x unhandled!]\n", opcode);
@ -138,6 +145,7 @@ typedef struct sony_drive_info {
void *op_ctx;
disc_op_read op_read; // Callback for read (when data == 0)
disc_op_write op_write; // '' '' write ''
disc_op_next op_next; // Callback for mount next disc
} sony_drinfo_t;
// List of drives handled by this driver
@ -160,17 +168,24 @@ static sony_drinfo_t *get_drive_info(int num)
* Initialization
*/
void SonyInit(disc_descr_t discs[DISC_NUM_DRIVES])
static void SonyInit1(sony_drinfo_t *drive, disc_descr_t *disc)
{
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;
drives[0].op_ctx = discs[0].op_ctx;
drives[0].op_read = discs[0].op_read;
drives[0].op_write = discs[0].op_write;
// FIXME: Disc 2
drive->to_be_mounted = 1;
drive->read_only = disc->read_only;
drive->data = disc->base;
drive->size = disc->size;
drive->op_ctx = disc->op_ctx;
drive->op_read = disc->op_read;
drive->op_write = disc->op_write;
drive->op_next = disc->op_next;
}
static void SonyInit(disc_descr_t discs[DISC_NUM_DRIVES])
{
for(int i = 0; i < DISC_NUM_DRIVES; i++) {
drives[i].num = 0; // set in SonyOpen
SonyInit1(&drives[i], &discs[i]);
}
}
/*
@ -227,38 +242,44 @@ int16_t SonyOpen(uint32_t pb, uint32_t dce, uint32_t status)
set_dsk_err(0);
// Install drives
//for (int drnum = 0; drnum < NUM_DRIVES; drnum++) {
const int drnum = 0;
sony_drinfo_t *info = &drives[drnum];
int free_drive_number = 1;
for (int drnum = 0; drnum < DISC_NUM_DRIVES; drnum++) {
sony_drinfo_t *info = &drives[drnum];
info->num = FindFreeDriveNumber(1); // ? 1 for internal, 2 for external
info->to_be_mounted = 0;
info->num = FindFreeDriveNumber(free_drive_number); // ? 1 for internal, 2 for external
free_drive_number = info->num + 1;
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);
// 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 + 30 * drnum;
DDBG(" DrvSts at %08x gets drnum %d num %d\n", info->status, drnum, info->num);
// 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)
// 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;
// If disk in drive...
if (info->size) {
if (drnum == 0)
WriteMacInt8(info->status + dsDiskInPlace, 1); // Inserted removable disk
else
info->to_be_mounted = 1;
WriteMacInt8(info->status + dsWriteProt, info->read_only ? 0xff : 0);
DDBG(" disk inserted, flagging for mount\n");
}
}
// Original code ddded drive to drive queue here (invoked trap
// Original code added 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;
@ -369,14 +390,8 @@ int16_t SonyControl(uint32_t pb, uint32_t dce)
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);
accrun_flag = 1;
return set_dsk_err(noErr);
}
}
@ -411,7 +426,15 @@ int16_t SonyControl(uint32_t pb, uint32_t dce)
// SysEject(info->fh);
WriteMacInt8(info->status + dsDiskInPlace, 0);
umac_disc_ejected();
if(info->num == 1)
umac_disc_ejected();
}
if(info->op_next) {
DERR("DISC: mount new\n");
disc_descr_t *new_disc = info->op_next(info->op_ctx);
if (new_disc) {
SonyInit1(info, new_disc);
}
}
break;
@ -534,3 +557,46 @@ int16_t SonyStatus(uint32_t pb, uint32_t dce)
return set_dsk_err(err);
}
#define M68K_REG_LAST (M68K_REG_CPU_TYPE)
static int PostEvent(int type, int num) {
DDBG("PostEvent EventCode=%d EventMsg=%d\n", type, num);
uint32_t regs[M68K_REG_LAST];
for(int i=0; i<M68K_REG_LAST; i++)
regs[i] = m68k_get_reg(NULL, i);
m68k_set_reg(M68K_REG_D0, num);
m68k_set_reg(M68K_REG_A0, type);
uint32_t new_sp = regs[M68K_REG_SP] - 4;
cpu_write_word(new_sp, 0xA02F); // PostEvent
cpu_write_word(new_sp+2, 0x7180); // emul_ret
m68k_set_reg(M68K_REG_PC, new_sp); // PostEvent
m68k_set_reg(M68K_REG_SP, new_sp); // Emul_Ret
int used = m68k_execute(80000);
int result = m68k_get_reg(NULL, M68K_REG_D0);
if (used >= 20000 || m68k_get_reg(NULL, M68K_REG_PC) != new_sp + 4 ||
m68k_get_reg(NULL, M68K_REG_SP) != new_sp) {
DERR("trap call didn't seem to work. used=%d PC=%08x (expected %08x) SP=%08x (expected %08x)\n",
used,
m68k_get_reg(NULL, M68K_REG_PC), new_sp+4,
m68k_get_reg(NULL, M68K_REG_SP), new_sp);
result = 1; // some kind of failure (but you're probably doomed)
}
for(int i=0; i<M68K_REG_LAST; i++)
m68k_set_reg(i, regs[i]);
return result;
}
void disc_tick() {
if (!accrun_flag) return;
for (int i = 0; i < DISC_NUM_DRIVES; i++) {
sony_drinfo_t *info = &drives[i];
if (drives[i].to_be_mounted) {
int inPlace = ReadMacInt8(info->status + dsDiskInPlace); // Inserted removable disk
if (inPlace || PostEvent(7, drives[i].num) == 0) {
drives[i].to_be_mounted = 0;
WriteMacInt8(info->status + dsDiskInPlace, 1); // Inserted removable disk
}
}
}
}

View file

@ -46,6 +46,7 @@
#include "scc.h"
#include "rom.h"
#include "disc.h"
#include "ringbuf.h"
#ifdef PICO
#include "pico.h"
@ -81,6 +82,8 @@ static int disassemble = 0;
static void update_overlay_layout(void);
extern int pv_uart_read();
////////////////////////////////////////////////////////////////////////////////
static int m68k_dump_regs(char *buf, int len)
@ -209,6 +212,7 @@ static uint8_t via_rb_in(void)
*/
#define KBD_CMD_GET_MODEL 0x16
#define KBD_CMD_INQUIRY 0x10
#define KBD_CMD_INSTANT 0x14
#define KBD_MODEL 5
#define KBD_RSP_NULL 0x7b
@ -225,7 +229,9 @@ static void via_sr_tx(uint8_t data)
kbd_last_cmd_time = global_time_us;
}
static int kbd_pending_evt = -1;
uint8_t kbd_pending_evt_array[32];
ringbuf_t kbd_pending_evt = {kbd_pending_evt_array, sizeof(kbd_pending_evt_array), 0, 0};
/* Emulate the keyboard: receive commands (such as an inquiry, polling
* for keypresses) and respond using via_sr_rx().
*/
@ -236,12 +242,14 @@ static void kbd_rx(uint8_t data)
case KBD_CMD_GET_MODEL:
via_sr_rx(0x01 | (KBD_MODEL << 1));
break;
case KBD_CMD_INSTANT:
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;
{
int evt = KBD_RSP_NULL;
if (ringbuf_avail(&kbd_pending_evt)) {
evt = ringbuf_get(&kbd_pending_evt);
}
via_sr_rx(evt);
}
break;
@ -267,12 +275,18 @@ static void kbd_check_work(void)
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 */
int result = 0;
if (scancode & 0x80) {
result = ringbuf_put(&kbd_pending_evt, 0x79);
scancode &= ~0x80;
}
if (result == 0) {
result = ringbuf_put(&kbd_pending_evt, scancode | (down ? 0 : 0x80));
}
if (result < 0) {
MERR("KBD: Received event %02x with full queue. Event discarded.\n",
scancode);
}
kbd_pending_evt = scancode | (down ? 0 : 0x80);
}
// VIA IRQ output hook:
@ -388,6 +402,10 @@ unsigned int FAST_FUNC(cpu_read_word)(unsigned int address)
if (IS_ROM(address))
return ROM_RD16(address & (ROM_SIZE - 1));
if (address == PV_UART_ADDR) {
return pv_uart_read();
}
if (IS_TESTSW(address))
return 0;
@ -462,6 +480,11 @@ void FAST_FUNC(cpu_write_byte)(unsigned int address, unsigned int value)
exit_error("Disc PV hook failed (%02x)", value);
return;
}
if (address == PV_UART_ADDR) {
putchar(value);
fflush(stdout);
return;
}
printf("Ignoring write %02x to address %08x\n", value&0xff, address);
}
@ -592,10 +615,35 @@ void umac_opt_disassemble(int enable)
disassemble = enable;
}
#define MOUSE_MAX_PENDING_PIX 30
/* Provide mouse input (movement, button) data.
*
* X is positive going right; Y is positive going upwards.
*/
void umac_absmouse(int x, int y, int button)
{
if (!scc_get_mie()) return;
#define MTemp_h 0x82a
#define MTemp_v 0x828
#define CrsrNew 0x8ce
#define CrsrCouple 0x8cf
static int pending_mouse_deltax = 0;
static int pending_mouse_deltay = 0;
int oldx = RAM_RD16(MTemp_h);
int oldy = RAM_RD16(MTemp_v);
if(x != oldx) {
RAM_WR16(MTemp_h, x);
}
if (y != oldy) {
RAM_WR16(MTemp_v, y);
}
if(x != oldx || y != oldy) {
RAM_WR8(CrsrNew, RAM_RD8(CrsrCouple));
}
via_mouse_pressed = button;
}
/* Provide mouse input (movement, button) data.
*
@ -603,83 +651,22 @@ static int pending_mouse_deltay = 0;
*/
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(void)
{
/* 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 (!scc_get_mie()) return;
if(deltax) {
int16_t temp_h = RAM_RD16(MTemp_h) + deltax;
RAM_WR16(MTemp_h, temp_h);
}
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);
int16_t temp_v = RAM_RD16(MTemp_v) - deltay;
RAM_WR16(MTemp_v, temp_v);
}
MDBG("\n");
via_quadbits = qb;
old_dcd_a = dcd_a;
old_dcd_b = dcd_b;
scc_set_dcd(dcd_a, dcd_b);
if(deltax || deltay) {
RAM_WR8(CrsrNew, RAM_RD8(CrsrCouple));
}
via_mouse_pressed = button;
}
void umac_reset(void)
@ -711,7 +698,7 @@ int umac_loop(void)
// Device polling
via_tick(global_time_us);
mouse_tick();
disc_tick();
kbd_check_work();
return sim_done;

98
src/ringbuf.c Normal file
View file

@ -0,0 +1,98 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Jim Mussared
*
* 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 "ringbuf.h"
int ringbuf_get16(ringbuf_t *r) {
int v = ringbuf_peek16(r);
if (v == -1) {
return v;
}
r->iget += 2;
if (r->iget >= r->size) {
r->iget -= r->size;
}
return v;
}
int ringbuf_peek16(ringbuf_t *r) {
if (r->iget == r->iput) {
return -1;
}
uint32_t iget_a = r->iget + 1;
if (iget_a == r->size) {
iget_a = 0;
}
if (iget_a == r->iput) {
return -1;
}
return (r->buf[r->iget] << 8) | (r->buf[iget_a]);
}
int ringbuf_put16(ringbuf_t *r, uint16_t v) {
uint32_t iput_a = r->iput + 1;
if (iput_a == r->size) {
iput_a = 0;
}
if (iput_a == r->iget) {
return -1;
}
uint32_t iput_b = iput_a + 1;
if (iput_b == r->size) {
iput_b = 0;
}
if (iput_b == r->iget) {
return -1;
}
r->buf[r->iput] = (v >> 8) & 0xff;
r->buf[iput_a] = v & 0xff;
r->iput = iput_b;
return 0;
}
// Returns:
// 0: Success
// -1: Not enough data available to complete read (try again later)
// -2: Requested read is larger than buffer - will never succeed
int ringbuf_get_bytes(ringbuf_t *r, uint8_t *data, size_t data_len) {
if (ringbuf_avail(r) < data_len) {
return (r->size <= data_len) ? -2 : -1;
}
ringbuf_memcpy_get_internal(r, data, data_len);
return 0;
}
// Returns:
// 0: Success
// -1: Not enough free space available to complete write (try again later)
// -2: Requested write is larger than buffer - will never succeed
int ringbuf_put_bytes(ringbuf_t *r, const uint8_t *data, size_t data_len) {
if (ringbuf_free(r) < data_len) {
return (r->size <= data_len) ? -2 : -1;
}
ringbuf_memcpy_put_internal(r, data, data_len);
return 0;
}

131
src/ringbuf.h Normal file
View file

@ -0,0 +1,131 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Paul Sokolovsky
*
* 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 MICROPY_INCLUDED_PY_RINGBUF_H
#define MICROPY_INCLUDED_PY_RINGBUF_H
#include <stddef.h>
#include <stdint.h>
#include <string.h>
typedef struct _ringbuf_t {
uint8_t *buf;
uint16_t size;
uint16_t iget;
uint16_t iput;
} ringbuf_t;
// Static initialization:
// byte buf_array[N];
// ringbuf_t buf = {buf_array, sizeof(buf_array)};
// Dynamic initialization. This needs to become findable as a root pointer!
#define ringbuf_alloc(r, sz) \
{ \
(r)->buf = m_new(uint8_t, sz); \
(r)->size = sz; \
(r)->iget = (r)->iput = 0; \
}
static inline int ringbuf_get(ringbuf_t *r) {
if (r->iget == r->iput) {
return -1;
}
uint8_t v = r->buf[r->iget++];
if (r->iget >= r->size) {
r->iget = 0;
}
return v;
}
static inline int ringbuf_peek(ringbuf_t *r) {
if (r->iget == r->iput) {
return -1;
}
return r->buf[r->iget];
}
static inline int ringbuf_put(ringbuf_t *r, uint8_t v) {
uint32_t iput_new = r->iput + 1;
if (iput_new >= r->size) {
iput_new = 0;
}
if (iput_new == r->iget) {
return -1;
}
r->buf[r->iput] = v;
r->iput = iput_new;
return 0;
}
static inline size_t ringbuf_free(ringbuf_t *r) {
return (r->size + r->iget - r->iput - 1) % r->size;
}
static inline size_t ringbuf_avail(ringbuf_t *r) {
return (r->size + r->iput - r->iget) % r->size;
}
static inline void ringbuf_memcpy_get_internal(ringbuf_t *r, uint8_t *data, size_t data_len) {
// No bounds / space checking is performed here so ensure available size is checked before running this
// otherwise data loss or buffer overflow can occur.
uint32_t iget = r->iget;
uint32_t iget_a = (iget + data_len) % r->size;
uint8_t *datap = data;
if (iget_a < iget) {
// Copy part of the data from the space left at the end of the buffer
memcpy(datap, r->buf + iget, r->size - iget);
datap += (r->size - iget);
iget = 0;
}
memcpy(datap, r->buf + iget, iget_a - iget);
r->iget = iget_a;
}
static inline void ringbuf_memcpy_put_internal(ringbuf_t *r, const uint8_t *data, size_t data_len) {
// No bounds / space checking is performed here so ensure free size is checked before running this
// otherwise data loss or buffer overflow can occur.
uint32_t iput = r->iput;
uint32_t iput_a = (iput + data_len) % r->size;
const uint8_t *datap = data;
if (iput_a < iput) {
// Copy part of the data to the end of the buffer
memcpy(r->buf + iput, datap, r->size - iput);
datap += (r->size - iput);
iput = 0;
}
memcpy(r->buf + iput, datap, iput_a - iput);
r->iput = iput_a;
}
// Note: big-endian. No-op if not enough room available for both bytes.
int ringbuf_get16(ringbuf_t *r);
int ringbuf_peek16(ringbuf_t *r);
int ringbuf_put16(ringbuf_t *r, uint16_t v);
int ringbuf_get_bytes(ringbuf_t *r, uint8_t *data, size_t data_len);
int ringbuf_put_bytes(ringbuf_t *r, const uint8_t *data, size_t data_len);
#endif // MICROPY_INCLUDED_PY_RINGBUF_H

View file

@ -126,6 +126,8 @@ static void scc_wr3(int AnB, uint8_t data)
}
}
int scc_get_mie() { return scc_mie; }
// WR9: Master Interrupt control and reset commands
static void scc_wr9(uint8_t data)
{

View file

@ -28,6 +28,8 @@
*/
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/mman.h>
@ -49,7 +51,7 @@ static void print_help(char *n)
printf("Syntax: %s <options>\n"
"\t-r <rom path>\t\tDefault 'rom.bin'\n"
"\t-W <rom dump path>\tDump ROM after patching\n"
"\t-d <disc path>\n"
"\t-d <disc path> (may be repeated)\n"
"\t-w\t\t\tEnable persistent disc writes (default R/O)\n"
"\t-i\t\t\tDisassembled instruction trace\n", n);
}
@ -78,6 +80,159 @@ static void copy_fb(uint32_t *fb_out, uint8_t *fb_in)
}
}
typedef struct {
disc_descr_t *desc;
int opt_write;
int slot;
int idx, num_names;
char **names;
} unix_disc_descr_t;
static int open_disc_single(unix_disc_descr_t *desc, int slot, int opt_write, const char *disc_filename) {
int ofd;
void *disc_base;
struct stat sb;
printf("Opening disc '%s'\n", disc_filename);
ofd = open(disc_filename, opt_write ? O_RDWR : O_RDONLY);
if (ofd < 0) {
perror("Disc");
return 1;
}
fstat(ofd, &sb);
size_t disc_size = sb.st_size;
/* Discs are always _writable_ from the perspective of
* the Mac, but by default data is a MAP_PRIVATE copy
* and is not synchronised to the backing file. If
* opt_write, we use MAP_SHARED and open the file RW,
* so writes persist to the disc image.
*/
disc_base = mmap(0, disc_size, PROT_READ | PROT_WRITE,
opt_write ? MAP_SHARED : MAP_PRIVATE,
ofd, 0);
close(ofd);
if (disc_base == MAP_FAILED) {
printf("Can't mmap disc!\n");
return 1;
}
printf("Disc mapped at %p, slot %d, size %ld\n", (void *)disc_base, slot, disc_size);
desc->desc->base = disc_base;
desc->desc->read_only = 0; /* See above */
desc->desc->size = disc_size;
desc->slot = slot;
return 0;
}
extern int asprintf(char **restrict strp, const char *restrict fmt, ...);
static disc_descr_t *disc_open_next(void *desc_in) {
unix_disc_descr_t *desc = desc_in;
if (desc->num_names == 0) {
return 0;
}
munmap(desc->desc->base, desc->desc->size);
desc->desc->base = 0;
desc->idx = (desc->idx + 1) % desc->num_names;
int r = open_disc_single(desc, desc->slot, desc->opt_write, desc->names[desc->idx]);
if (r == 0) return desc->desc;
return 0;
}
static int open_disc_collection(unix_disc_descr_t *desc, int slot, int opt_write, const char *disc_filename) {
struct stat sb;
printf("Opening disc collection '%s'\n", disc_filename);
DIR *d = opendir(disc_filename);
char **names = malloc(0);
struct dirent *ent;
int cnt=0;
for(; errno=0, ent = readdir(d);) {
names = realloc(names, sizeof(const char *) * (cnt + 1));
asprintf(&names[cnt], "%s/%s", disc_filename, ent->d_name);
stat(names[cnt], &sb);
if((sb.st_mode & S_IFMT) == S_IFDIR) {
continue;
}
printf("Found disc %s\n", names[cnt]);
cnt++;
}
if (errno != 0) {
perror("readdir");
}
closedir(d);
if(cnt == 0) {
printf("Didn't find any discs\n");
return 1;
}
desc->names = names;
desc->num_names = cnt;
desc->desc->op_next = disc_open_next;
desc->desc->op_ctx = desc;
return open_disc_single(desc, slot, opt_write, names[0]);
}
static int open_disc(unix_disc_descr_t *desc, int slot, int opt_write, const char *disc_filename) {
struct stat sb;
int r = stat(disc_filename, &sb);
if (r < 0) {
perror("Disc");
return 1;
}
if((sb.st_mode & S_IFMT) == S_IFDIR) {
if(slot == 0) {
printf("Initial disc argument must be a file, not a directory\n");
return 1;
}
return open_disc_collection(desc, slot, opt_write, disc_filename);
} else {
desc->desc->op_next = 0;
return open_disc_single(desc, slot, opt_write, disc_filename);
}
}
// from micropython
#include <termios.h>
#include <unistd.h>
#include <poll.h>
static struct termios orig_termios;
static void stdio_mode_raw(void) {
// save and set terminal settings
tcgetattr(0, &orig_termios);
static struct termios termios;
termios = orig_termios;
termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
termios.c_cflag = (termios.c_cflag & ~(CSIZE | PARENB)) | CS8;
termios.c_lflag = 0;
termios.c_cc[VMIN] = 1;
termios.c_cc[VTIME] = 0;
tcsetattr(0, TCSAFLUSH, &termios);
}
static void stdio_mode_orig(void) {
// restore terminal settings
tcsetattr(0, TCSAFLUSH, &orig_termios);
}
int pv_uart_read() {
struct pollfd p = { .fd = 0, .events = POLLIN };
int r = poll(&p, 1, 0);
if (r > 0) {
unsigned char result;
r = read(0, &result, 1);
if (r == 1) return result;
}
return EOF;
}
/**********************************************************************/
/* The emulator core expects to be given ROM and RAM pointers,
@ -91,11 +246,9 @@ 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;
@ -104,6 +257,15 @@ int main(int argc, char *argv[])
////////////////////////////////////////////////////////////////////////
// Args
disc_descr_t discs[DISC_NUM_DRIVES] = {0};
unix_disc_descr_t udiscs[DISC_NUM_DRIVES] = {0};
for(int i=0; i<DISC_NUM_DRIVES; i++) {
udiscs[i].desc = &discs[i];
}
size_t disc_num = 0;
while ((ch = getopt(argc, argv, "r:d:W:ihw")) != -1) {
switch (ch) {
case 'r':
@ -115,7 +277,19 @@ int main(int argc, char *argv[])
break;
case 'd':
disc_filename = strdup(optarg);
if (disc_num < DISC_NUM_DRIVES) {
if (open_disc(&udiscs[disc_num], disc_num, opt_write, optarg) != 0) {
return 1;
}
disc_num ++;
} else {
printf("Too many discs\n");
return 1;
}
break;
case 'R':
opt_write = 0;
break;
case 'w':
@ -192,40 +366,6 @@ int main(int argc, char *argv[])
}
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: >1 disc
ofd = open(disc_filename, opt_write ? O_RDWR : O_RDONLY);
if (ofd < 0) {
perror("Disc");
return 1;
}
fstat(ofd, &sb);
size_t disc_size = sb.st_size;
/* Discs are always _writable_ from the perspective of
* the Mac, but by default data is a MAP_PRIVATE copy
* and is not synchronised to the backing file. If
* opt_write, we use MAP_SHARED and open the file RW,
* so writes persist to the disc image.
*/
disc_base = mmap(0, disc_size, PROT_READ | PROT_WRITE,
opt_write ? MAP_SHARED : 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 = 0; /* See above */
discs[0].size = disc_size;
}
////////////////////////////////////////////////////////////////////////
// SDL/UI init
@ -243,10 +383,15 @@ int main(int argc, char *argv[])
perror("SDL window");
return 1;
}
SDL_SetWindowGrab(window, SDL_TRUE);
SDL_SetRelativeMouseMode(SDL_TRUE);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
static const int absmouse = 1;
if (!absmouse) {
SDL_SetWindowGrab(window, SDL_TRUE);
SDL_SetRelativeMouseMode(SDL_TRUE);
} else {
SDL_ShowCursor(SDL_DISABLE);
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
@ -269,10 +414,12 @@ int main(int argc, char *argv[])
umac_init(ram_base, rom_base, discs);
umac_opt_disassemble(opt_disassemble);
stdio_mode_raw();
////////////////////////////////////////////////////////////////////////
// Main loop
int done = 0;
int mouse_button = 0;
uint64_t last_vsync = 0;
@ -282,7 +429,8 @@ int main(int argc, char *argv[])
SDL_Event event;
int mousex = 0;
int mousey = 0;
int send_mouse = 0;
static int absmousex, absmousey;
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
@ -293,27 +441,37 @@ int main(int argc, char *argv[])
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:
send_mouse = 1;
absmousex = event.motion.x / DISP_SCALE;
absmousey = event.motion.y / DISP_SCALE;
mousex = event.motion.xrel;
mousey = -event.motion.yrel;
break;
case SDL_MOUSEBUTTONDOWN:
send_mouse = 1;
mouse_button = 1;
break;
case SDL_MOUSEBUTTONUP:
send_mouse = 1;
mouse_button = 0;
break;
}
}
umac_mouse(mousex, mousey, mouse_button);
if (send_mouse) {
if(absmouse) {
umac_absmouse(absmousex, absmousey, mouse_button);
} else {
umac_mouse(mousex, mousey, mouse_button);
}
}
done |= umac_loop();
@ -339,5 +497,6 @@ int main(int argc, char *argv[])
}
} while (!done);
stdio_mode_orig();
return 0;
}