diff options
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | abc80.c | 20 | ||||
-rw-r--r-- | abcio.c | 159 | ||||
-rw-r--r-- | abcio.h | 27 | ||||
-rw-r--r-- | cas.c | 55 | ||||
-rw-r--r-- | clock.c | 51 | ||||
-rw-r--r-- | clock.h | 2 | ||||
-rw-r--r-- | compiler.h | 15 | ||||
-rw-r--r-- | rtc.c | 2 | ||||
-rw-r--r-- | screen.h | 2 | ||||
-rw-r--r-- | sdlscrn.c | 194 | ||||
-rw-r--r-- | simprint.c | 7 | ||||
-rw-r--r-- | z80.c | 92 | ||||
-rw-r--r-- | z80.h | 33 | ||||
-rw-r--r-- | z80irq.c | 132 | ||||
-rw-r--r-- | z80irq.h | 31 |
16 files changed, 549 insertions, 275 deletions
diff --git a/Makefile.in b/Makefile.in index 355fafb..af1feee 100644 --- a/Makefile.in +++ b/Makefile.in @@ -29,7 +29,7 @@ X = @EXEEXT@ .SUFFIXES: .c .h .$(O) .bin $(X) .i .s .asm GENC = $(patsubst %.rom,%.c,$(wildcard roms/*.rom)) -SRCS = abc80.c clock.c sdlscrn.c screenshot.c z80.c abcmem.c abcio.c \ +SRCS = abc80.c clock.c sdlscrn.c screenshot.c z80.c z80irq.c abcmem.c abcio.c \ abcfont.c disk.c rtc.c simprint.c print.c fileop.c z80dis.c \ nstime.c hostfile.c console.c cas.c abcfile.c filelist.c \ clib/asprintf.c $(GENC) @@ -12,6 +12,9 @@ #include "clock.h" #include <SDL_main.h> +#include <SDL_thread.h> + +static int z80_thread(void *); double ns_per_tstate = 1000.0/3.0; /* Nanoseconds per tstate (clock cycle) */ double tstate_per_ns = 3.0/1000.0; /* Inverse of the above = freq in GHz */ @@ -321,6 +324,7 @@ int main(int argc, char **argv) bool color = true; bool console = false; bool faketype_set = false; + SDL_Thread *cpu_thread; attach_console(); @@ -525,11 +529,23 @@ int main(int argc, char **argv) /* * Off we go... */ + cpu_thread = SDL_CreateThread(z80_thread, NULL); + event_loop(); /* Handling external events and screen */ + z80_quit = true; + SDL_WaitThread(cpu_thread, NULL); + + screen_reset(); + exit(0); +} + +int z80_thread(void *data) +{ + (void)data; + z80_reset(); timer_init(); z80_run(true, false); - screen_reset(); - exit(0); + return 0; } @@ -1,25 +1,36 @@ #include "compiler.h" #include "z80.h" +#include "z80irq.h" #include "screen.h" #include "abcio.h" #include "clock.h" #include "trace.h" -#define READ_MODE 0 -#define WRITE_MODE 1 - /* Select code for ABC/4680 bus */ static int8_t abcbus_select = -1; /* Keyboard IRQ vector */ -static uint8_t keyb_irq = 0xff; /* = no IRQ vector set */ -static uint8_t keyb_data; -static bool keyb_new, keyb_down; +static volatile unsigned int keyb_data; +static uint8_t keyb_fakedata; + +/* These constants are designed to make dart_keyb_in() as simple as possible */ +#define KEYB_NEW 0x100 +#define KEYB_DOWN 0x800 /* Fake minimal-touch input */ bool faketype; +static int keyb_intack_fake(unsigned int prio, struct z80_irq *irq); +static struct z80_irq *keyb_irq; + +static struct z80_irq keyb_irq_80 = + { NULL, NULL, NULL, -1, IRQ80_PIOA }; +static struct z80_irq keyb_irq_fake = + { keyb_intack_fake, NULL, NULL, -1, IRQ80_PIOA }; +static struct z80_irq keyb_irq_800 = + { NULL, NULL, NULL, -1, IRQ800_DARTA }; + static inline uint8_t abc800_mangle_port(uint8_t port) { if ((port & 0xe0) == 0x00) @@ -90,7 +101,7 @@ static void abc80_out(uint8_t port, uint8_t value) case (57 & 0x17): /* Keyboard control port */ if (!(value & 1)) { - keyb_irq = value; + keyb_irq->vector = value; } break; @@ -135,7 +146,7 @@ static void dart_keyb_out(uint8_t port, uint8_t value) vsync = false; break; case 3: - keyb_irq = -1; + keyb_irq->vector = -1; memset(dart_keyb_ctl, 0, sizeof dart_keyb_ctl); return; case 4: @@ -153,49 +164,69 @@ static void dart_keyb_out(uint8_t port, uint8_t value) } if ((dart_keyb_ctl[1] & 0x18) == 0) { - keyb_irq = 0; + keyb_irq->vector = 0; } else { if (dart_keyb_ctl[1] & 0x04) { /* Status affects vector */ - keyb_irq = (dart_keyb_ctl[2] & ~0x0f) | 0x04; + keyb_irq->vector = (dart_keyb_ctl[2] & ~0x0f) | 0x04; } else { - keyb_irq = (dart_keyb_ctl[1] & ~0x01); + keyb_irq->vector = (dart_keyb_ctl[1] & ~0x01); } } } -static uint8_t dart_keyb_in(uint8_t port) +/* Get the keyboard data, clearing the KEYB_NEW flag */ +static unsigned int get_key(void) { - uint8_t v, reg; + unsigned int rv, kbd; - if ((port & 1) == 0) { - /* Data register */ - keyb_new = false; - v = keyb_data; - return keyb_data; - } + rv = kbd = keyb_data; + cmpxchg(&keyb_data, &kbd, kbd & ~KEYB_NEW); - /* Control register */ + return rv; +} - reg = dart_keyb_ctl[0] & 7; - dart_keyb_ctl[0] &= ~7; /* Restore register 0 */ +static int keyb_intack_fake(unsigned int prio, struct z80_irq *irq) +{ + unsigned int data = get_key(); - switch (reg) { - case 0: - v = ((keyb_new) << 0) + - (1 << 2) + /* Transmit buffer empty */ - (keyb_down << 3) + /* DCD -> key down */ - (dart_keyb_vsync << 4) + /* RI -> vsync */ - (1 << 5); /* CTS -> 60 Hz */ - break; - case 1: - v = (1 << 0); /* All sent */ - break; - case 2: - v = dart_keyb_ctl[2]; + (void)prio; + + keyb_fakedata = (data & 0x7f) | ((data & KEYB_NEW) ? 0x80 : 0x00); + + return irq->vector; +} + +static uint8_t dart_keyb_in(uint8_t port) +{ + uint8_t v, reg; + + switch (port & 1) { + case 0: /* Data register */ + v = get_key(); break; - default: - v = 0; + + case 1: /* Control register */ + reg = dart_keyb_ctl[0] & 7; + dart_keyb_ctl[0] &= ~7; /* Restore register 0 */ + + switch (reg) { + case 0: + v = (keyb_data >> 8) + + (1 << 2) + /* Transmit buffer empty */ + (dart_keyb_vsync << 4) + /* RI -> vsync */ + (1 << 5); /* CTS -> 60 Hz */ + break; + case 1: + v = (1 << 0); /* All sent */ + break; + case 2: + v = dart_keyb_ctl[2]; + break; + default: + v = 0; + break; + } break; } @@ -324,9 +355,13 @@ static uint8_t abc80_in(uint8_t port) break; case (56 & 0x17): - v = keyb_data; - if (faketype) - keyb_data &= ~0x80; /* Hack to avoid insanely fast repeat */ + if (faketype) { + v = keyb_fakedata; + keyb_fakedata &= ~0x80; + } else { + unsigned int kbd = keyb_data; + v = (kbd & 0x7f) | ((kbd & KEYB_DOWN) ? 0x80 : 0); + } break; case (58 & 0x17): @@ -410,39 +445,26 @@ int z80_in(int port) return v; } +/* This is called in the event handler thread context! */ void keyboard_down(int sym) { - keyb_down = true; - - switch (model) { - case MODEL_ABC80: - if (sym <= 127) { - keyb_data = sym | 0x80; - z80_interrupt(keyb_irq); - } - break; - - case MODEL_ABC802: - keyb_data = sym; - keyb_new = true; - z80_interrupt(keyb_irq); - break; + if (model == MODEL_ABC80) { + if (sym & ~127) + return; } + + keyb_data = sym | KEYB_NEW | KEYB_DOWN; + z80_interrupt(keyb_irq->prio); } -void keyboard_up(void) +unsigned int keyboard_up(void) { - keyb_down = false; + unsigned int rv, kbd; - switch (model) { - case MODEL_ABC80: - keyb_data &= ~0x80; - break; + rv = kbd = keyb_data; + cmpxchg(&keyb_data, &kbd, kbd & ~KEYB_DOWN); - case MODEL_ABC802: - /* Do nothing? */ - break; - } + return rv; } void io_init(void) @@ -451,10 +473,17 @@ void io_init(void) case MODEL_ABC80: do_out = abc80_out; do_in = abc80_in; + keyb_data = 0; + z80_register_irq(keyb_irq = faketype ? &keyb_irq_fake : &keyb_irq_80); + abc80_cas_init(); break; case MODEL_ABC802: do_out = abc802_out; do_in = abc802_in; keyb_data = 0xff; + z80_register_irq(keyb_irq = &keyb_irq_800); + abc800_cas_init(); + abc800_ctc_init(); + break; } } @@ -33,6 +33,7 @@ extern void abc806_rtc_out(uint8_t port, uint8_t value); extern void abc800_ctc_out(uint8_t, uint8_t); extern uint8_t abc800_ctc_in(uint8_t); +extern void abc800_ctc_init(void); extern void printer_reset(void); extern void printer_out(int sel, int port, int value); @@ -41,7 +42,7 @@ extern void dart_pr_out(uint8_t port, uint8_t v); extern uint8_t dart_pr_in(uint8_t port); extern void keyboard_down(int sym); -extern void keyboard_up(void); +extern unsigned int keyboard_up(void); extern bool faketype; extern void abc802_vsync(void); @@ -50,9 +51,33 @@ extern void dump_memory(bool ramonly); extern void abc80_piob_out(uint8_t port, uint8_t v); extern uint8_t abc80_piob_in(void); +extern void abc80_cas_init(void); extern void abc800_sio_cas_out(uint8_t port, uint8_t v); extern uint8_t abc800_sio_cas_in(uint8_t port); +extern void abc800_cas_init(void); + + +/* + * Z80 has fixed priorities based on the device daisy chain, but the vectors + * can be different, so we identify interrupts by their priority level. + * + * XXX: This ordering is almost certainly completely wrong. + */ +enum abc80_irq { + IRQ80_PIOA, + IRQ80_PIOB +}; +enum abc800_irq { + IRQ800_DARTA, + IRQ800_DARTB, + IRQ800_SIOA, + IRQ800_SIOB, + IRQ800_CTC0, + IRQ800_CTC1, + IRQ800_CTC2, + IRQ800_CTC3 +}; /* Directory and filenames */ extern const char *fileop_path, *disk_path, *screen_path, *memdump_path; @@ -5,6 +5,7 @@ #include "hostfile.h" #include "abcio.h" #include "z80.h" +#include "z80irq.h" #include "abcfile.h" #include "trace.h" @@ -169,6 +170,7 @@ static bool cas_edge(void) /* * ABC80 PIO interfacing */ +static inline int pio_eoi(unsigned int, struct z80_irq *); enum pioctl_state { pcs_init, @@ -179,19 +181,23 @@ enum pioctl_state { struct pio { uint8_t out, in, mask; uint8_t mode; - uint8_t irq, irqmask, irqctl, irqprev; + uint8_t irqmask, irqctl, irqprev; enum pioctl_state ctlstate; + struct z80_irq irq; +}; +static struct pio portb = { + .in = 0xff, + .irq.eoi = pio_eoi, + .irq.pvt = &portb, + .irq.vector = -1, + .irq.prio = IRQ80_PIOB }; - -static struct pio portb = { .in = 0xff }; static inline uint8_t pio_readval(const struct pio *pio) { return (pio->out & pio->mask) | (pio->in & ~pio->mask); } -static void pio_eoi(uint8_t vector, void *arg); - static void pio_check_interrupt(struct pio *pio) { uint8_t val = pio_readval(pio); @@ -207,15 +213,16 @@ static void pio_check_interrupt(struct pio *pio) (pio->irqctl & 0x40) ? (masked == pio->irqmask) : (masked != 0); if (trigger) - z80_interrupt_eoi(pio->irq, pio_eoi, (void *)pio); + z80_interrupt(pio->irq.prio); else - z80_clear_interrupt(pio->irq); + z80_clear_interrupt(pio->irq.prio); } -static void pio_eoi(uint8_t vector, void *arg) +static int pio_eoi(unsigned int prio, struct z80_irq *irq) { - (void)vector; - pio_check_interrupt((struct pio *)arg); + (void)prio; + pio_check_interrupt((struct pio *)(irq->pvt)); + return 0; } static void pio_control(struct pio *pio, uint8_t v) @@ -248,7 +255,7 @@ static void pio_control(struct pio *pio, uint8_t v) break; default: if ((v & 1) == 0) - pio->irq = v; + pio->irq.vector = v; break; } break; @@ -307,6 +314,11 @@ uint8_t abc80_piob_in(void) return pio_readval(&portb); } +void abc80_cas_init(void) +{ + z80_register_irq(&portb.irq); +} + /* * ABC800 SIO/2 cassette interface * @@ -317,9 +329,14 @@ uint8_t abc80_piob_in(void) * - At end of block either hardware or software go back to need sync */ +static int sio_cas_eoi(unsigned int prio, struct z80_irq *irq); + static uint8_t sio_cas_ctl[8]; static bool cas_first_rx_armed = true; +static struct z80_irq sio_cas_irq = +{ NULL, sio_cas_eoi, NULL, -1, IRQ800_SIOB }; + static inline bool cas_have_sync(void) { return !cas_idle() && (sio_cas_ctl[3] && 1); @@ -337,12 +354,13 @@ static inline bool cas_rx_interrupt(bool huntok) static void cas_poll_interrupt(void); -static void sio_cas_eoi(uint8_t vector, void *dummy) +static int sio_cas_eoi(unsigned int prio, struct z80_irq *irq) { - (void)vector; - (void)dummy; + (void)prio; + (void)irq; cas_poll_interrupt(); + return 0; } static void cas_poll_interrupt(void) @@ -353,8 +371,8 @@ static void cas_poll_interrupt(void) /* Actually signal a receive data interrupt */ sio_cas_ctl[3] &= ~0x10; /* Not hunting anymore */ cas_first_rx_armed = false; - z80_interrupt_eoi((sio_cas_ctl[2] & ~0x0f) | 0x04, - sio_cas_eoi, NULL); + sio_cas_irq.vector = (sio_cas_ctl[2] & ~0x0f) | 0x04; + z80_interrupt(sio_cas_irq.prio); } void abc800_sio_cas_out(uint8_t port, uint8_t v) @@ -480,3 +498,8 @@ uint8_t abc800_sio_cas_in(uint8_t port) cas_poll_interrupt(); return v; } + +void abc800_cas_init(void) +{ + z80_register_irq(&sio_cas_irq); +} @@ -1,4 +1,6 @@ +#include "compiler.h" #include "z80.h" +#include "z80irq.h" #include "screen.h" #include "abcio.h" #include "clock.h" @@ -97,8 +99,8 @@ static void consider_napping(uint64_t now, uint64_t next) behind = now - when; ahead = when - next; - /* Sanity range check: 250 ms behind or 100 ms ahead of schedule */ - if (unlikely(behind >= MS(250) || ahead >= MS(100))) + /* Sanity range check: 100 ms behind or 100 ms ahead of schedule */ + if (unlikely(behind >= MS(200) || ahead >= MS(100))) goto weird; /* If we are ahead of the next event, hold off and wait for it */ @@ -120,7 +122,9 @@ static void consider_napping(uint64_t now, uint64_t next) #define CHECK_FREQUENCY 64 /* Poll for timers - these the only external event we look for */ -void z80_poll_external(void) +volatile bool z80_quit; + +bool z80_poll_external(void) { uint64_t now; static uint64_t next = 0; @@ -128,8 +132,11 @@ void z80_poll_external(void) bool sleepy = limit_speed; static uint64_t next_check_tstate; + if (z80_quit) + return true; /* Terminate CPU loop */ + if (likely(TSTATE < next_check_tstate)) - return; + return false; next_check_tstate = TSTATE + poll_tstate_period; @@ -162,6 +169,8 @@ void z80_poll_external(void) if (sleepy) consider_napping(now, next); + + return false; } /* @@ -173,18 +182,22 @@ static void abc80_clock_tick(void) z80_nmi(); } -static uint8_t ctc_ctl[4], ctc_div[4], ctc_vector; - /* * ABC800: Clock interrupt through the CTC */ - -static uint8_t ctc_ctl[4], ctc_div[4], ctc_vector; +static uint8_t ctc_ctl[4], ctc_div[4]; +static struct z80_irq ctc_irq[4] = +{ + { NULL, NULL, NULL, -1, IRQ800_CTC0 }, + { NULL, NULL, NULL, -1, IRQ800_CTC1 }, + { NULL, NULL, NULL, -1, IRQ800_CTC2 }, + { NULL, NULL, NULL, -1, IRQ800_CTC3 } +}; static void abc800_clock_tick(void) { - if ((ctc_ctl[3] & 0xc0) == 0x80) - z80_interrupt(ctc_vector | (3 << 1)); /* 3 = channel */ + if ((ctc_ctl[3] & 0xc0) == 0x80) + z80_interrupt(IRQ800_CTC3); } /* @@ -192,10 +205,13 @@ static void abc800_clock_tick(void) */ void abc800_ctc_out(uint8_t port, uint8_t v) { - if ((v & 1) == 0) { - ctc_vector = v; - return; - } + if ((v & 1) == 0) { + int i; + v &= ~7; + for (i = 0; i <= 3; i++) + ctc_irq[i].vector = v | (i << 1); + return; + } port &= 3; /* Get channel */ @@ -235,3 +251,10 @@ uint8_t abc800_ctc_in(uint8_t port) return v; } + +void abc800_ctc_init(void) +{ + int i; + for (i = 0; i < 4; i++) + z80_register_irq(&ctc_irq[i]); +} @@ -9,4 +9,6 @@ extern double ns_per_tstate; extern double tstate_per_ns; extern bool limit_speed; +extern volatile bool z80_quit; + #endif /* CLOCK_H */ @@ -186,4 +186,19 @@ typedef int mode_t; # define is_constant(x) false #endif +/* Simple atomic operations */ +#ifdef __GNUC__ +# define atomic_load(p) __atomic_load_n((p), __ATOMIC_ACQUIRE) +# define atomic_store(p,v) __atomic_store_n((p), (v), __ATOMIC_RELEASE) +# define cmpxchg(p, e, d) \ + likely(__atomic_compare_exchange_n((p), (e), (d), false, \ + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) +# define xchg(p, v) \ + __atomic_exchange_n((p), (v), __ATOMIC_ACQ_REL) +#define barrier() __atomic_thread_fence(__ATOMIC_ACQ_REL) + +#else +/* ? */ +#endif + #endif /* COMPILER_H */ @@ -11,7 +11,7 @@ static int ptr; * ASCII string - will be converted to BCD * __ is a placeholder for the command phase */ -static uint8_t e05time[9*2+1]; /* __ HH MM dd mm YY ?? SS ?? */ +static uint8_t e05time[17*2]; /* __ HH MM dd mm YY ?? SS ?? */ static unsigned int e05bit; static uint8_t e05cmd; @@ -11,7 +11,7 @@ extern void screen_write(int, int); extern void screen_flush(void); extern void setmode40(bool); -extern void get_event(void); +extern void event_loop(void); extern void key_check(void); extern volatile int event_pending; @@ -33,7 +33,7 @@ extern const unsigned char abc_font[256][FONT_YSIZE]; -static void check_event(void); +static void trigger_refresh(bool); #define NCOLORS 8 @@ -48,10 +48,26 @@ static struct argb { uint8_t a, r, g, b; } rgbcolors[NCOLORS] = { {0x00,0xff,0xff,0xff}, /* white */ }; +/* Mutex for interaction with the CPU thread */ +static SDL_mutex *screen_mutex; + #define VRAM_SIZE 2048 #define VRAM_MASK (VRAM_SIZE-1) + +/* Video RAM accessed by the CPU */ unsigned char video_ram[VRAM_SIZE]; +/* + * Buffer for passing video RAM between the CPU and screen threads, + * protected by screen_mutex + */ +static unsigned char vram_queue[VRAM_SIZE]; /* CPU->screen video RAM copy */ + +/* + * Copy of video RAM used to draw on screen (or screenshot) + */ +static unsigned char vram_shadow[VRAM_SIZE]; /* Screen accessed video RAM */ + union crtc { uint8_t regs[18]; struct { @@ -73,7 +89,7 @@ union crtc { uint8_t curl; /* Low half of cursor address */ } r; }; -static union crtc crtc; +static union crtc crtc, crtc_shadow; static uint8_t crtc_addr; static uint16_t startaddr, curaddr; struct xy { @@ -81,10 +97,6 @@ struct xy { }; static struct xy addr_to_xy_tbl[2][2048]; -struct do_event { - void (*func)(void); -}; - /* A local abstraction of a drawing surface */ struct surface { SDL_Surface *surf; /* SDL_Surface object */ @@ -96,15 +108,15 @@ struct surface { }; static struct surface rscreen; -static bool blink_on = true; -static bool mode40; +static volatile bool blink_on = true; +static volatile bool mode40, mode40_cpu; /* - * Give the x,y coordinates for a given location in video RAM + * Give the x,y coordinates for a given location in shadow video RAM */ static inline struct xy addr_to_xy(const uint8_t *p) { - uint16_t addr = p - video_ram; + uint16_t addr = p - vram_shadow; addr = (addr - startaddr) & VRAM_MASK; return addr_to_xy_tbl[mode40][addr]; } @@ -140,7 +152,7 @@ static inline unsigned int screenoffs(uint8_t y, uint8_t x) */ static inline uint8_t screendata(uint8_t y, uint8_t x) { - return video_ram[screenoffs(y,x) & VRAM_MASK]; + return vram_shadow[screenoffs(y,x) & VRAM_MASK]; } /* @@ -199,7 +211,7 @@ put_screen(struct surface *s, unsigned int tx, unsigned int ty, bool blink) } voffs = screenoffs(ty,tx); - cc = video_ram[voffs & VRAM_MASK]; + cc = vram_shadow[voffs & VRAM_MASK]; fontp = abc_font[(cc & 0x7f) + gmode]; invmask = (blink || model != MODEL_ABC80) ? 0x80 : 0; invmask = (cc & invmask) ? 7 : 0; @@ -220,9 +232,9 @@ put_screen(struct surface *s, unsigned int tx, unsigned int ty, bool blink) curmask = 0; if (unlikely(voffs == curaddr)) { - if (blink | (crtc.r.curstart & 0x40)) { - curmask = (~0U << (crtc.r.curstart & 0x1f)); - curmask &= (2U << (crtc.r.curend & 0x1f))-1; + if (blink | (crtc_shadow.r.curstart & 0x40)) { + curmask = (~0U << (crtc_shadow.r.curstart & 0x1f)); + curmask &= (2U << (crtc_shadow.r.curend & 0x1f))-1; } } @@ -288,10 +300,17 @@ static void screen_dirty(struct surface *s) static void refresh_screen(struct surface *s, bool blink) { unsigned int x, y; - unsigned int width = TS_WIDTH >> mode40; + unsigned int width; screen_dirty(s); + SDL_mutexP(screen_mutex); + memcpy(vram_shadow, vram_queue, sizeof vram_shadow); + crtc_shadow = crtc; + mode40 = mode40_cpu; + SDL_mutexV(screen_mutex); + + width = TS_WIDTH >> mode40; lock_screen(s); for (y = 0; y < TS_HEIGHT; y++) @@ -336,77 +355,18 @@ void write_screen(uint8_t *p, uint8_t v) static void do_set_mode40(bool m) { - mode40 = m; - - refresh_screen(&rscreen, blink_on); - if (model == MODEL_ABC80) - abc80_mem_mode40(m); + mode40 = m; + refresh_screen(&rscreen, blink_on); } +/* Called from the CPU thread */ void setmode40(bool m) { - if (m != mode40) - do_set_mode40(m); -} - -/* - * This routine switches the blink status, then goes around the screen - * and updates all characters which has any kind of blink. Returns the - * previous value. - */ -static bool set_blink(bool to_what) -{ - struct xy xy; - int x, y; - int width = TS_WIDTH >> mode40; - - if (likely(to_what == blink_on)) - return to_what; - - blink_on = to_what; - - lock_screen(&rscreen); - - switch (model) { - case MODEL_ABC80: - for ( y = 0 ; y < TS_HEIGHT ; y++ ) { - for ( x = 0 ; x < width ; x++ ) { - if ( screendata(y,x) & 0x80 ) - put_screen(&rscreen, x, y, blink_on); - } - } - break; - - case MODEL_ABC802: - if (!(crtc.r.curstart & 0x40)) { - xy = addr_to_xy(curaddr + video_ram); - put_screen(&rscreen, xy.x, xy.y, blink_on); + if (m != mode40_cpu) { + mode40_cpu = m; + if (model == MODEL_ABC80) + abc80_mem_mode40(m); } - break; - } - - unlock_screen(&rscreen); - - return !to_what; /* We just flipped it... */ -} - -/* Called from the timer that corresponds to the simulated vsync */ -void vsync_screen(void) -{ - const int blink_rate = 400/20; /* 400 ms/20 ms = 2.5 Hz */ - static int blink_ctr; - - check_event(); /* Poll for an SDL event */ - - if (!blink_ctr--) { - set_blink(!blink_on); - blink_ctr += blink_rate; - } - - update_screen(&rscreen); - - if (traceflags) - fflush(tracef); /* So we don't buffer indefinitely */ } /* @@ -489,6 +449,8 @@ void screen_init(bool width40, bool color) crtc.r.vscantotal = 24; crtc.r.vdisplay = 24; crtc.r.curstart = 0x1f; /* No CRTC cursor */ + crtc_shadow = crtc; + startaddr = curaddr = 0; /* Initialize reverse mapping table */ @@ -504,15 +466,18 @@ void screen_init(bool width40, bool color) } } + /* Create interlock mutex */ + screen_mutex = SDL_CreateMutex(); + + if (!init_surface(&rscreen)) + return; + /* Enable keyboard decoding */ SDL_EnableUNICODE(1); /* Enable keyboard repeat */ SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); - if (!init_surface(&rscreen)) - return; - /* Forcibly set the screen width and load the appropriate BASIC */ do_set_mode40(width40); } @@ -526,12 +491,9 @@ void screen_reset(void) } /* - * Handle events. This is called from vsync_screen(), because - * SDL_PollEvent() might be expensive on some platforms. + * Event-handling loop; main loop of the event/screen thread. */ -int keyboard_code; /* Keyboard code exported to PIO/DART */ - -static void check_event(void) +void event_loop(void) { SDL_Event event; static int keyboard_scan = -1; /* No key currently down */ @@ -541,7 +503,7 @@ static void check_event(void) KSH_ALT = 4 } kshift; - while ( SDL_PollEvent(&event) ) { + while ( SDL_WaitEvent(&event) ) { switch ( event.type ) { case SDL_KEYDOWN: kshift = \ @@ -555,7 +517,7 @@ static void check_event(void) switch (event.key.keysym.sym) { case SDLK_END: case SDLK_q: - exit(0); + return; /* Return to main() and exit simulator */ case SDLK_s: abc_screenshot(); @@ -712,15 +674,55 @@ static void check_event(void) if ( event.key.keysym.scancode == keyboard_scan ) keyboard_up(); break; + case SDL_USEREVENT: + /* Time to update the screen */ + refresh_screen(&rscreen, blink_on); + break; case SDL_QUIT: - exit(1); - break; + return; /* Return to main(), terminate */ default: break; } } } +/* + * Called from the timer that corresponds to the simulated vsync + * in the CPU thread context + */ +void vsync_screen(void) +{ + const int blink_rate = 400/20; /* 400 ms/20 ms = 2.5 Hz */ + static int blink_ctr; + bool blink; + + blink = !blink_ctr--; + if (blink) + blink_ctr = blink_rate; + + trigger_refresh(blink); + + if (traceflags) + fflush(tracef); /* So we don't buffer indefinitely */ +} + +/* Used from the CPU thread context to cause a screen redraw */ +static void trigger_refresh(bool toggle_blink) +{ + SDL_Event trigger_redraw; + + memset(&trigger_redraw, 0, sizeof trigger_redraw); + trigger_redraw.type = SDL_USEREVENT; + + SDL_mutexP(screen_mutex); + memcpy(vram_queue, video_ram, sizeof vram_queue); + if (toggle_blink) + blink_on = !blink_on; + SDL_mutexV(screen_mutex); + SDL_PushEvent(&trigger_redraw); +} + +/* Called in the CPU thread context */ void crtc_out(uint8_t port, uint8_t data) { if (!(port & 1)) { @@ -731,12 +733,14 @@ void crtc_out(uint8_t port, uint8_t data) if (crtc_addr >= sizeof crtc.regs) return; + SDL_mutexP(screen_mutex); + crtc.regs[crtc_addr] = data; startaddr = ((crtc.r.starth & 0x3f) << 8) + crtc.r.startl; curaddr = ((crtc.r.curh & 0x3f) << 8) + crtc.r.curl; - refresh_screen(&rscreen, blink_on); + SDL_mutexV(screen_mutex); } uint8_t crtc_in(uint8_t port) @@ -1,5 +1,6 @@ #include "abcprintd.h" #include "abcio.h" +#include "z80irq.h" #define BUF_SIZE 512 @@ -40,6 +41,8 @@ static bool abcprint_poll(void) return output_head != output_tail; } +static struct z80_irq dart_pr_irq; + void printer_reset(void) { static bool init = false; @@ -47,6 +50,8 @@ void printer_reset(void) if (!init) { init = true; abcprint_init(); + if (model != MODEL_ABC80) + z80_register_irq(&dart_pr_irq); } } @@ -96,6 +101,8 @@ int printer_in(int sel, int port) /* Hardware-like interface via the ABC800 PR: port */ static uint8_t dart_pr_ctl[8]; +static struct z80_irq dart_pr_irq = { NULL, NULL, NULL, -1, IRQ800_DARTB }; + void dart_pr_out(uint8_t port, uint8_t v) { uint8_t r; @@ -26,9 +26,7 @@ * please do send a report. */ #include "z80.h" -#include "screen.h" - -#include <setjmp.h> +#include "z80irq.h" /* * The state of our Z-80 registers is kept in this structure: @@ -1219,6 +1217,11 @@ static void do_im2(void) static void do_nmi(void) { + bool nminterrupt = xchg(&z80_state.nminterrupt, false); + + if (!nminterrupt) + return; + /* handle a non-maskable interrupt */ if (tracing(TRACE_IO|TRACE_CPU)) { fprintf(tracef, "[%12"PRIu64"] NMI: PC=%04x\n", TSTATE, REG_PC); @@ -1229,7 +1232,6 @@ static void do_nmi(void) z80_state.iff2 = z80_state.iff1; z80_state.iff1 = false; z80_state.nmi_in_progress = true; - z80_state.nminterrupt = false; REG_PC = 0x66; inc_r(); TSTATE += 11; @@ -1240,6 +1242,11 @@ do_int(void) { uint16_t old_pc = REG_PC; uint64_t when = TSTATE; + int i_vector; + + i_vector = z80_intack(); + if (i_vector < 0) + return; switch (z80_state.interrupt_mode) { case 0: @@ -1247,7 +1254,7 @@ do_int(void) do_di(); REG_SP -= 2; mem_write_word(REG_SP, REG_PC); - REG_PC = z80_state.i_vector & 0x38; + REG_PC = i_vector & 0x38; TSTATE += 11; break; @@ -1263,7 +1270,7 @@ do_int(void) do_di(); REG_SP -= 2; mem_write_word(REG_SP, REG_PC); - REG_PC = mem_read_word((z80_state.i << 8) | (z80_state.i_vector & ~1)); + REG_PC = mem_read_word((z80_state.i << 8) | (i_vector & ~1)); TSTATE += 19; break; @@ -1271,36 +1278,17 @@ do_int(void) break; } - z80_state.interrupt = false; z80_state.iff1 = false; - z80_state.int_in_progress = z80_state.i_vector & ~1; if (tracing(TRACE_CPU|TRACE_IO)) { fprintf(tracef, "[%12"PRIu64"] INT: " "vector 0x%02x (%3d) I=%02x PC=%04x -> %04x\n", - when, - z80_state.i_vector, z80_state.i_vector, - z80_state.i, old_pc, REG_PC); + when, i_vector, i_vector, z80_state.i, old_pc, REG_PC); } inc_r(); } - -void z80_interrupt_eoi(uint8_t vector, eoifunc do_eoi, void *eoi_arg) -{ - if (!(vector & 1) && z80_state.int_in_progress != vector) { - if (tracing(TRACE_CPU|TRACE_IO)) { - fprintf(tracef, "IRQ: interrupt pending, vector 0x%02x (%3u)\n", - vector, vector); - } - z80_state.interrupt = true; - z80_state.i_vector = vector; - z80_state.eoi.func = do_eoi; - z80_state.eoi.arg = eoi_arg; - } -} - static uint16_t get_hl_addr(wordregister *ix) { if (ix == &z80_state.hl) { @@ -2436,8 +2424,7 @@ static void do_ED_instruction(wordregister *ix) REG_PC = mem_read_word(REG_SP); REG_SP += 2; z80_state.iff1 = z80_state.iff2; - z80_state.eoi.trigger = z80_state.int_in_progress; - z80_state.int_in_progress = -1; + z80_state.signal_eoi = true; /* Send EOI before next instruction */ } break; @@ -2478,6 +2465,16 @@ static void do_ED_instruction(wordregister *ix) } } + +static inline void check_eoi(void) +{ + if (!likely(z80_state.signal_eoi)) + return; + + z80_state.signal_eoi = false; + z80_eoi(); +} + int z80_run(bool continuous, bool halted) { uint8_t instruction; @@ -2486,42 +2483,33 @@ int z80_run(bool continuous, bool halted) /* loop to do a z80 instruction */ do { - if (tracing(TRACE_CPU)) { + if (tracing(TRACE_CPU)) { diffstate(); tracemem(); fputc('\n', tracef); } - if (z80_state.eoi.trigger >= 0) { - struct eoi eoi = z80_state.eoi; - - /* We need to set these back *before* calling eoi.func */ - z80_state.eoi.func = NULL; - z80_state.eoi.trigger = -1; - - if (tracing(TRACE_CPU|TRACE_IO)) { - fprintf(tracef, "EOI: vector 0x%02x (%3u) PC=%04x\n", - eoi.trigger, eoi.trigger, REG_PC); - } - if (eoi.func) - eoi.func(eoi.trigger, eoi.arg); - } + check_eoi(); for (;;) { /* Poll for external event */ - z80_poll_external(); + if (z80_poll_external()) + return halted; /* Check for an interrupt */ if (z80_state.nminterrupt && !z80_state.nmi_in_progress) { - halted = false; - do_nmi(); - } else if (z80_state.interrupt && z80_state.iff1 && - !z80_state.ei_shadow) { - halted = false; - do_int(); + halted = false; + do_nmi(); + } else if (z80_state.iff1 && !z80_state.ei_shadow && + poll_irq()) { + halted = false; + do_int(); } z80_state.ei_shadow = false; if (!halted) break; TSTATE += 4; + + if (!continuous) + return halted; } if (tracing(TRACE_CPU)) { @@ -3686,9 +3674,7 @@ z80_reset(void) z80_state.ei_shadow = false; z80_state.interrupt_mode = 0; z80_state.nmi_in_progress = false; - z80_state.interrupt = false; - z80_state.int_in_progress = -1; - z80_state.eoi.trigger = -1; + z80_state.signal_eoi = false; /* z80_state.r = 0; */ } @@ -19,6 +19,8 @@ #include "compiler.h" #include "trace.h" +#include <SDL.h> + struct twobyte { #if WORDS_LITTLEENDIAN @@ -42,6 +44,8 @@ struct eoi { int trigger; /* Vector to call EOI for, otherwise -1 */ }; +struct z80_irq; + struct z80_state_struct { wordregister af; @@ -63,16 +67,10 @@ struct z80_state_struct uint8_t rf; /* fixed part of register R (bit 7) */ uint8_t interrupt_mode; - bool iff1, iff2, ei_shadow; + bool iff1, iff2, ei_shadow, signal_eoi; bool nmi_in_progress; /* to prevent multiple simultaneous NMIs */ - - bool nminterrupt; /* used to signal a non maskable interrupt */ - bool interrupt; /* used to signal an interrupt */ - - uint8_t i_vector; /* offset into interrupt-page from _external_ device */ - uint8_t int_in_progress; /* interrupt being serviced */ - struct eoi eoi; /* EOI (= RETI) callback */ + volatile bool nminterrupt; /* used to signal a non maskable interrupt */ uint64_t tc; /* T-state (clock cycle) counter */ }; @@ -190,7 +188,7 @@ extern void z80_out(int, uint8_t); extern int z80_in(int); extern int disassemble(int); extern int DAsm(uint16_t pc, char *T, int *target); -extern void z80_poll_external(void); +extern bool z80_poll_external(void); extern uint8_t ram[]; /* Array for plain RAM */ @@ -198,21 +196,4 @@ extern void mem_init(unsigned int flags, const char *memfile); #define MEMFL_NOBASIC 1 #define MEMFL_NODEV 2 -/* Signal an interrupt. If passed an odd value, e.g. -1, ignore. */ -extern void z80_interrupt_eoi(uint8_t vector, eoifunc do_eoi, void *eoi_arg); - -static inline void z80_interrupt(uint8_t vector) -{ - z80_interrupt_eoi(vector, NULL, NULL); -} - -static inline void z80_clear_interrupt(uint8_t vector) -{ - if (!(vector & 1) && z80_state.interrupt == vector) { - z80_state.interrupt = false; - if (z80_state.int_in_progress & 1) - z80_state.eoi.func = NULL; - } -} - #endif /* Z80_H */ diff --git a/z80irq.c b/z80irq.c new file mode 100644 index 0000000..2919db3 --- /dev/null +++ b/z80irq.c @@ -0,0 +1,132 @@ +#include "compiler.h" +#include "z80.h" +#include "z80irq.h" + +volatile unsigned int irq_pending; /* Quick way to poll */ +static struct z80_irq *irqs[MAX_IRQ]; +static struct z80_irq *current_irq; +static unsigned int current_prio; + +void z80_register_irq(struct z80_irq *irq) +{ + if (irq->prio < MAX_IRQ) + irqs[irq->prio] = irq; +} + +/* + * Z80 interrupt acknowledge cycle. Return the vector from the highest + * priority pending interrupt, or -1 if spurious. + */ +int z80_intack(void) +{ + int prio, vector; + unsigned int irqmask; + struct z80_irq *irq; + + do { + /* Find the highest priority (lowest numeric) interrupt pending */ + irqmask = irq_pending; + do { + if (!irqmask) + return -1; /* All interrupts went away... */ + + prio = __builtin_ctz(irqmask); + } while (!cmpxchg(&irq_pending, &irqmask, irqmask & ~(1U << prio))); + + irq = irqs[prio]; + + if (unlikely(irq->intack)) + vector = irq->intack(prio, irq); + else + vector = irq->vector; + } while (vector < 0); + + current_irq = irq; + current_prio = prio; + + return vector; +} + +/* + * A RETI instruction was invoked, which is interpreted as an EOI. + * In a real Z80 this is done by snooping the bus. Ick. + */ +void z80_eoi(void) +{ + struct z80_irq *irq = current_irq; + + if (!irq) + return; /* No known interrupt to EOI */ + + if (irq->eoi) + irq->eoi(current_prio, irq); + + current_irq = NULL; +} + +/* + * Raise an interrupt with specific priority level; return true if + * interrupt raised, false if the interrupt was already pending or + * the argument is invalid. + */ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +bool z80_interrupt(unsigned int prio) +{ + bool raised; + + asm volatile("lock btsl %2,%0" + : "+m" (irq_pending), "=@ccnc" (raised) + : "ri" (prio)); + + return raised; +} + +bool z80_clear_interrupt(unsigned int prio) +{ + bool cleared; + + asm volatile("lock btrl %2,%0" + : "+m" (irq_pending), "=@ccc" (cleared) + : "ri" (prio)); + + return cleared; +} + +#else + +bool z80_interrupt(unsigned int prio) +{ + unsigned int irqmask, irqpend; + + if (prio >= MAX_IRQ) + return false; + + irqmask = 1U << prio; + irqpend = irq_pending; + do { + if (irqpend & irqmask) + return false; + } while (!cmpxchg(&irq_pending, &irqpend, irqpend | irqmask)); + + return true; +} + +bool z80_clear_interrupt(unsigned int prio) +{ + unsigned int irqmask, irqpend; + + if (prio >= MAX_IRQ) + return false; + + irqmask = 1U << prio; + irqpend = irq_pending; + do { + if (!(irqpend & irqmask)) + return false; + } while (!cmpxchg(&irq_pending, &irqpend, irqpend & ~irqmask)); + + return true; +} + +#endif diff --git a/z80irq.h b/z80irq.h new file mode 100644 index 0000000..9e2c1dc --- /dev/null +++ b/z80irq.h @@ -0,0 +1,31 @@ +#ifndef Z80IRQ_H +#define Z80IRQ_H + +#include "compiler.h" +#include "z80.h" + +typedef int (*irq_func)(unsigned int prio, struct z80_irq *irq); + +struct z80_irq { + irq_func intack; + irq_func eoi; + void *pvt; /* Available for user */ + int vector; /* Available for user if intack defined */ + unsigned int prio; /* Available for user after register_irq */ +}; + +#define MAX_IRQ 32 + +extern volatile unsigned int irq_pending; +static inline bool poll_irq(void) +{ + return unlikely(irq_pending != 0); +} + +void z80_register_irq(struct z80_irq *irq); +int z80_intack(void); +void z80_eoi(void); +bool z80_interrupt(unsigned int prio); +bool z80_clear_interrupt(unsigned int prio); + +#endif /* Z80IRQ_H */ |