From 9084f2d0433ee05fc8e0b5469a409dc59ed00bd1 Mon Sep 17 00:00:00 2001 From: "LGB (Gabor Lenart)" Date: Mon, 13 Feb 2023 15:25:50 +0100 Subject: [PATCH] MEGA65: refactoring DMA emulation code #198 --- targets/mega65/dma65.c | 225 ++++++++++++++++++--------------- targets/mega65/dma65.h | 33 ++--- targets/mega65/hypervisor.c | 22 ++-- targets/mega65/input_devices.c | 13 +- targets/mega65/mega65.c | 7 +- 5 files changed, 159 insertions(+), 141 deletions(-) diff --git a/targets/mega65/dma65.c b/targets/mega65/dma65.c index 49f82ff1..b8ce7406 100644 --- a/targets/mega65/dma65.c +++ b/targets/mega65/dma65.c @@ -32,16 +32,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ # define DEBUGDMA(...) DEBUG(__VA_ARGS__) #endif -Uint8 dma_status; -Uint8 dma_registers[16]; // The four DMA registers (with last values written by the CPU) -int dma_default_revision; // DMA chip revision if non-enhanced DMA mode is used -int dma_revision; // current DMA revision throughout a DMA job or something +int in_dma; // DMA session is in progress if non-zero. Also used by the main emu loop to tell if it needs to call DMA or CPU emu. + // Hacky stuff: // low byte: the transparent byte value // bit 8: zero = transprent mode is used, 1 = no DMA transparency is in used // This is done this way to have a single 'if' to check both of enabled transparency and the transparent value, // since the value (being 8 bit) to be written would never match values > $FF -static unsigned int dma_transparency; +static unsigned int transparency; enum dma_op_types { COPY_OP, @@ -50,29 +48,24 @@ enum dma_op_types { FILL_OP }; +static int default_revision; // DMA chip revision if non-enhanced DMA mode is used +static int session_revision; // Current DMA revision throughout a DMA job (initialized from default_revision, unless if overridden in an enhanced mode DMA job) static int length; // DMA operation length static Uint8 length_byte3; // extra high byte of DMA length (bits 23-16) to allow to have >64K DMA (during optlist read, combined into length during the actual DMA execution) static int command; // DMA command (-1, no command yet) byte of DMA list reading static enum dma_op_types dma_op; // two lower bits of "command" static int chained; // 1 = chained (read next DMA operation "descriptor") -static int list_addr; // Current address of the DMA list, controller will read to "execute" +static int list_addr; // Current address for the DMA controller to read from, when reading/processing the DMA list static int list_addr_policy; // 0 = normal, 1 = list addr by CPU addr, 2 = list addr by CPU addr WITH PC-writeback (3=temporary signal to fetch CPU-PC then go back to '2') static Uint8 minterms[4]; // Used with MIX DMA command only -static int in_dma_update; // signal that DMA update do something. Currently only useful to avoid PANIC when DMA would modify its own registers -static int dma_self_write_warning; // Warning window policy for the event in case of the happening described in the comment of the previous line :) static Uint8 filler_byte; // byte used for FILL DMA command only -static int enhanced_dma; // MEGA65 enhanced mode DMA - -// In case of MEGA65 we should support fractional steps (8 bits for the fraction part). -// DMA_ADDR_FRACT_PART() is used only in debug functions to log -#define DMA_ADDR_INTEGER_PART(p) ((p)>>8) -#define DMA_SOURCE_SKIP_RATE ((int)(source.step_fract | (source.step_int << 8))) -#define DMA_TARGET_SKIP_RATE ((int)(target.step_fract | (target.step_int << 8))) -#define DMA_ADDR_FRACT_PART(p) ((p) & 0xFF) +static int enhanced_mode; // MEGA65 enhanced mode DMA +static int with_io; // legacy MEGA65 stuff, should be removed? 0x80 or 0 // On C65, DMA cannot cross 64K boundaries, so the right mask is 0xFFFF // On MEGA65 it seems to be 1Mbyte, thus the mask should be 0xFFFFF -#define DMA_ADDRESSING(channel) ((DMA_ADDR_INTEGER_PART(channel.addr) & 0xFFFFF) + channel.base) +// channel.addr is a fixed-point value, with the lower 8 bits being the fractional part +#define DMA_ADDRESSING(channel) (((channel.addr >> 8) & 0xFFFFF) + channel.base) // source and target DMA "channels": static struct { @@ -82,8 +75,8 @@ static struct { Uint8 step_fract; // step value during option read, fractional part only Uint8 step_int; // step value during option read, integer part only Uint8 mbyte; // megabyte slice selected during option read - int is_modulo; // modulo mode, if it's not zero - int also_io; // channel access I/O instead of memory, if it's not zero + int is_modulo; // modulo mode, if it's non-zero + int also_io; // channel access I/O instead of memory, if it's non-zero } source, target; static struct { @@ -91,6 +84,17 @@ static struct { } modulo; +// Calculates step rate as a positive/negative fixed-point-math value from input values, used by dma_update() at processing (after reading the whole) DMA list +// is_hold and is_dec(rement) are from the C65-style DMA list, step_int and step_fract are from MEGA65 enhanced mode options (if any) +static inline int calculate_skip_rate ( const int is_hold, const int is_dec, const Uint8 step_int, const Uint8 step_fract ) +{ + // TODO/FIXME: what is the exact algorithm to calulcate step rate based on C65 and M65 sources of info on a real MEGA65? + if (is_hold) + return 0; + const int ret = (step_int << 8) | step_fract; + return XEMU_UNLIKELY(is_dec) ? -ret : ret; +} + static XEMU_INLINE Uint8 dma_read_source ( void ) { @@ -108,7 +112,7 @@ static XEMU_INLINE Uint8 dma_read_target ( void ) static XEMU_INLINE void dma_write_source ( const Uint8 data ) { - if (XEMU_LIKELY((unsigned int)data != dma_transparency)) { + if (XEMU_LIKELY((unsigned int)data != transparency)) { const int addr = DMA_ADDRESSING(source); if (XEMU_UNLIKELY(source.also_io && ((addr & 0xF000) == 0xD000))) io_dma_writer(addr, data); @@ -119,7 +123,7 @@ static XEMU_INLINE void dma_write_source ( const Uint8 data ) static XEMU_INLINE void dma_write_target ( const Uint8 data ) { - if (XEMU_LIKELY((unsigned int)data != dma_transparency)) { + if (XEMU_LIKELY((unsigned int)data != transparency)) { const int addr = DMA_ADDRESSING(target); if (XEMU_UNLIKELY(target.also_io && ((addr & 0xF000) == 0xD000))) io_dma_writer(addr, data); @@ -130,7 +134,7 @@ static XEMU_INLINE void dma_write_target ( const Uint8 data ) #ifdef DO_DEBUG_DMA -static int dma_list_entry_pos = 0; +static int list_entry_pos = 0; #endif static Uint8 dma_read_list_next_byte ( void ) @@ -144,8 +148,8 @@ static Uint8 dma_read_list_next_byte ( void ) data = memory_dma_list_reader(list_addr); } #ifdef DO_DEBUG_DMA - DEBUGPRINT("DMA: reading DMA (rev#%d) list from $%08X [%s] [#%d]: $%02X" NL, dma_revision, list_addr, list_addr_policy ? "CPU-addr" : "linear-addr", dma_list_entry_pos, data); - dma_list_entry_pos++; + DEBUGPRINT("DMA: reading DMA (rev#%d) list from $%08X [%s] [#%d]: $%02X" NL, session_revision, list_addr, list_addr_policy ? "CPU-addr" : "linear-addr", list_entry_pos, data); + list_entry_pos++; #endif list_addr++; return data; @@ -199,92 +203,102 @@ void dma_write_reg ( int addr, Uint8 data ) // The following condition is commented out for now. FIXME: how it is handled for real?! //if (vic_iomode != VIC4_IOMODE) // addr &= 3; - if (XEMU_UNLIKELY(in_dma_update)) { + if (XEMU_UNLIKELY(in_dma)) { // this is just an emergency stuff to disallow DMA to update its own registers ... FIXME: what would be the correct policy? // NOTE: without this, issuing a DMA transfer updating DMA registers would affect an on-going DMA transfer! - if (dma_self_write_warning) { - dma_self_write_warning = 0; + static int do_warn = 1; + if (do_warn) { + do_warn = 0; ERROR_WINDOW("DMA writes its own registers, ignoring!\nThere will be no more warning on this!"); } DEBUG("DMA: WARNING: tries to write own register by DMA reg#%d with value of $%02X" NL, addr, data); return; } - dma_registers[addr] = data; // The rule here: every cases in the "switch" statement MUST return from the function, UNLESS the DMA register // write is about to initiate a new DMA session! switch (addr) { + case 0x1: + list_addr = (list_addr & 0xFFF00FF) + (data << 8); // setting bits 8-15 + return; + case 0x2: // for compatibility with C65, MEGA65 here resets the MB part of the DMA list address + list_addr = (list_addr & 0xFFFF) + ((data & 0x7F) << 16); // setting bits 16-22, clearing out bits >= 23 + with_io = (data & 0x80); + return; case 0x3: - if (dma_default_revision != (data & 1)) { - DEBUGPRINT("DMA: default DMA chip revision change %d -> %d because of writing DMA register 3" NL, dma_default_revision, data & 1); - dma_default_revision = data & 1; + if (default_revision != (data & 1)) { + DEBUGPRINT("DMA: default DMA chip revision change %d -> %d because of writing DMA register 3" NL, default_revision, data & 1); + default_revision = data & 1; } return; - case 0x2: // for compatibility with C65, MEGA65 here resets the MB part of the DMA list address - dma_registers[4] = 0; // this is the "MB" part of the DMA list address (hopefully ...) + case 0x4: + list_addr = (list_addr & 0xFFFFF) + (data << 20); // setting bits 27-20 return; case 0xE: // Set low order bits of DMA list address, without starting (MEGA65 feature, but without VIC4 new mode, this reg will never addressed here anyway) - dma_registers[0] = data; + list_addr = (list_addr & 0xFFFFF00) + data; // setting bits 7-0 return; default: + DEBUGDMA("DMA: unknown DMA register (#%d) is written with data $%02X @ PC = $%04X" NL, addr, data, cpu65.pc); return; + // Writing the following registers triggers DMA job, so no "return" but "break" from this point! case 0x0: + // Normal mode DMA session + enhanced_mode = 0; + list_addr_policy = 0; + list_addr = (list_addr & 0xFFFF00) + data; // setting bits 7-0, also clearing out bits >= 23 + break; // Do NOT return but continue the function! case 0x5: - // Normal DMA session (0) OR enhanced DMA session (5) - enhanced_dma = !!addr; - dma_registers[0] = data; - list_addr = data | (dma_registers[1] << 8) | ((dma_registers[2] & 0xF) << 16) | (dma_registers[4] << 20); + // Enhanced mode DMA session + enhanced_mode = 1; list_addr_policy = 0; + list_addr = (list_addr & 0xFFFFF00) + data; // setting bits 7-0 break; // Do NOT return but continue the function! case 0x6: // Enhanced DMA session, list by CPU address - enhanced_dma = 1; - dma_registers[0] = data; - list_addr = data | (dma_registers[1] << 8); + // TODO/FIXME: + // 1. really, it seems this mode would need to map out I/O (only/too?) ... Currently Xemu uses CPU's view of 64K as-is, which is maybe BAD! + // 2. I am not sure if only DMA list reading is meant by "CPU view" or even read/write ops! If the second, then my implementation is TOTALLY WRONG! + enhanced_mode = 1; list_addr_policy = 1; + list_addr = (list_addr & 0xFF00) + data; // setting bits 7-0, but it's CPU address now, so only 16 bits are valid in total! break; // Do NOT return but continue the function! case 0x7: - // Enhanced DMA session, list by CPU address _AND_ using CPU's PC register! - enhanced_dma = 1; + // Enhanced DMA session, list by CPU address _AND_ using CPU's PC register! ("inline-DMA") + // TODO/FIXME: + // 1. I am not sure if only DMA list reading is meant by "CPU view" or even read/write ops! If the second, then my implementation is TOTALLY WRONG! + // 2. if the second, then the same problem as with "6", I/O mapping out etc? + enhanced_mode = 1; list_addr_policy = 3; // 3 signals to pick up CPU's PC in dma_update() then using '2'!! So list_addr will be set there. break; // Do NOT return but continue the function! } /* --- FROM THIS POINT, THERE IS A NEW DMA SESSION WAS INITAITED BY SOME REGISTER WRITE --- */ - if (XEMU_UNLIKELY(dma_status)) - FATAL("dma_write_reg(): new DMA op with dma_status != 0"); // Initial values, enhanced mode DMA may overrides this later during reading enhanced option list - dma_revision = dma_default_revision; // initialize current revision from default revision - dma_transparency = 0x100; // no DMA transparency by default + session_revision = default_revision; // initialize current revision from default revision + transparency = 0x100; // no DMA transparency by default source.step_fract = 0; // source skip rate, fraction part source.step_int = 1; // source skip rate, integer part target.step_fract = 0; // target skip rate, fraction part target.step_int = 1; // target skip rate, integer part source.mbyte = 0; // source MB target.mbyte = 0; // target MB - length_byte3 = 0; - if (enhanced_dma) + length_byte3 = 0; // length byte for >=64K DMA sessions + if (enhanced_mode) DEBUGDMA("DMA: initiation of ENCHANCED MODE DMA!!!!\n"); else DEBUGDMA("DMA: initiation of normal mode DMA\n"); DEBUGDMA("DMA: list address is $%07X (%s) now, just written to register %d value $%02X @ PC=$%04X" NL, list_addr, list_addr_policy ? "CPU-addr" : "linear-addr", addr, data, cpu65.pc); - dma_status = 0x80; // DMA is busy now, also to signal the emulator core to call dma_update() in its main loop + in_dma = 0x80; // DMA is busy now, also to signal the emulator core to call dma_update() in its main loop command = -1; // signal dma_update() that it's needed to fetch the DMA command, no command is fetched yet cpu65.multi_step_stop_trigger = 1; // trigger stopping multi-op CPU emulation mode, otherwise DMA wouldn't be started when it should be, right after triggering! } -int dma_is_in_use ( void ) -{ - return in_dma_update; -} - - int dma_get_revision ( void ) { - return dma_default_revision; + return default_revision; } -/* Main emulation loop should call this function regularly, if dma_status is not zero. +/* Main emulation loop should call this function regularly, if in_dma is non-zero. This way we have 'real' DMA, ie works while the rest of the machine is emulated too. Please note, that the "exact" timing of DMA and eg the CPU is still incorrect, but it's far better than the previous version where DMA was "blocky", ie the whole machine was "halted" while DMA worked ... @@ -293,15 +307,15 @@ int dma_get_revision ( void ) int dma_update ( void ) { int cycles = 0; - if (XEMU_UNLIKELY(!dma_status)) - FATAL("dma_update() called with no dma_status set!"); + if (XEMU_UNLIKELY(!in_dma)) + FATAL("dma_update() called with no in_dma set!"); if (XEMU_UNLIKELY(command == -1)) { if (XEMU_UNLIKELY(list_addr_policy == 3)) { list_addr = cpu65.pc; DEBUGDMA("DMA: adjusting list addr to CPU PC: $%04X" NL, list_addr); list_addr_policy = 2; } - if (enhanced_dma) { + if (enhanced_mode) { Uint8 opt, optval; do { opt = dma_read_list_next_byte(); @@ -317,15 +331,15 @@ int dma_update ( void ) DEBUGDMA("DMA: end of enhanced options" NL); break; case 0x06: // disable transparency (setting high byte of transparency, thus will never match) - dma_transparency |= 0x100; + transparency |= 0x100; break; case 0x07: // enable transparency - dma_transparency &= 0xFF; + transparency &= 0xFF; break; case 0x0A: case 0x0B: - DEBUGDMA("DMA: per-session changing DMA revision during enhanced list %d -> %d" NL, dma_revision, opt - 0x0A); - dma_revision = opt - 0x0A; + DEBUGDMA("DMA: per-session changing DMA revision during enhanced list %d -> %d" NL, session_revision, opt - 0x0A); + session_revision = opt - 0x0A; break; case 0x80: // set MB of source source.mbyte = optval; @@ -346,7 +360,7 @@ int dma_update ( void ) target.step_int = optval; break; case 0x86: // byte value to be treated as "transparent" (ie: skip writing that data), if enabled - dma_transparency = (dma_transparency & 0x100) | (unsigned int)optval; + transparency = (transparency & 0x100) | (unsigned int)optval; break; case 0x90: // extra high byte of DMA length (bits 23-16) to allow to have >64K DMA length_byte3 = optval; @@ -365,7 +379,7 @@ int dma_update ( void ) // This part is highly incorrect, ie fetching so many bytes in one step only of dma_update() // NOTE: in case of MEGA65: dma_read_list_next_byte() uses the "megabyte" part already (taken from reg#4, in case if that reg is written) #ifdef DO_DEBUG_DMA - dma_list_entry_pos = 0; + list_entry_pos = 0; #endif command = dma_read_list_next_byte(); dma_op = (enum dma_op_types)(command & 3); @@ -380,18 +394,18 @@ int dma_update ( void ) target.addr |= dma_read_list_next_byte() << 8; target.addr |= dma_read_list_next_byte() << 16; Uint8 subcommand; - if (dma_revision) // for F018B we have an extra byte fetch here! used later in this function [making DMA list one byte longer, indeed] + if (session_revision) // for F018B we have an extra byte fetch here! used later in this function [making DMA list one byte longer, indeed] subcommand = dma_read_list_next_byte(); else subcommand = 0; // just make gcc happy not to generate warning later // On MEGA65, modulo is used as a fixed point arithmetic value modulo.value = dma_read_list_next_byte() << 8; modulo.value |= dma_read_list_next_byte() << 16; - if (dma_revision) { + if (session_revision) { cycles += 12; // FIXME: correct timing? // F018B ("new") behaviour - source.step = (subcommand & 2) ? 0 : ((command & 16) ? -DMA_SOURCE_SKIP_RATE : DMA_SOURCE_SKIP_RATE); - target.step = (subcommand & 8) ? 0 : ((command & 32) ? -DMA_TARGET_SKIP_RATE : DMA_TARGET_SKIP_RATE); + source.step = calculate_skip_rate(subcommand & 2, command & 16, source.step_int, source.step_fract); + target.step = calculate_skip_rate(subcommand & 8, command & 32, target.step_int, target.step_fract); source.is_modulo = (subcommand & 1); target.is_modulo = (subcommand & 4); if (dma_op == MIX_OP) { // if it's a MIX command @@ -404,8 +418,8 @@ int dma_update ( void ) } else { cycles += 11; // FIXME: correct timing? // F018A ("old") behaviour - source.step = (source.addr & 0x100000) ? 0 : ((source.addr & 0x400000) ? -DMA_SOURCE_SKIP_RATE : DMA_SOURCE_SKIP_RATE); - target.step = (target.addr & 0x100000) ? 0 : ((target.addr & 0x400000) ? -DMA_TARGET_SKIP_RATE : DMA_TARGET_SKIP_RATE); + source.step = calculate_skip_rate(source.addr & 0x100000, source.addr & 0x400000, source.step_int, source.step_fract); + target.step = calculate_skip_rate(target.addr & 0x100000, target.addr & 0x400000, target.step_int, target.step_fract); source.is_modulo = (source.addr & 0x200000); target.is_modulo = (target.addr & 0x200000); if (dma_op == MIX_OP) { // if it's a MIX command @@ -452,13 +466,13 @@ int dma_update ( void ) // base selection for M65 // M65 has an "mbyte part" register for source (and target too) // however, with F018B there are 3 bits over 1Mbyte as well, and it seems M65 (see VHDL code) add these together then. Interesting. - if (dma_revision) + if (session_revision) source.base = ((source.mbyte << 20) + (source.addr & 0x700000)) & 0xFF00000; else source.base = source.mbyte << 20; source.addr = (source.addr & 0x0FFFFF) << 8;// offset from base, for M65 this *IS* fixed point arithmetic! /* -- target selection -- see similar lines with comments above, for source ... */ - if (dma_revision) + if (session_revision) target.base = ((target.mbyte << 20) + (target.addr & 0x700000)) & 0xFF00000; else target.base = target.mbyte << 20; @@ -467,8 +481,8 @@ int dma_update ( void ) chained = (command & 4); // FIXME: this is a debug mesg, yeah, but with fractional step on M65, the step values needs to be interpreted with keep in mind the fixed point math ... DEBUG("DMA: READ COMMAND: $%07X[%s%s %d:%d] -> $%07X[%s%s %d:%d] (L=$%04X) CMD=%d (%s)" NL, - DMA_ADDRESSING(source), source.also_io ? "I/O" : "MEM", source.is_modulo ? " MOD" : "", DMA_ADDR_INTEGER_PART(source.step), DMA_ADDR_FRACT_PART(source.step), - DMA_ADDRESSING(target), target.also_io ? "I/O" : "MEM", target.is_modulo ? " MOD" : "", DMA_ADDR_INTEGER_PART(target.step), DMA_ADDR_FRACT_PART(target.step), + DMA_ADDRESSING(source), source.also_io ? "I/O" : "MEM", source.is_modulo ? " MOD" : "", source.step >> 8, source.step & 0xFF, + DMA_ADDRESSING(target), target.also_io ? "I/O" : "MEM", target.is_modulo ? " MOD" : "", target.step >> 8, target.step & 0xFF, length, dma_op, chained ? "CHAINED" : "LAST" ); if (!length) @@ -477,7 +491,6 @@ int dma_update ( void ) } // We have valid command to be executed, or continue to execute //DEBUG("DMA: EXECUTING: command=%d length=$%04X" NL, command & 3, length); - in_dma_update = 1; switch (dma_op) { case COPY_OP: // COPY command (0) copy_next(); @@ -503,7 +516,7 @@ int dma_update ( void ) // it will be detected by the 'if' at the bottom of this huge function as the end of the DMA op if (modulo.col_counter == modulo.col_limit) { if (modulo.row_counter == modulo.row_limit) { - length = 0; // just to fullfish end-of-operation condition + length = 0; // just to fulfill end-of-operation condition } else { // end of modulo "columns", but there are "rows" left, reset columns counter, increment row counter, // also add the modulo value to source and/or target channels, if the given channel is in modulo mode @@ -523,10 +536,10 @@ int dma_update ( void ) if (length <= 0) { // end of DMA operation for the current DMA list entry? if (chained) { // chained? DEBUGDMA("DMA: end of operation, but chained!" NL); - dma_status = 0x81; // still busy then, with also bit0 set (chained) + in_dma = 0x81; // still busy then, with also bit0 set (chained) } else { DEBUGDMA("DMA: end of operation, no chained next one." NL); - dma_status = 0; // end of DMA command + in_dma = 0; // end of DMA command if (list_addr_policy == 2) { DEBUGDMA("DMA: adjusting CPU PC from $%04X to $%04X" NL, cpu65.pc, list_addr & 0xFFFF); cpu65.pc = list_addr & 0xFFFF; @@ -534,17 +547,16 @@ int dma_update ( void ) } command = -1; // signal for next DMA command fetch } - in_dma_update = 0; return cycles; } -int dma_update_multi_steps ( int do_for_cycles ) +int dma_update_multi_steps ( const int do_for_cycles ) { int cycles = 0; - do { + while (cycles <= do_for_cycles && in_dma) { cycles += dma_update(); - } while (cycles <= do_for_cycles && dma_status); + } return cycles; } @@ -557,18 +569,17 @@ void dma_init_set_rev ( const Uint8 *rom ) const int rom_suggested_dma_revision = (rom_date < 900000 || rom_date > 910522 || rom_is_openroms); if (rom_date <= 0) WARNING_WINDOW("ROM version cannot be detected.\nDefaulting to revision %d.\nWarning, this may cause incorrect behaviour!", rom_suggested_dma_revision); - DEBUGPRINT("DMA: default DMA chip revision change %d -> %d based on ROM version (%d %s)" NL, dma_default_revision, rom_suggested_dma_revision, rom_date, rom_name); - dma_default_revision = rom_suggested_dma_revision; - dma_registers[3] = dma_default_revision; + DEBUGPRINT("DMA: default DMA chip revision change %d -> %d based on ROM version (%d %s)" NL, default_revision, rom_suggested_dma_revision, rom_date, rom_name); + default_revision = rom_suggested_dma_revision; } void dma_init ( void ) { modulo.enabled = 0; // FIXME: make it configurable (real MEGA65 does not support modulo) - dma_default_revision = 1; // FIXME: what should be the power-on default revision? + default_revision = 1; // FIXME: what should be the power-on default revision? DEBUGPRINT("DMA: initializing DMA engine for chip revision %d (initially, may be modified later!), modulo_support=%s." NL, - dma_default_revision, + default_revision, modulo.enabled ? "ENABLED" : "DISABLED" ); dma_reset(); @@ -578,13 +589,11 @@ void dma_init ( void ) void dma_reset ( void ) { command = -1; // no command is fetched yet - dma_status = 0; - memset(dma_registers, 0, sizeof dma_registers); + in_dma = 0; source.base = 0; target.base = 0; - in_dma_update = 0; - dma_self_write_warning = 1; - dma_registers[3] = dma_default_revision; + list_addr = 0; + with_io = 0; } @@ -594,21 +603,22 @@ Uint8 dma_read_reg ( int addr ) switch (addr) { case 0: case 5: - data = dma_registers[0]; + data = list_addr & 0xFF; break; case 1: - data = dma_registers[1]; + data = (list_addr >> 8) & 0xFF; break; case 2: - data = dma_registers[2]; // FIXME: MS bit should be "reg_dmagic_withio"?! + data = with_io + ((list_addr >> 16) & 0x7F); break; case 3: - data = (dma_status & 0xFE) | dma_default_revision; // reg_dmagic_status(7 downto 1) & support_f018b + data = (in_dma & 0xFE) | default_revision; // reg_dmagic_status(7 downto 1) & support_f018b break; case 4: - data = dma_registers[4]; // reg_dmagic_addr(27 downto 20); + data = (list_addr >> 20) & 0xFF; // reg_dmagic_addr(27 downto 20); break; default: + DEBUGDMA("DMA: unknown DMA register (#%d) is read @ PC = $%04X" NL, addr, cpu65.pc); data = 0xFF; // FIXME: the right choice of reading "unknown" DMA register? break; } @@ -617,6 +627,21 @@ Uint8 dma_read_reg ( int addr ) } +void dma_get_list_addr_as_bytes ( Uint8 *p ) +{ + p[0] = list_addr & 0xFF; + p[1] = (list_addr >> 8) & 0xFF; + p[2] = (list_addr >> 16) & 0xFF; + p[3] = (list_addr >> 24) & 0x0F; +} + + +void dma_set_list_addr_from_bytes ( const Uint8 *p ) +{ + list_addr = p[0] + (p[1] << 8) + (p[2] << 16) + ((p[3] & 0x0F) << 24); + DEBUGDMA("DMA: list address is set 'externally' to $%X" NL, list_addr); +} + /* --- SNAPSHOT RELATED --- */ #ifdef XEMU_SNAPSHOT_SUPPORT diff --git a/targets/mega65/dma65.h b/targets/mega65/dma65.h index 415bd9b4..ae1ed6ca 100644 --- a/targets/mega65/dma65.h +++ b/targets/mega65/dma65.h @@ -1,6 +1,6 @@ /* F018 DMA core emulation for MEGA65 Part of the Xemu project. https://github.com/lgblgblgb/xemu - Copyright (C)2016-2022 LGB (Gábor Lénárt) + Copyright (C)2016-2023 LGB (Gábor Lénárt) 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 @@ -19,25 +19,18 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef XEMU_MEGA65_DMA_H_INCLUDED #define XEMU_MEGA65_DMA_H_INCLUDED -/* Variables */ - -extern Uint8 dma_status; -extern Uint8 dma_registers[16]; -extern int dma_chip_revision; - -/* Functions: */ - -extern void dma_write_reg ( int addr, Uint8 data ); -extern Uint8 dma_read_reg ( int reg ); -extern void dma_init ( void ); -extern void dma_init_set_rev ( const Uint8 *rom ); -extern void dma_reset ( void ); -extern int dma_update ( void ); -extern int dma_update_multi_steps ( int do_for_cycles ); -extern int dma_is_in_use ( void ); -extern int dma_get_revision ( void ); - -/* Snapshot related part: */ +extern int in_dma; + +extern void dma_write_reg ( int addr, Uint8 data ); +extern Uint8 dma_read_reg ( int reg ); +extern void dma_init ( void ); +extern void dma_init_set_rev ( const Uint8 *rom ); +extern void dma_reset ( void ); +extern int dma_update ( void ); +extern int dma_update_multi_steps ( const int do_for_cycles ); +extern int dma_get_revision ( void ); +extern void dma_get_list_addr_as_bytes ( Uint8 *p ); +extern void dma_set_list_addr_from_bytes ( const Uint8 *p ); #ifdef XEMU_SNAPSHOT_SUPPORT #include "xemu/emutools_snapshot.h" diff --git a/targets/mega65/hypervisor.c b/targets/mega65/hypervisor.c index 5d63af03..ec86117e 100644 --- a/targets/mega65/hypervisor.c +++ b/targets/mega65/hypervisor.c @@ -1,6 +1,6 @@ /* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu - Copyright (C)2016-2022 LGB (Gábor Lénárt) + Copyright (C)2016-2023 LGB (Gábor Lénárt) 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 @@ -94,7 +94,7 @@ int hypervisor_queued_enter ( int trapno ) // (sometimes one byte is skipped on execution after a trap caused by writing D640-D67F) void hypervisor_enter_via_write_trap ( int trapno ) { - if (XEMU_UNLIKELY(dma_is_in_use())) { + if (XEMU_UNLIKELY(in_dma)) { static int do_warn = 1; if (do_warn) { WARNING_WINDOW("DMA operation would trigger hypervisor trap.\nThis is totally ignored!\nThere will be no future warning before you restart Xemu"); @@ -157,12 +157,9 @@ void hypervisor_enter ( int trapno ) D6XX_registers[0x50] = memory_get_cpu_io_port(0); D6XX_registers[0x51] = memory_get_cpu_io_port(1); D6XX_registers[0x52] = vic_iomode; - D6XX_registers[0x53] = dma_registers[5]; // GS $D653 - Hypervisor DMAgic source MB - D6XX_registers[0x54] = dma_registers[6]; // GS $D654 - Hypervisor DMAgic destination MB - D6XX_registers[0x55] = dma_registers[0]; // GS $D655 - Hypervisor DMAGic list address bits 0-7 - D6XX_registers[0x56] = dma_registers[1]; // GS $D656 - Hypervisor DMAGic list address bits 15-8 - D6XX_registers[0x57] = (dma_registers[2] & 15) | ((dma_registers[4] & 15) << 4); // GS $D657 - Hypervisor DMAGic list address bits 23-16 - D6XX_registers[0x58] = dma_registers[4] >> 4; // GS $D658 - Hypervisor DMAGic list address bits 27-24 + D6XX_registers[0x53] = 0; // GS $D653 - Hypervisor DMAgic source MB - *UNUSED* + D6XX_registers[0x54] = 0; // GS $D654 - Hypervisor DMAgic destination MB - *UNUSED* + dma_get_list_addr_as_bytes(D6XX_registers + 0x55); // GS $D655-$D658 - Hypervisor DMAGic list address bits 27-0 // Now entering into hypervisor mode in_hypervisor = 1; // this will cause apply_memory_config to map hypervisor RAM, also for checks later to out-of-bound execution of hypervisor RAM, etc ... // In hypervisor mode, VIC4 I/O mode is implied. I also disable $D02F writing to take effect while in hypervisor mode in vic4.c! @@ -334,12 +331,9 @@ void hypervisor_leave ( void ) vic_iomode = D6XX_registers[0x52] & 3; if (vic_iomode == VIC_BAD_IOMODE) vic_iomode = VIC3_IOMODE; // I/O mode "2" (binary: 10) is not used, I guess - dma_registers[5] = D6XX_registers[0x53]; // GS $D653 - Hypervisor DMAgic source MB - dma_registers[6] = D6XX_registers[0x54]; // GS $D654 - Hypervisor DMAgic destination MB - dma_registers[0] = D6XX_registers[0x55]; // GS $D655 - Hypervisor DMAGic list address bits 0-7 - dma_registers[1] = D6XX_registers[0x56]; // GS $D656 - Hypervisor DMAGic list address bits 15-8 - dma_registers[2] = D6XX_registers[0x57] & 15; // - dma_registers[4] = (D6XX_registers[0x57] >> 4) | (D6XX_registers[0x58] << 4); + // GS $D653 - Hypervisor DMAgic source MB - *UNUSED* + // GS $D654 - Hypervisor DMAgic destination MB - *UNUSED* + dma_set_list_addr_from_bytes(D6XX_registers + 0x55); // GS $D655-$D658 - Hypervisor DMAGic list address bits 27-0 // Now leaving hypervisor mode ... in_hypervisor = 0; machine_set_speed(0); // restore speed ... diff --git a/targets/mega65/input_devices.c b/targets/mega65/input_devices.c index e90e18e0..fce7739a 100644 --- a/targets/mega65/input_devices.c +++ b/targets/mega65/input_devices.c @@ -1,6 +1,6 @@ /* A work-in-progess MEGA65 (Commodore-65 clone origins) emulator Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu - Copyright (C)2016-2022 LGB (Gábor Lénárt) + Copyright (C)2016-2023 LGB (Gábor Lénárt) 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 @@ -27,6 +27,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "hypervisor.h" #include "ui.h" #include "matrix_mode.h" +#include "dma65.h" #define DEBUGKBD(...) DEBUG(__VA_ARGS__) @@ -383,12 +384,16 @@ void kbd_trigger_restore_trap ( void ) restore_is_held++; if (restore_is_held >= 20) { restore_is_held = 0; - if (!in_hypervisor) { + if (XEMU_UNLIKELY(in_hypervisor)) { + DEBUGPRINT("KBD: *IGNORING* RESTORE trap trigger, already in hypervisor mode!" NL); + } else if (XEMU_UNLIKELY(in_dma)) { + // keyboard triggered trap is "async" but the CPU must be not in DMA mode to be able to handle that without a disaster + DEBUGPRINT("KBD: *IGNORING* RESTORE trap trigger, DMA is in progress!" NL); + } else { DEBUGPRINT("KBD: RESTORE trap has been triggered." NL); KBD_RELEASE_KEY(RESTORE_KEY_POS); hypervisor_enter(TRAP_FREEZER_RESTORE_PRESS); - } else - DEBUGPRINT("KBD: *IGNORING* RESTORE trap trigger, already in hypervisor mode!" NL); + } } } } diff --git a/targets/mega65/mega65.c b/targets/mega65/mega65.c index 9ebd76ae..d5085e56 100644 --- a/targets/mega65/mega65.c +++ b/targets/mega65/mega65.c @@ -1,6 +1,6 @@ /* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu - Copyright (C)2016-2022 LGB (Gábor Lénárt) + Copyright (C)2016-2023 LGB (Gábor Lénárt) 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 @@ -532,6 +532,7 @@ void reset_mega65_cpu_only ( void ) vic_registers[0x30] = 0; // FIXME: hack! we need this, and memory_set_vic3_rom_mapping above too :( memory_set_vic3_rom_mapping(0); memory_set_do_map(); + dma_reset(); // We need this: even though it's CPU reset only, DMA is part of the CPU: either DMA or CPU running, resetting in the middle of a DMA session is a disaster cpu65_reset(); } @@ -722,7 +723,7 @@ static void emulation_loop ( void ) } #endif while (XEMU_UNLIKELY(paused)) { // paused special mode, ie tracing support, or something ... - if (XEMU_UNLIKELY(dma_status)) + if (XEMU_UNLIKELY(in_dma)) break; // if DMA is pending, do not allow monitor/etc features #ifdef HAS_UARTMON_SUPPORT if (m65mon_callback) { // delayed uart monitor command should be finished ... @@ -766,7 +767,7 @@ static void emulation_loop ( void ) DEBUGPRINT("TRACE: Breakpoint @ $%04X hit, Xemu moves to trace mode after the execution of this opcode." NL, cpu65.pc); paused = 1; } - cycles += XEMU_UNLIKELY(dma_status) ? dma_update_multi_steps(cpu_cycles_per_scanline) : cpu65_step( + cycles += XEMU_UNLIKELY(in_dma) ? dma_update_multi_steps(cpu_cycles_per_scanline) : cpu65_step( #ifdef CPU_STEP_MULTI_OPS cpu_cycles_per_step #endif