--- tme/ic/m68k/m68k-misc.c 2018/04/24 16:37:52 1.1 +++ tme/ic/m68k/m68k-misc.c 2018/04/24 16:45:41 1.1.1.6 @@ -1,22 +1,42 @@ -/* m68k-misc.c - miscellaneous things for the m68k emulator: */ +/* $Id: m68k-misc.c,v 1.1.1.6 2018/04/24 16:45:41 root Exp $ */ -/* $Id: m68k-misc.c,v 1.1 2018/04/24 16:37:52 root Exp $ */ +/* ic/m68k/m68k-misc.c - miscellaneous things for the m68k emulator: */ + +/* + * Copyright (c) 2002, 2003 Matt Fredette + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Matt Fredette. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ /* includes: */ #include "m68k-impl.h" -_TME_RCSID("$Id: m68k-misc.c,v 1.1 2018/04/24 16:37:52 root Exp $"); - -/* small immediates: */ -const tme_uint32_t _tme_m68k_imm32[9] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8 -}; -const tme_uint16_t _tme_m68k_imm16[9] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8 -}; -const tme_uint8_t _tme_m68k_imm8[9] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8 -}; +_TME_RCSID("$Id: m68k-misc.c,v 1.1.1.6 2018/04/24 16:45:41 root Exp $"); /* the memory buffer read and write functions: */ #if TME_M68K_SIZE_8 != 1 @@ -68,17 +88,16 @@ _tme_m68k_bus_signal(struct tme_bus_conn ic = conn_bus->tme_bus_connection.tme_connection_element->tme_element_private; /* take out the level and edge: */ - level_edge = signal & (TME_BUS_SIGNAL_LEVEL_MASK - | TME_BUS_SIGNAL_EDGE); - signal &= ~(TME_BUS_SIGNAL_LEVEL_MASK - | TME_BUS_SIGNAL_EDGE); + level_edge = signal; + signal = TME_BUS_SIGNAL_WHICH(signal); + level_edge ^= signal; /* lock the external mutex: */ tme_mutex_lock(&ic->tme_m68k_external_mutex); /* on the falling edge of HALT or RESET, halt the processor: */ - if (level_edge == (TME_BUS_SIGNAL_LEVEL_ASSERTED - | TME_BUS_SIGNAL_EDGE) + if (((level_edge & TME_BUS_SIGNAL_LEVEL_MASK) + == TME_BUS_SIGNAL_LEVEL_ASSERTED) && (signal == TME_BUS_SIGNAL_HALT || signal == TME_BUS_SIGNAL_RESET)) { ic->tme_m68k_external_halt = TRUE; @@ -86,8 +105,8 @@ _tme_m68k_bus_signal(struct tme_bus_conn /* on the rising edge of RESET, reset the processor: */ else if (signal == TME_BUS_SIGNAL_RESET - && level_edge == (TME_BUS_SIGNAL_LEVEL_NEGATED - | TME_BUS_SIGNAL_EDGE)) { + && ((level_edge & TME_BUS_SIGNAL_LEVEL_MASK) + == TME_BUS_SIGNAL_LEVEL_NEGATED)) { ic->tme_m68k_external_reset = TRUE; } @@ -110,6 +129,23 @@ _tme_m68k_bus_signal(struct tme_bus_conn return (TME_OK); } +/* this enables or disables an m6888x: */ +static int +_tme_m6888x_enable(struct tme_m68k_bus_connection *conn_m68k, int enabled) +{ + struct tme_m68k *ic; + + /* recover our IC: */ + ic = conn_m68k->tme_m68k_bus_connection.tme_bus_connection.tme_connection_element->tme_element_private; + + /* NB: we're lazy here and don't bother locking the external mutex: */ + if (ic->tme_m68k_fpu_type == TME_M68K_FPU_NONE) { + return (ENXIO); + } + ic->tme_m68k_fpu_enabled = enabled; + return (TME_OK); +} + /* our interrupt handler: */ static int _tme_m68k_bus_interrupt(struct tme_m68k_bus_connection *conn_m68k, unsigned int ipl) @@ -125,6 +161,12 @@ _tme_m68k_bus_interrupt(struct tme_m68k_ /* set the interrupt line: */ ic->tme_m68k_external_ipl = ipl; + /* if the IPL has dropped below the NMI level, the next transition + to that level will cause an NMI: */ + if (ipl < TME_M68K_IPL_NMI) { + ic->tme_m68k_external_ipl_previous_nmi = FALSE; + } + /* unlock the external mutex: */ tme_mutex_unlock(&ic->tme_m68k_external_mutex); @@ -147,7 +189,7 @@ tme_m68k_external_check(struct tme_m68k if (ic->tme_m68k_external_reset) { ic->tme_m68k_external_reset = FALSE; tme_mutex_unlock(&ic->tme_m68k_external_mutex); - tme_m68k_exception(ic, TME_M68K_EXCEPTION_GROUP0_RESET); + tme_m68k_exception(ic, TME_M68K_EXCEPTION_RESET); } /* if an external halt has been requested, halt: */ @@ -165,30 +207,39 @@ tme_m68k_external_check(struct tme_m68k if (ic->_tme_m68k_mode != TME_M68K_MODE_HALT && ipl >= TME_M68K_IPL_MIN && ipl <= TME_M68K_IPL_MAX - && (ipl == TME_M68K_IPL_NMI + && ((ipl == TME_M68K_IPL_NMI + && !ic->tme_m68k_external_ipl_previous_nmi) || ipl > TME_M68K_FLAG_IPM(ic->tme_m68k_ireg_sr))) { + + /* if this is an NMI, prevent it from being repeatedly accepted: */ + if (ipl == TME_M68K_IPL_NMI) { + ic->tme_m68k_external_ipl_previous_nmi = TRUE; + } + tme_mutex_unlock(&ic->tme_m68k_external_mutex); /* acknowledge the interrupt and get the vector: */ + tme_m68k_callout_unlock(ic); rc = (*ic->_tme_m68k_bus_connection->tme_m68k_bus_connection.tme_bus_intack) (&ic->_tme_m68k_bus_connection->tme_m68k_bus_connection, ipl, &vector); + tme_m68k_callout_relock(ic); if (rc == TME_EDEADLK) { abort(); } /* if the interrupt acknowledge failed, this is a spurious interrupt: */ if (rc == ENOENT) { - vector = 24; + vector = TME_M68K_VECTOR_SPURIOUS; } /* if no vector is given, use the autovector: */ else if (vector == TME_BUS_INTERRUPT_VECTOR_UNDEF) { - vector = 24 + ipl; + vector = TME_M68K_VECTOR_SPURIOUS + ipl; } /* dispatch the exceptions: */ - tme_m68k_exception(ic, internal_exceptions | TME_M68K_EXCEPTION_GROUP1_INT(ipl, vector)); + tme_m68k_exception(ic, internal_exceptions | TME_M68K_EXCEPTION_INT(ipl, vector)); } /* if there are internal exceptions to process, do so: */ @@ -226,6 +277,12 @@ tme_m68k_thread(struct tme_m68k *ic) /* we use longjmp to redispatch: */ do { } while (setjmp(ic->_tme_m68k_dispatcher)); + /* we must not have a busy fast instruction TLB entry: */ + assert (ic->_tme_m68k_insn_fetch_fast_itlb == NULL); + + /* clear the group 0 hook: */ + ic->_tme_m68k_group0_hook = NULL; + /* dispatch on the current mode: */ switch (ic->_tme_m68k_mode) { @@ -246,6 +303,9 @@ tme_m68k_thread(struct tme_m68k *ic) tme_m68k_idle(ic); /* NOTREACHED */ + case TME_M68K_MODE_DIE: + return; + default: abort(); } @@ -296,15 +356,16 @@ _tme_m68k_connection_score(struct tme_co /* this must be a bus, and not another m68k chip: */ case TME_CONNECTION_BUS_M68K: - if (conn_bus->tme_bus_tlb_set_allocate != NULL - && conn_m68k->tme_m68k_bus_tlb_fill != NULL) { + if (conn_bus->tme_bus_tlb_set_add != NULL + && conn_m68k->tme_m68k_bus_tlb_fill != NULL + && conn_m68k->tme_m68k_bus_m6888x_enable == NULL) { score = 10; } break; /* this must be a bus, and not another chip: */ case TME_CONNECTION_BUS_GENERIC: - if (conn_bus->tme_bus_tlb_set_allocate != NULL + if (conn_bus->tme_bus_tlb_set_add != NULL && conn_bus->tme_bus_tlb_fill != NULL) { score = 1; } @@ -325,6 +386,10 @@ _tme_m68k_connection_make(struct tme_con struct tme_m68k_bus_connection *conn_m68k; struct tme_bus_connection *conn_bus; struct tme_connection *conn_other; + struct tme_bus_tlb_set_info tlb_set_info; + unsigned long tlb_i; + struct tme_m68k_tlb *tlb; + int rc; /* since the CPU is halted, it won't be making any connection calls, so we only have to do work when the connection is fully made: */ @@ -355,19 +420,29 @@ _tme_m68k_connection_make(struct tme_con default: abort(); } - /* allocate the TLB hash set: */ - (*ic->_tme_m68k_bus_connection->tme_m68k_bus_connection.tme_bus_tlb_set_allocate) - (&ic->_tme_m68k_bus_connection->tme_m68k_bus_connection, - _TME_M68K_TLB_HASH_SIZE, - sizeof(struct tme_m68k_tlb), - TME_ATOMIC_POINTER((struct tme_bus_tlb **) &ic->_tme_m68k_tlb_array)); - - /* allocate the ITLB set: */ - (*ic->_tme_m68k_bus_connection->tme_m68k_bus_connection.tme_bus_tlb_set_allocate) - (&ic->_tme_m68k_bus_connection->tme_m68k_bus_connection, - 1, - sizeof(struct tme_m68k_tlb), - TME_ATOMIC_POINTER((struct tme_bus_tlb **) &ic->_tme_m68k_itlb)); + /* make the TLB set information: */ + memset(&tlb_set_info, 0, sizeof(tlb_set_info)); + tlb_set_info.tme_bus_tlb_set_info_token0 = &ic->_tme_m68k_tlb_array[0].tme_m68k_tlb_token; + tlb_set_info.tme_bus_tlb_set_info_token_stride = sizeof(struct tme_m68k_tlb); + tlb_set_info.tme_bus_tlb_set_info_token_count = TME_ARRAY_ELS(ic->_tme_m68k_tlb_array); + tlb_set_info.tme_bus_tlb_set_info_bus_context = &ic->_tme_m68k_bus_context; + + /* initialize the TLBs in the set: */ + for (tlb_i = 0; tlb_i < TME_ARRAY_ELS(ic->_tme_m68k_tlb_array); tlb_i++) { + tlb = &ic->_tme_m68k_tlb_array[tlb_i]; + + /* initialize this token: */ + tme_token_init(&tlb->tme_m68k_tlb_token); + + /* connect this token with this TLB: */ + tlb->tme_m68k_tlb_bus_tlb.tme_bus_tlb_token = &tlb->tme_m68k_tlb_token; + } + + /* add the TLB set: */ + rc = ((*ic->_tme_m68k_bus_connection->tme_m68k_bus_connection.tme_bus_tlb_set_add) + (&ic->_tme_m68k_bus_connection->tme_m68k_bus_connection, + &tlb_set_info)); + assert (rc == TME_OK); } /* NB: the machine needs to issue a reset to bring the CPU out of halt. */ @@ -379,6 +454,7 @@ static int _tme_m68k_connection_break(struct tme_connection *conn, unsigned int state) { abort(); + return (0); } /* this makes new connection sides: */ @@ -408,11 +484,12 @@ _tme_m68k_connections_new(struct tme_ele /* fill in the generic bus connection: */ conn_bus->tme_bus_signal = _tme_m68k_bus_signal; - conn_bus->tme_bus_tlb_set_allocate = NULL; + conn_bus->tme_bus_tlb_set_add = NULL; /* full in the m68k bus connection: */ conn_m68k->tme_m68k_bus_interrupt = _tme_m68k_bus_interrupt; conn_m68k->tme_m68k_bus_tlb_fill = NULL; + conn_m68k->tme_m68k_bus_m6888x_enable = _tme_m6888x_enable; /* add this connection to the set of possibilities: */ *_conns = conn; @@ -430,7 +507,7 @@ _tme_m68k_connections_new(struct tme_ele /* fill in the generic bus connection: */ conn_bus->tme_bus_signal = _tme_m68k_bus_signal; - conn_bus->tme_bus_tlb_set_allocate = NULL; + conn_bus->tme_bus_tlb_set_add = NULL; conn_bus->tme_bus_tlb_fill = NULL; /* add this connection to the set of possibilities: */ @@ -445,15 +522,47 @@ int tme_m68k_new(struct tme_m68k *ic, const char * const *args, const void *extra, char **_output) { struct tme_element *element; + int arg_i; + int usage; - /* we take no arguments: */ - if (args[1] != NULL) { - tme_output_append_error(_output, - "%s %s, %s %s", - args[1], - _("unexpected"), + /* check our arguments: */ + arg_i = 1; + usage = FALSE; + for (;;) { + + if (0) { + + } + + /* if we've run out of arguments: */ + else if (args[arg_i + 0] == NULL) { + break; + } + + /* this is either a bad argument or an FPU argument: */ + else { + + /* if this is not an FPU argument: */ + if (!tme_m68k_fpu_new(ic, args, &arg_i, &usage, _output)) { + tme_output_append_error(_output, + "%s %s, ", + args[arg_i], + _("unexpected")); + usage = TRUE; + } + + if (usage) { + break; + } + } + } + + if (usage) { + tme_output_append_error(_output, + "%s %s", _("usage:"), args[0]); + tme_m68k_fpu_usage(_output); tme_free(ic); return (EINVAL); } @@ -464,13 +573,13 @@ tme_m68k_new(struct tme_m68k *ic, const /* dispatch on the type: */ switch (ic->tme_m68k_type) { case TME_M68K_M68000: - ic->_tme_m68k_bus_16bit = TRUE; + ic->_tme_m68k_bus_16bit = 1; break; case TME_M68K_M68010: - ic->_tme_m68k_bus_16bit = TRUE; + ic->_tme_m68k_bus_16bit = 1; break; case TME_M68K_M68020: - ic->_tme_m68k_bus_16bit = FALSE; + ic->_tme_m68k_bus_16bit = 0; break; default: abort(); @@ -486,7 +595,26 @@ tme_m68k_new(struct tme_m68k *ic, const /* calculate the instruction burst size: */ /* XXX TBD: */ - ic->_tme_m68k_instruction_burst = 20; + ic->_tme_m68k_instruction_burst = 200; + ic->_tme_m68k_instruction_burst_remaining + = ic->_tme_m68k_instruction_burst; + + /* set the status register T bits mask: */ + ic->_tme_m68k_sr_mask_t + = (TME_M68K_FLAG_T1 + | ((ic->tme_m68k_type >= TME_M68K_M68020) + * TME_M68K_FLAG_T0)); + + /* initialize the small immediates: */ + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_ZERO) = 0; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_ONE) = 1; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_TWO) = 2; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_THREE) = 3; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_FOUR) = 4; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_FIVE) = 5; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_SIX) = 6; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_SEVEN) = 7; + ic->tme_m68k_ireg_uint32(TME_M68K_IREG_EIGHT) = 8; /* force the processor to be halted: */ ic->_tme_m68k_mode = TME_M68K_MODE_HALT; @@ -506,11 +634,14 @@ tme_m68k_do_reset(struct tme_m68k *ic) /* force the VBR to zero: */ ic->tme_m68k_ireg_vbr = 0; + /* clear the E and F bits in the CACR: */ + ic->tme_m68k_ireg_cacr = 0; + /* force supervisor mode, interrupts disabled: */ tme_m68k_change_sr(ic, TME_M68K_FLAG_S | (7 << 8)); /* load the initial SSP and PC: */ - ic->_tme_m68k_ea_function_code = TME_M68K_FC_SD; + ic->_tme_m68k_ea_function_code = TME_M68K_FC_SP; ic->_tme_m68k_ea_address = 0; tme_m68k_read_mem32(ic, TME_M68K_IREG_A7); ic->_tme_m68k_ea_address += sizeof(ic->tme_m68k_ireg_a7); @@ -519,6 +650,9 @@ tme_m68k_do_reset(struct tme_m68k *ic) /* clear all exceptions: */ ic->_tme_m68k_exceptions = 0; + /* reset the FPU: */ + tme_m68k_fpu_reset(ic); + /* start execution: */ ic->_tme_m68k_mode = TME_M68K_MODE_EXECUTION; TME_M68K_SEQUENCE_START; @@ -530,18 +664,32 @@ tme_m68k_do_reset(struct tme_m68k *ic) int tme_m68k_go_slow(const struct tme_m68k *ic) { - struct tme_m68k_tlb *tlb; + tme_bus_context_t bus_context; + const struct tme_m68k_tlb *tlb; tme_uint32_t linear_pc; + const tme_shared tme_uint8_t *emulator_load; + const tme_shared tme_uint8_t *emulator_load_last; - tlb = TME_ATOMIC_READ(struct tme_m68k_tlb *, ic->_tme_m68k_itlb); + bus_context = ic->_tme_m68k_bus_context; + tlb = &ic->_tme_m68k_itlb; + emulator_load = tlb->tme_m68k_tlb_emulator_off_read; + emulator_load_last = emulator_load; + if (emulator_load != TME_EMULATOR_OFF_UNDEF) { + emulator_load += (tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_first; + emulator_load_last += (tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_last; + assert (emulator_load <= emulator_load_last); + } linear_pc = ic->tme_m68k_ireg_pc; return ( /* the ITLB entry must support reads from emulator memory: */ - !TME_M68K_TLB_OK_FAST_READ(tlb, - TME_M68K_FUNCTION_CODE_PROGRAM(ic), - linear_pc, - linear_pc) + tme_m68k_tlb_is_invalid(tlb) + || tlb->tme_m68k_tlb_bus_context != bus_context + || (tlb->tme_m68k_tlb_function_codes_mask + & TME_BIT(TME_M68K_FUNCTION_CODE_PROGRAM(ic))) == 0 + || linear_pc < (tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_first + || linear_pc > (tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_last + || tlb->tme_m68k_tlb_emulator_off_read == TME_EMULATOR_OFF_UNDEF /* the ITLB emulator memory must be 32-bit aligned for the benefit of the fast instruction word fetch macros, so @@ -550,17 +698,51 @@ tme_m68k_go_slow(const struct tme_m68k * || (((unsigned long) tlb->tme_m68k_tlb_emulator_off_read) & (sizeof(tme_uint32_t) - 1)) + /* the ITLB emulator memory must not be so low that the + first valid pointer minus one, or the last valid pointer + minus (sizeof(tme_uint32_t) - 1), wraps around, nor so + high that the last valid pointer, plus one, wraps around: */ + /* NB: this enables the fast instruction word fetch macros + to simply fetch 16 and 32 bit values until fetch_fast_next + is greater than ic->_tme_m68k_insn_fetch_fast_last, and + not have to do any pointer math or ever check for pointer + wrapping: */ + || ((emulator_load + - 1) + >= emulator_load) + || ((emulator_load_last + - (sizeof(tme_uint32_t) - 1)) + >= emulator_load_last) + || ((emulator_load_last + + 1) + <= emulator_load_last) + /* the linear PC must be 16-bit aligned: */ || (linear_pc & 1) /* there must be no tracing: */ - || TME_M68K_FLAG_T(ic->tme_m68k_ireg_sr) != 0); + || (ic->tme_m68k_ireg_sr & ic->_tme_m68k_sr_mask_t) != 0); } /* this redispatches: */ void tme_m68k_redispatch(struct tme_m68k *ic) { + struct tme_m68k_tlb *tlb; + + /* if we have a busy fast instruction TLB entry: */ + tlb = ic->_tme_m68k_insn_fetch_fast_itlb; + if (__tme_predict_true(tlb != NULL)) { + + /* unbusy and forget the fast instruction TLB entry: */ + tme_m68k_tlb_unbusy(tlb); + ic->_tme_m68k_insn_fetch_fast_itlb = NULL; + } + + /* do the redispatch: */ +#ifdef _TME_M68K_STATS + ic->tme_m68k_stats.tme_m68k_stats_redispatches++; +#endif /* _TME_M68K_STATS */ longjmp(ic->_tme_m68k_dispatcher, 1); } @@ -574,6 +756,16 @@ tme_m68k_tlb_fill(struct tme_m68k *ic, s tme_uint32_t external_address; struct tme_bus_tlb tlb_internal; +#ifdef _TME_M68K_STATS + if (function_code == TME_M68K_FC_UP + || function_code == TME_M68K_FC_SP) { + ic->tme_m68k_stats.tme_m68k_stats_itlb_fill++; + } + else { + ic->tme_m68k_stats.tme_m68k_stats_dtlb_fill++; + } +#endif /* _TME_M68K_STATS */ + /* when emulating a CPU with a 16-bit bus, only 24 bits of address are external: */ external_address = linear_address; @@ -581,6 +773,15 @@ tme_m68k_tlb_fill(struct tme_m68k *ic, s external_address &= 0x00ffffff; } + /* unbusy the TLB entry: */ + tme_m68k_tlb_unbusy(tlb); + + /* clear any invalid token: */ + tme_token_invalid_clear(&tlb->tme_m68k_tlb_token); + + /* unlock for the callout: */ + tme_m68k_callout_unlock(ic); + /* fill the TLB entry: */ (*ic->_tme_m68k_bus_connection->tme_m68k_bus_tlb_fill) (ic->_tme_m68k_bus_connection, tlb, @@ -588,14 +789,23 @@ tme_m68k_tlb_fill(struct tme_m68k *ic, s external_address, cycles); + /* relock after the callout: */ + tme_m68k_callout_relock(ic); + + /* set the context on the TLB entry: */ + tlb->tme_m68k_tlb_bus_context = ic->_tme_m68k_bus_context; + + /* rebusy the TLB entry: */ + tme_m68k_tlb_busy(tlb); + /* if this code isn't 32-bit clean, we have to deal: */ if (external_address != linear_address) { - TME_ATOMIC_WRITE(tme_bus_addr_t, tlb_internal.tme_bus_tlb_addr_first, - TME_ATOMIC_READ(tme_bus_addr_t, tlb->tme_m68k_tlb_linear_first) - | (linear_address ^ external_address)); - TME_ATOMIC_WRITE(tme_bus_addr_t, tlb_internal.tme_bus_tlb_addr_last, - TME_ATOMIC_READ(tme_bus_addr_t, tlb->tme_m68k_tlb_linear_last) - | (linear_address ^ external_address)); + tlb_internal.tme_bus_tlb_addr_first + = (((tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_first) + | (linear_address ^ external_address)); + tlb_internal.tme_bus_tlb_addr_last + = (((tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_last) + | (linear_address ^ external_address)); tlb_internal.tme_bus_tlb_cycles_ok = tlb->tme_m68k_tlb_bus_tlb.tme_bus_tlb_cycles_ok; tme_bus_tlb_map(&tlb->tme_m68k_tlb_bus_tlb, external_address, &tlb_internal, linear_address); @@ -610,9 +820,9 @@ tme_m68k_exception(struct tme_m68k *ic, /* if the set of new exceptions includes a group zero exception: */ if (new_exceptions & - (TME_M68K_EXCEPTION_GROUP0_RESET - | TME_M68K_EXCEPTION_GROUP0_AERR - | TME_M68K_EXCEPTION_GROUP0_BERR)) { + (TME_M68K_EXCEPTION_RESET + | TME_M68K_EXCEPTION_AERR + | TME_M68K_EXCEPTION_BERR)) { /* there must be only one exception - you cannot trigger a group 0 exception simultaneously with any other group 0, 1, or 2 @@ -620,7 +830,7 @@ tme_m68k_exception(struct tme_m68k *ic, assert((new_exceptions & (new_exceptions - 1)) == 0); /* if this is a reset exception, it clears all other exceptions: */ - if (new_exceptions == TME_M68K_EXCEPTION_GROUP0_RESET) { + if (new_exceptions == TME_M68K_EXCEPTION_RESET) { ic->_tme_m68k_exceptions = 0; } @@ -628,9 +838,12 @@ tme_m68k_exception(struct tme_m68k *ic, already processing a group 0 exception, this is a double fault, and the processor enters the halted state: */ else if (ic->_tme_m68k_exceptions & - (TME_M68K_EXCEPTION_GROUP0_RESET - | TME_M68K_EXCEPTION_GROUP0_AERR - | TME_M68K_EXCEPTION_GROUP0_BERR)) { + (TME_M68K_EXCEPTION_RESET + | TME_M68K_EXCEPTION_AERR + | TME_M68K_EXCEPTION_BERR)) { + tme_log(TME_M68K_LOG_HANDLE(ic), 0, TME_OK, + (TME_M68K_LOG_HANDLE(ic), + _("double fault, processor halted"))); ic->_tme_m68k_mode = TME_M68K_MODE_HALT; TME_M68K_SEQUENCE_START; tme_m68k_redispatch(ic); @@ -653,33 +866,39 @@ tme_m68k_exception(struct tme_m68k *ic, void tme_m68k_change_sr(struct tme_m68k *ic, tme_uint16_t sr) { + tme_uint16_t flags_mode; + + /* only recognize the M bit on a 68020 or better: */ + flags_mode = (TME_M68K_FLAG_S + | ((ic->tme_m68k_type >= TME_M68K_M68020) + * TME_M68K_FLAG_M)); /* save %a7 in the proper stack pointer control register: */ - switch (ic->tme_m68k_ireg_sr & (TME_M68K_FLAG_S | TME_M68K_FLAG_M)) { + switch (ic->tme_m68k_ireg_sr & flags_mode) { case 0: case TME_M68K_FLAG_M: ic->tme_m68k_ireg_usp = ic->tme_m68k_ireg_a7; break; case TME_M68K_FLAG_S: - ic->tme_m68k_ireg_msp = ic->tme_m68k_ireg_a7; + ic->tme_m68k_ireg_isp = ic->tme_m68k_ireg_a7; break; case (TME_M68K_FLAG_S | TME_M68K_FLAG_M): - ic->tme_m68k_ireg_isp = ic->tme_m68k_ireg_a7; + ic->tme_m68k_ireg_msp = ic->tme_m68k_ireg_a7; break; } /* load %a7 from the proper stack pointer control register: */ ic->tme_m68k_ireg_sr = sr; - switch (ic->tme_m68k_ireg_sr & (TME_M68K_FLAG_S | TME_M68K_FLAG_M)) { + switch (ic->tme_m68k_ireg_sr & flags_mode) { case 0: case TME_M68K_FLAG_M: ic->tme_m68k_ireg_a7 = ic->tme_m68k_ireg_usp; break; case TME_M68K_FLAG_S: - ic->tme_m68k_ireg_a7 = ic->tme_m68k_ireg_msp; + ic->tme_m68k_ireg_a7 = ic->tme_m68k_ireg_isp; break; case (TME_M68K_FLAG_S | TME_M68K_FLAG_M): - ic->tme_m68k_ireg_a7 = ic->tme_m68k_ireg_isp; + ic->tme_m68k_ireg_a7 = ic->tme_m68k_ireg_msp; break; } } @@ -694,7 +913,7 @@ tme_m68k_exception_process_start(struct T, and update I: */ if (!TME_M68K_SEQUENCE_RESTARTING) { ic->tme_m68k_ireg_shadow_sr = ic->tme_m68k_ireg_sr; - sr = (ic->tme_m68k_ireg_sr | TME_M68K_FLAG_S) ^ TME_M68K_FLAG_T(ic->tme_m68k_ireg_sr); + sr = (ic->tme_m68k_ireg_sr | TME_M68K_FLAG_S) & ~ic->_tme_m68k_sr_mask_t; if (ipl > TME_M68K_IPL_NONE) { assert(ipl == TME_M68K_IPL_NMI || ipl > TME_M68K_FLAG_IPM(sr)); @@ -724,51 +943,176 @@ tme_m68k_exception_process_finish(struct /* do a bus cycle to read the vector into the program counter: */ if (!TME_M68K_SEQUENCE_RESTARTING) { - ic->_tme_m68k_ea_function_code = TME_M68K_FC_SD; /* XXX is this right? */ + ic->_tme_m68k_ea_function_code = TME_M68K_FC_SD; ic->_tme_m68k_ea_address = ic->tme_m68k_ireg_vbr + vector_offset; } tme_m68k_read_mem32(ic, TME_M68K_IREG_PC); } -/* common m68k exception processing: */ +/* common m68000 and m68010 exception processing: */ void -tme_m68k_exception_process(struct tme_m68k *ic) +tme_m68000_exception_process(struct tme_m68k *ic) { tme_uint32_t exceptions; + tme_uint8_t vector; /* get the set of exceptions. we must have no group 0 exceptions: */ exceptions = ic->_tme_m68k_exceptions; - assert((exceptions & (TME_M68K_EXCEPTION_GROUP0_RESET - | TME_M68K_EXCEPTION_GROUP0_AERR - | TME_M68K_EXCEPTION_GROUP0_BERR)) == 0); + assert((exceptions & (TME_M68K_EXCEPTION_RESET + | TME_M68K_EXCEPTION_AERR + | TME_M68K_EXCEPTION_BERR)) == 0); /* these if statements are ordered to implement the priority relationship between the different exceptions as outlined in the 68000 user's manual (pp 93 in my copy): */ - if (TME_M68K_EXCEPTION_IS_GROUP2(exceptions)) { + if (TME_M68K_EXCEPTION_IS_INST(exceptions)) { tme_m68k_exception_process_start(ic, 0); - tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_EXCEPTION_IS_GROUP2(exceptions)); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_EXCEPTION_IS_INST(exceptions)); } - if (exceptions & TME_M68K_EXCEPTION_GROUP1_TRACE) { + if (exceptions & TME_M68K_EXCEPTION_TRACE) { tme_m68k_exception_process_start(ic, 0); - tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, 0x09); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_VECTOR_TRACE); } - if (TME_M68K_EXCEPTION_IS_GROUP1_INT(exceptions)) { - tme_m68k_exception_process_start(ic, TME_M68K_EXCEPTION_IS_GROUP1_INT(exceptions)); - tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_EXCEPTION_GROUP1_INT_VEC(exceptions)); + if (TME_M68K_EXCEPTION_IS_INT(exceptions)) { + tme_m68k_exception_process_start(ic, TME_M68K_EXCEPTION_IS_INT(exceptions)); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_EXCEPTION_INT_VEC(exceptions)); } - if (exceptions & TME_M68K_EXCEPTION_GROUP1_ILL) { + if (exceptions & TME_M68K_EXCEPTION_ILL) { + if (TME_FIELD_EXTRACTU(ic->_tme_m68k_insn_opcode, 12, 4) == 0xa) { + vector = TME_M68K_VECTOR_LINE_A; + } + else if (TME_FIELD_EXTRACTU(ic->_tme_m68k_insn_opcode, 12, 4) == 0xf) { + vector = TME_M68K_VECTOR_LINE_F; + } + else { + vector = TME_M68K_VECTOR_ILL; + } tme_m68k_exception_process_start(ic, 0); - tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, 0x04); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, vector); } - if (exceptions & TME_M68K_EXCEPTION_GROUP1_PRIV) { + if (exceptions & TME_M68K_EXCEPTION_PRIV) { tme_m68k_exception_process_start(ic, 0); - tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, 0x08); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_VECTOR_TRACE); + } + + /* we have processed all exceptions - resume execution: */ + ic->_tme_m68k_exceptions = 0; + ic->_tme_m68k_mode = TME_M68K_MODE_EXECUTION; + TME_M68K_SEQUENCE_START; + tme_m68k_redispatch(ic); +} + +/* common m68020 and later exception processing: */ +void +tme_m68020_exception_process(struct tme_m68k *ic) +{ + tme_uint32_t exceptions; + tme_uint8_t vector; + struct { + tme_uint16_t tme_m68k_fmt1_sr; + tme_uint16_t tme_m68k_fmt1_pc_hi; + tme_uint16_t tme_m68k_fmt1_pc_lo; + tme_uint16_t tme_m68k_fmt1_vector_offset; + } fmt1; + + /* get the set of exceptions. we must have no group 0 or 1 + exceptions: */ + exceptions = ic->_tme_m68k_exceptions; + assert((exceptions & (TME_M68K_EXCEPTION_RESET + | TME_M68K_EXCEPTION_AERR + | TME_M68K_EXCEPTION_BERR)) == 0); + + /* these if statements are ordered to implement the priority + relationship between the different exceptions as outlined in + the 68020 user's manual (pp 144 in my copy): */ + + /* group 2 exceptions: */ + if (TME_M68K_EXCEPTION_IS_INST(exceptions)) { + tme_m68k_exception_process_start(ic, 0); + + /* get the vector number: */ + vector = TME_M68K_EXCEPTION_IS_INST(exceptions); + + /* of the group 2 exceptions, only the Format Error and TRAP #N + exceptions generate a format 0 stack frame. the RTE mode code + and the TRAP instruction code are expected to have left + ic->tme_m68k_ireg_pc as the PC they want stacked: */ + if (vector == TME_M68K_VECTOR_FORMAT + || (TME_M68K_VECTOR_TRAP_0 <= vector + && vector < (TME_M68K_VECTOR_TRAP_0 + 16))) { + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, vector); + } + + /* all other group 2 exceptions generate a format 2 stack frame. + all code that can signal this exception is expected to have + left ic->tme_m68k_ireg_pc *and* ic->tme_m68k_ireg_pc_last as + the PCs they want stacked: */ + else { + + /* stack the program counter of the instruction that caused the exception: */ + tme_m68k_push32(ic, ic->tme_m68k_ireg_pc_last); + + /* finish with a format 2 stack frame: */ + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_2, vector); + } + } + + /* group 3 exceptions: */ + if (exceptions & TME_M68K_EXCEPTION_ILL) { + if (TME_FIELD_EXTRACTU(ic->_tme_m68k_insn_opcode, 12, 4) == 0xa) { + vector = TME_M68K_VECTOR_LINE_A; + } + else if (TME_FIELD_EXTRACTU(ic->_tme_m68k_insn_opcode, 12, 4) == 0xf) { + vector = TME_M68K_VECTOR_LINE_F; + } + else { + vector = TME_M68K_VECTOR_ILL; + } + tme_m68k_exception_process_start(ic, 0); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, vector); + } + if (exceptions & TME_M68K_EXCEPTION_PRIV) { + tme_m68k_exception_process_start(ic, 0); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_VECTOR_PRIV); + } + + /* group 4.1 exceptions: */ + if (exceptions & TME_M68K_EXCEPTION_TRACE) { + tme_m68k_exception_process_start(ic, 0); + tme_m68k_push32(ic, ic->tme_m68k_ireg_pc_last); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_2, TME_M68K_VECTOR_TRACE); + } + + /* group 4.2 exceptions: */ + if (TME_M68K_EXCEPTION_IS_INT(exceptions)) { + tme_m68k_exception_process_start(ic, TME_M68K_EXCEPTION_IS_INT(exceptions)); + tme_m68k_exception_process_finish(ic, TME_M68K_FORMAT_0, TME_M68K_EXCEPTION_INT_VEC(exceptions)); + + /* if the M-bit is set: */ + if (ic->tme_m68k_ireg_sr & TME_M68K_FLAG_M) { + + /* make the throwaway four-word stack frame (format 1): */ + fmt1.tme_m68k_fmt1_vector_offset = tme_htobe_u16((TME_M68K_FORMAT_1 << 12) | (TME_M68K_EXCEPTION_INT_VEC(exceptions) << 2)); + fmt1.tme_m68k_fmt1_pc_lo = tme_htobe_u16((ic->tme_m68k_ireg_pc >> 0) & 0xffff); + fmt1.tme_m68k_fmt1_pc_hi = tme_htobe_u16((ic->tme_m68k_ireg_pc >> 16) & 0xffff); + fmt1.tme_m68k_fmt1_sr = tme_htobe_u16(ic->tme_m68k_ireg_sr); + + /* store the throwaway four-word stack frame on the interrupt stack: */ + if (!TME_M68K_SEQUENCE_RESTARTING) { + ic->_tme_m68k_ea_function_code = TME_M68K_FC_SD; + ic->_tme_m68k_ea_address = ic->tme_m68k_ireg_isp - sizeof(fmt1); + } + tme_m68k_write_mem(ic, (tme_uint8_t *) &fmt1, sizeof(fmt1)); + + /* move to the interrupt stack: */ + ic->tme_m68k_ireg_isp -= sizeof(fmt1); + tme_m68k_change_sr(ic, ic->tme_m68k_ireg_sr & ~TME_M68K_FLAG_M); + } } /* we have processed all exceptions - resume execution: */ @@ -784,21 +1128,29 @@ tme_m68k_rte_start(struct tme_m68k *ic) { /* set up to read from the stack frame: */ - ic->_tme_m68k_ea_function_code = TME_M68K_FC_SD; - ic->_tme_m68k_ea_address = ic->tme_m68k_ireg_a7; + if (!TME_M68K_SEQUENCE_RESTARTING) { + ic->_tme_m68k_ea_function_code = TME_M68K_FC_SD; + ic->_tme_m68k_ea_address = ic->tme_m68k_ireg_a7; + } /* read the stacked status register: */ tme_m68k_read_mem16(ic, TME_M68K_IREG_SHADOW_SR); - ic->_tme_m68k_ea_address += sizeof(ic->tme_m68k_ireg_shadow_sr); + if (!TME_M68K_SEQUENCE_RESTARTING) { + ic->_tme_m68k_ea_address += sizeof(ic->tme_m68k_ireg_shadow_sr); + } /* read the stacked PC: */ tme_m68k_read_mem32(ic, TME_M68K_IREG_PC_NEXT); - ic->_tme_m68k_ea_address += sizeof(ic->tme_m68k_ireg_pc_next); + if (!TME_M68K_SEQUENCE_RESTARTING) { + ic->_tme_m68k_ea_address += sizeof(ic->tme_m68k_ireg_pc_next); + } /* read the stacked format/offset word, unless this is a 68000: */ if (ic->tme_m68k_type != TME_M68K_M68000) { tme_m68k_read_mem16(ic, TME_M68K_IREG_FORMAT_OFFSET); - ic->_tme_m68k_ea_address += sizeof(ic->tme_m68k_ireg_format_offset); + if (!TME_M68K_SEQUENCE_RESTARTING) { + ic->_tme_m68k_ea_address += sizeof(ic->tme_m68k_ireg_format_offset); + } } else { ic->tme_m68k_ireg_format_offset = 0; @@ -840,7 +1192,7 @@ tme_m68k_rte_finish(struct tme_m68k *ic, /* this stores the group 0 sequence into a region of host memory. this is used when preparing the state information to be stored on the stack for a bus or address error: */ -int +unsigned int tme_m68k_sequence_empty(const struct tme_m68k *ic, tme_uint8_t *raw, unsigned int raw_avail) { const struct _tme_m68k_sequence *sequence; @@ -886,7 +1238,7 @@ tme_m68k_sequence_empty(const struct tme /* this restores the group 0 sequence from a region of host memory. this is used when reading the state information stored on the stack for a bus or address error: */ -int +unsigned int tme_m68k_sequence_fill(struct tme_m68k *ic, const tme_uint8_t *raw, unsigned int raw_avail) { struct _tme_m68k_sequence *sequence; @@ -899,7 +1251,7 @@ tme_m68k_sequence_fill(struct tme_m68k * /* we used 8 bits for the mode (2 bits) and flags (6 bits): */ raw_used += sizeof(tme_uint8_t); if (raw_avail < raw_used) { - return (-1); + return (0); } sequence->_tme_m68k_sequence_mode = *raw >> 6; sequence->_tme_m68k_sequence_mode_flags = (*(raw++) & (TME_BIT(6) - 1)); @@ -908,7 +1260,7 @@ tme_m68k_sequence_fill(struct tme_m68k * (12 bits) and already-transferred byte count (4 bits): */ raw_used += sizeof(tme_uint16_t); if (raw_avail < raw_used) { - return (-1); + return (0); } sequence->_tme_m68k_sequence_transfer_faulted = (((tme_uint16_t) raw[0]) << 4) @@ -920,7 +1272,7 @@ tme_m68k_sequence_fill(struct tme_m68k * /* we used sizeof(_tme_m68k_sequence_uid) bytes for the sequence UID: */ raw_used += sizeof(sequence->_tme_m68k_sequence_uid); if (raw_avail < raw_used) { - return (-1); + return (0); } memcpy(&sequence->_tme_m68k_sequence_uid, raw, @@ -935,239 +1287,740 @@ tme_m68k_sequence_fill(struct tme_m68k * return (raw_used); } -/* this transfers the instruction buffer to or from a region of host - memory. unlike the raw region in host memory, where the 16- and - 32-bit parts of the instruction are contiguous and therefore - potentially misaligned, in the instruction buffer these instruction - parts are all properly aligned. given the instruction buffer, a - contiguous host memory buffer, a count of instruction bytes and the - sizes of the instruction fetches, this transfers from one buffer to - the other. - - this is used to fill the instruction buffer when we're restoring - our state from an exception stack or when we fault anywhere inside - the fast executor, and it's used to empty the instruction buffer - into an exception stack when we fault: */ -int -tme_m68k_insn_buffer_xfer(struct tme_m68k *ic, tme_uint8_t *raw, unsigned int raw_avail, int what) +/* this empties the instruction buffer into an exception frame: */ +unsigned int +tme_m68k_insn_buffer_empty(const struct tme_m68k *ic, tme_uint8_t *raw, unsigned int raw_avail) { - int fill, sanity_assert; - tme_uint16_t fetch_total; - tme_uint16_t fetch_sizes; - unsigned int fetch_sizes_bits; - unsigned int insn_buffer_off, fetch_off, fetch_size, resid; -#define _FETCH_SIZES_BITS (8 * sizeof(ic->_tme_m68k_insn_buffer_fetch_sizes)) -#define _FETCH_SIZE_BIT (1 << (_FETCH_SIZES_BITS - 1)) -#define _FETCH_SANITY(e) \ -do { \ - if (sanity_assert) \ - assert(e); \ - else if (!(e)) \ - return (-1); \ -} while (/* CONSTCOND */ 0) - - /* if what is zero, we faulted somewhere inside the fast executor - and we need to fill the instruction buffer from raw host memory: */ - if (what == 0) { - fill = TRUE; - sanity_assert = TRUE; - - /* the fetch total and sizes are in the state: */ - fetch_total = ic->_tme_m68k_insn_buffer_fetch_total; - fetch_sizes = ic->_tme_m68k_insn_buffer_fetch_sizes; - raw_avail = fetch_total; - } - - /* else, if what is one, we're emptying the instruction buffer into - an exception frame: */ - else if (what == 1) { - fill = FALSE; - sanity_assert = TRUE; - - /* the fetch total and sizes are in the state: */ - fetch_total = ic->_tme_m68k_insn_buffer_fetch_total; - fetch_sizes = ic->_tme_m68k_insn_buffer_fetch_sizes; - - /* the first word we place into the exception frame is - (fetch_sizes << 4) | (fetch_total >> 1): */ - _FETCH_SANITY(raw_avail >= sizeof(tme_uint16_t)); - raw[1] = (fetch_sizes << 4) | (fetch_total >> 1); - raw[0] = (fetch_sizes >> 4); - raw += sizeof(tme_uint16_t); - raw_avail -= sizeof(tme_uint16_t); - } + unsigned int fetch_total; + + /* get the total number of bytes in the instruction buffer: */ + fetch_total = ic->_tme_m68k_insn_fetch_slow_count_total; - /* otherwise, we're filling the instruction buffer from an exception - frame: */ - else { - fill = TRUE; - sanity_assert = FALSE; + /* save the total number of bytes fetched into the instruction + buffer, the number of bytes in the instruction buffer fetched by + the fast executor, and then the instruction buffer itself: */ + assert ((fetch_total % sizeof(tme_uint16_t)) == 0 + && fetch_total <= (TME_M68K_INSN_WORDS_MAX * sizeof(tme_uint16_t))); + assert ((ic->_tme_m68k_insn_fetch_slow_count_fast % sizeof(tme_uint16_t)) == 0 + && ic->_tme_m68k_insn_fetch_slow_count_fast <= fetch_total); + assert (raw_avail >= (sizeof(tme_uint8_t) + sizeof(tme_uint8_t) + fetch_total)); + raw[0] = fetch_total; + raw[1] = ic->_tme_m68k_insn_fetch_slow_count_fast; + memcpy(raw + 2, + &ic->_tme_m68k_insn_fetch_buffer[0], + fetch_total); + + /* return the number of bytes we put in an exception frame: */ + return (sizeof(tme_uint8_t) + sizeof(tme_uint8_t) + fetch_total); +} - /* this function previously emptied the instruction buffer into - this exception frame, and the first big-endian word placed - there is (fetch_sizes << 4) | (fetch_total >> 1): */ - _FETCH_SANITY(raw_avail >= sizeof(tme_uint16_t)); - fetch_total = (raw[1] & 0x0f) << 1; - fetch_sizes = (raw[0] << 4) | (raw[1] >> 4); - raw += sizeof(tme_uint16_t); - raw_avail -= sizeof(tme_uint16_t); - } - - /* fetch_total must be even, because we only fetch some multiple of - 16-bit words: */ - _FETCH_SANITY((fetch_total & (sizeof(tme_uint16_t) - 1)) == 0); - - /* fetch_sizes is a bitmask, with a one bit representing a 32-bit - fetch and a zero bit representing a 16-bit fetch, and the least - significant bit is the *last* fetch performed. count the number - of significant bits in fetch_sizes and confirm that it makes - sense with fetch_total: */ - fetch_sizes_bits = 0; - for (fetch_off = 0; fetch_off < fetch_total; ) { - _FETCH_SANITY(fetch_sizes_bits < _FETCH_SIZES_BITS); - fetch_off += ((fetch_sizes & (1 << fetch_sizes_bits)) - /* a 32-bit fetch: */ - ? sizeof(tme_uint32_t) - /* a 16-bit fetch: */ - : sizeof(tme_uint16_t)); - fetch_sizes_bits++; - } - _FETCH_SANITY(fetch_off == fetch_total); - - /* we must have enough raw space available: */ - _FETCH_SANITY(raw_avail >= fetch_total); - - /* shift fetch_sizes up so the bit for the first transfer - is the most significant bit: */ - fetch_sizes <<= (_FETCH_SIZES_BITS - fetch_sizes_bits); - - /* now fill or empty the instruction buffer: */ - insn_buffer_off = 0; - for (fetch_off = 0; fetch_off < fetch_total; ) { - - /* get the size of this fetch: */ - fetch_size = ((fetch_sizes & _FETCH_SIZE_BIT) - /* a 32-bit fetch: */ - ? sizeof(tme_uint32_t) - /* a 16-bit fetch: */ - : sizeof(tme_uint16_t)); - fetch_sizes <<= 1; - - /* do one transfer. the insn buffer is kept in host byte - order, like the internal registers are: */ - insn_buffer_off = TME_ALIGN(insn_buffer_off, fetch_size); - if (fill) { - for (resid = fetch_size; resid-- > 0; ) { - ic->_tme_m68k_insn_buffer[(insn_buffer_off - + (resid -#ifndef WORDS_BIGENDIAN - ^ (fetch_size - 1) -#endif /* !WORDS_BIGENDIAN */ - ))] - = raw[fetch_off + resid]; - } - } - else { - for (resid = fetch_size; resid-- > 0; ) { - raw[fetch_off + resid] = - ic->_tme_m68k_insn_buffer[(insn_buffer_off - + (resid -#ifndef WORDS_BIGENDIAN - ^ (fetch_size - 1) -#endif /* !WORDS_BIGENDIAN */ - ))]; - } - } - insn_buffer_off += fetch_size; - fetch_off += fetch_size; +/* this fills the instruction buffer from an exception frame: */ +unsigned int +tme_m68k_insn_buffer_fill(struct tme_m68k *ic, const tme_uint8_t *raw, unsigned int raw_avail) +{ + unsigned int fetch_total; + unsigned int fetch_fast; + + /* there must be at least two bytes in the exception frame: */ + if (raw_avail >= (sizeof(tme_uint8_t) + sizeof(tme_uint8_t))) { + + /* restore the total number of bytes fetched into the instruction + buffer, and the number of bytes in the instruction buffer + fetched by the fast executor: */ + fetch_total = raw[0]; + fetch_fast = raw[1]; + if ((fetch_total % sizeof(tme_uint16_t)) == 0 + && fetch_total <= (TME_M68K_INSN_WORDS_MAX * sizeof(tme_uint16_t)) + && (fetch_fast % sizeof(tme_uint16_t)) == 0 + && fetch_fast <= fetch_total + && raw_avail >= (sizeof(tme_uint8_t) + sizeof(tme_uint8_t) + fetch_total)) { - /* if we faulted somewhere in the fast executor, we need to - account for the instruction fetches in the group0 sequence: */ - if (what == 0) { - ic->_tme_m68k_group0_sequence._tme_m68k_sequence_transfer_next++; + /* restore the total number of bytes fetched into the instruction + buffer, the number of bytes in the instruction buffer fetched by + the fast executor, and then the instruction buffer itself: */ + ic->_tme_m68k_insn_fetch_slow_count_total = fetch_total; + ic->_tme_m68k_insn_fetch_slow_count_fast = fetch_fast; + memcpy(&ic->_tme_m68k_insn_fetch_buffer[0], + raw + 2, + fetch_total); + + /* return the number of bytes restored from the exception frame: */ + return ((sizeof(tme_uint8_t) + sizeof(tme_uint8_t) + fetch_total)); } } - /* NB: for total consistency we might want to store the fetch total - and sizes into the state when we're filling the instruction - buffer from an execution frame. however this isn't really needed - because the fetch pattern from the restarting slow executor - should be exactly the same and it doesn't care anyways. - if the user bashes the exception frame all bets are off. */ + /* this exception frame is invalid: */ + return (0); +} - /* return the number of bytes we put in an exception frame: */ - return (sizeof(tme_uint16_t) + fetch_total); +/* this unlocks data structures before a callout: */ +void +tme_m68k_callout_unlock(struct tme_m68k *ic) +{ + struct tme_m68k_tlb *tlb; + + assert ((ic->_tme_m68k_mode == TME_M68K_MODE_EXECUTION) + || (ic->_tme_m68k_insn_fetch_fast_itlb == NULL)); + + /* if we have a busy fast instruction TLB entry: */ + tlb = ic->_tme_m68k_insn_fetch_fast_itlb; + if (tlb != NULL) { + + /* unbusy the fast instruction TLB entry: */ + tme_m68k_tlb_unbusy(tlb); + } +} + +/* this relocks data structures after a callout: */ +void +tme_m68k_callout_relock(struct tme_m68k *ic) +{ + struct tme_m68k_tlb *tlb; + tme_bus_context_t bus_context; + struct tme_m68k_tlb *tlb_now; + + assert ((ic->_tme_m68k_mode == TME_M68K_MODE_EXECUTION) + || (ic->_tme_m68k_insn_fetch_fast_itlb == NULL)); + + /* if we have a busy fast instruction TLB entry: */ + tlb = ic->_tme_m68k_insn_fetch_fast_itlb; + if (tlb != NULL) { + + /* rebusy the fast instruction TLB entry: */ + tme_m68k_tlb_busy(tlb); + + /* get the bus context: */ + bus_context = ic->_tme_m68k_bus_context; + + /* get what should be our instruction TLB entry now: */ + tlb_now = &ic->_tme_m68k_itlb; + + /* if this instruction TLB entry has changed, is for the wrong + context, or is invalid: */ + if (__tme_predict_false(tlb_now != tlb + || tlb->tme_m68k_tlb_bus_context != bus_context + || tme_m68k_tlb_is_invalid(tlb))) { + + /* poison ic->_tme_m68k_insn_fetch_fast_last so the fast + instruction executor fetch macros will fail: */ + assert ((ic->_tme_m68k_insn_fetch_fast_next - 1) < ic->_tme_m68k_insn_fetch_fast_next); + ic->_tme_m68k_insn_fetch_fast_last = ic->_tme_m68k_insn_fetch_fast_next - 1; + } + } } /* this is the group 0 fault hook for the fast executor: */ void tme_m68k_group0_hook_fast(struct tme_m68k *ic) { - struct tme_m68k_tlb *tlb; - tme_uint8_t *raw; + unsigned int fetch_fast; - /* fill the instruction buffer and increase the transfer count - as if the slow executor had been doing the fetching: */ - tlb = TME_ATOMIC_READ(struct tme_m68k_tlb *, ic->_tme_m68k_itlb); - raw = tlb->tme_m68k_tlb_emulator_off_read + ic->tme_m68k_ireg_pc; - tme_m68k_insn_buffer_xfer(ic, raw, 0, 0); + /* get the number of bytes in the instruction buffer. they have all + been fetched by the fast executor: */ + /* NB: it's possible for this to be zero: */ + fetch_fast = (ic->_tme_m68k_insn_fetch_fast_next - ic->_tme_m68k_insn_fetch_fast_start); + assert ((fetch_fast % sizeof(tme_uint16_t)) == 0 + && fetch_fast <= (TME_M68K_INSN_WORDS_MAX * sizeof(tme_uint16_t))); + ic->_tme_m68k_insn_fetch_slow_count_total = fetch_fast; + ic->_tme_m68k_insn_fetch_slow_count_fast = fetch_fast; } -/* this starts a read/modify/write cycle. this works in conjunction - with the tme_m68k_readSIZE() and tme_m68k_writeSIZE() functions to - aggregate many bus transactions into a larger transaction: */ -struct tme_m68k_tlb * -tme_m68k_rmw_start(struct tme_m68k *ic) +/* this starts a read/modify/write cycle: */ +int +tme_m68k_rmw_start(struct tme_m68k *ic, + struct tme_m68k_rmw *rmw) { + tme_bus_context_t bus_context; + struct tme_m68k_tlb *tlbs_all[3]; + int tlbs_busy[2]; struct tme_m68k_tlb *tlb; + struct tme_m68k_tlb *tlb_use; + unsigned int tlb_i; + unsigned int address_i; + unsigned int address_i_fill; + tme_uint32_t address; + unsigned int address_cycles[2]; + unsigned int address_fills[2]; + tme_uint32_t *buffer_reg; + int supported; - /* if the user reran the cycle, do nothing: */ + /* if the user reran the cycle: */ if (TME_M68K_SEQUENCE_RESTARTING && (ic->_tme_m68k_group0_buffer_read_softrr > 0 || ic->_tme_m68k_group0_buffer_write_softrr > 0)) { - return (NULL); + + /* return failure: */ + return (-1); } /* we always rerun read/modify/write cycles in their entirety: */ ic->_tme_m68k_sequence._tme_m68k_sequence_transfer_faulted = ic->_tme_m68k_sequence._tme_m68k_sequence_transfer_next - 1; - /* get an applicable TLB entry: */ - tlb = TME_M68K_TLB_ENTRY(ic, ic->_tme_m68k_ea_function_code, ic->_tme_m68k_ea_address); + /* we only support tas and cas, which have one address, and cas2, + which has two addresses: */ + assert (rmw->tme_m68k_rmw_address_count == 1 + || rmw->tme_m68k_rmw_address_count == 2); + + /* get the context that we will use to index TLB entries for this + instruction. NB that this may be different from the context in + which the instruction eventually completes: */ + bus_context = ic->_tme_m68k_bus_context; + + /* assume that we will only consider one TLB entry, for the first + address: */ + tlbs_all[0] = TME_M68K_DTLB_ENTRY(ic, + bus_context, + ic->_tme_m68k_ea_function_code, + rmw->tme_m68k_rmw_addresses[0]); + tlbs_all[1] = NULL; + + /* if there are two addresses: */ + if (rmw->tme_m68k_rmw_address_count == 2) { + + /* we will consider another TLB entry for the second address: */ + tlbs_all[1] = TME_M68K_DTLB_ENTRY(ic, + bus_context, + ic->_tme_m68k_ea_function_code, + rmw->tme_m68k_rmw_addresses[1]); + + /* if the TLB entry for the second address collides with the TLB + entry for the first address: */ + if (tlbs_all[1] == tlbs_all[0]) { + + /* we will instead consider an alternate TLB entry for the + second address: */ + tlbs_all[1] = TME_M68K_DTLB_ENTRY(ic, + bus_context, + ic->_tme_m68k_ea_function_code, + (rmw->tme_m68k_rmw_addresses[1] + + TME_M68K_TLB_ADDRESS_BIAS(1))); + assert (tlbs_all[1] != tlbs_all[0]); + } + } + + /* make sure that the list of TLB entries to consider is terminated: */ + tlbs_all[2] = NULL; + + /* none of the TLB entries to consider are busy: */ + tlbs_busy[0] = FALSE; + tlbs_busy[1] = FALSE; + + /* the addresses aren't using any TLB entries yet: */ + rmw->tme_m68k_rmw_tlbs[0] = NULL; + rmw->tme_m68k_rmw_tlbs[1] = NULL; + + /* we haven't done any slow reads for any addresses yet: */ + rmw->tme_m68k_rmw_slow_reads[0] = FALSE; + rmw->tme_m68k_rmw_slow_reads[1] = FALSE; + + /* whenever we need to find a TLB entry to use for an address, we + always prefer one that allows both reading and writing, because + we hope that such a TLB entry allows both fast reading and fast + writing. + + if we can't find such a TLB entry initially, we try to fill a TLB + entry for writing (you can't fill a TLB entry for both reading + and writing), in the hopes that this gives us a TLB entry that + allows both fast reading and fast writing. filling for writing + is important with some virtual memory hardware, and may actually + be required to enable writing. + + if this fill gives us a TLB entry that doesn't allow both fast + reading and fast writing, it actually might not allow reading at + all. to check for this, we then try to fill a TLB entry for + reading. + + if we still don't have a TLB entry that allows both fast reading + and fast writing, we must at least have a TLB entry that allows + slow reading. at this point we do a slow read to start a locked + read-modify-write cycle (unless this is a cas2, in which case we + do a normal slow read). + + we always want to return to the caller with a TLB entry that + allows writing, so after we do a slow read we do one more TLB + fill for writing. + + the first TLB fill we do for an address will be for writing, so + that is how we initialize an address' address_cycles mask: */ + address_cycles[0] = TME_BUS_CYCLE_WRITE; + address_cycles[1] = TME_BUS_CYCLE_WRITE; + + /* we haven't filled TLBs for any addresses yet: */ + address_fills[0] = 0; + address_fills[1] = 0; + + /* assume that we can support this instruction on the given memory: */ + supported = TRUE; + + /* loop forever: */ + for (;;) { + + /* assume that no address needs a TLB fill: */ + address_i_fill = rmw->tme_m68k_rmw_address_count; + + /* get the bus context for this iteration: */ + bus_context = ic->_tme_m68k_bus_context; + + /* walk the addresses: */ + address_i = 0; + do { + + /* get this address: */ + address = rmw->tme_m68k_rmw_addresses[address_i]; + + /* this address isn't using a TLB entry yet: */ + tlb_use = NULL; + + /* walk the TLB entries we are considering: */ + for (tlb_i = 0; + (tlb = tlbs_all[tlb_i]) != NULL; + tlb_i++) { + + /* if this TLB entry isn't busy, busy it: */ + if (!tlbs_busy[tlb_i]) { + tme_m68k_tlb_busy(tlb); + tlbs_busy[tlb_i] = TRUE; + } + + /* if this TLB entry is valid, applies to this context, function code + and address, and allows at least the desired cycle(s), and + either this address isn't already using a TLB entry, or the + TLB entry it's using doesn't cover the entire operand, or + this TLB entry allows more cycles or allows both fast + reading and fast writing: */ + if (tme_m68k_tlb_is_valid(tlb) + && tlb->tme_m68k_tlb_bus_context == bus_context + && (tlb->tme_m68k_tlb_function_codes_mask + & TME_BIT(ic->_tme_m68k_ea_function_code)) != 0 + && address >= (tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_first + && address <= (tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_last + && (tlb->tme_m68k_tlb_cycles_ok + & address_cycles[address_i]) != 0 + && (tlb_use == NULL + || (((tme_bus_addr32_t) tlb_use->tme_m68k_tlb_linear_last) - address) < rmw->tme_m68k_rmw_size + || tlb->tme_m68k_tlb_cycles_ok > tlb_use->tme_m68k_tlb_cycles_ok + || (tlb->tme_m68k_tlb_emulator_off_read != TME_EMULATOR_OFF_UNDEF + && tlb->tme_m68k_tlb_emulator_off_write != TME_EMULATOR_OFF_UNDEF))) { + + /* update the TLB entry this address is using: */ + tlb_use = tlb; + } + } + + /* set the TLB entry being used by this address: */ + rmw->tme_m68k_rmw_tlbs[address_i] = tlb_use; + + /* if this address is not using any TLB entry: */ + if (tlb_use == NULL) { + + /* we need to fill a TLB entry for this address: */ + address_i_fill = address_i; + } + + } while (++address_i < rmw->tme_m68k_rmw_address_count); + + /* if we need to fill a TLB entry for an address: */ + address_i = address_i_fill; + if (address_i < rmw->tme_m68k_rmw_address_count) { + + /* get this address: */ + address = rmw->tme_m68k_rmw_addresses[address_i]; + + /* get an unused TLB entry to fill: */ + tlb_i = 0; + tlb = tlbs_all[0]; + if (tlb == rmw->tme_m68k_rmw_tlbs[!address_i]) { + tlb_i = 1; + tlb = tlbs_all[1]; + } + assert (tlb != NULL + && tlb != rmw->tme_m68k_rmw_tlbs[!address_i]); + + /* NB: cas2 can need two TLB entries. we may find one good TLB + entry for one address, but need to call out to fill a TLB for + the second address, and unfortunately we have to unbusy the + good one while we're doing the fill. while the good one is + unbusy, it can be invalidated, and we'll have to fill it + again, unbusying the good one we just filled, possibly + leading to a vicious cycle. + + it's also possible that the TLB entry we fill here could be + invalidated after it's been filled and before we've busied it + again. this is also the case for the single-TLB operations: + normal memory reads and writes, and tas and cas, and to + handle that we simply loop around the fill. since these + operations only use a single TLB entry, we assume that there + won't be a vicious cycle - that eventually a single filled + TLB entry will stay valid until we can busy it and use it. + + but we can't really guarantee this for two TLB entries. + there's not much we can do about this, except put a limit on + the number of times we will fill for each address. this + limit is somewhat arbitrary: */ + /* XXX FIXME - this should be a macro, or a per-m68k argument: */ + if (rmw->tme_m68k_rmw_address_count == 2 + && address_fills[address_i]++ >= 20) { + + /* we can't support this instruction on this memory: */ + supported = FALSE; + break; + } + + /* if the other TLB entry is busy, unbusy it: */ + if (tlbs_busy[!tlb_i]) { + tme_m68k_tlb_unbusy(tlbs_all[tlb_i]); + tlbs_busy[!tlb_i] = FALSE; + } + + /* fill this TLB entry: */ + tme_m68k_tlb_fill(ic, + tlb, + ic->_tme_m68k_ea_function_code, + address, + address_cycles[address_i]); + + /* restart: */ + continue; + } + + /* walk the addresses: */ + address_i = 0; + do { + + /* get this address and its TLB entry: */ + address = rmw->tme_m68k_rmw_addresses[address_i]; + tlb = rmw->tme_m68k_rmw_tlbs[address_i]; + + /* if this TLB entry doesn't cover the entire operand: */ + if ((((tme_bus_addr32_t) tlb->tme_m68k_tlb_linear_last) - address) < rmw->tme_m68k_rmw_size) { + + /* we can't support this instruction on this memory, because + we can't split an atomic operation across TLB entries. on + a real m68k, the CPU can do repeated bus cycles under one + bus lock: */ + supported = FALSE; + break; + } + + /* if this TLB entry supports both fast reading and fast + writing: */ + if (tlb->tme_m68k_tlb_emulator_off_read != TME_EMULATOR_OFF_UNDEF + && tlb->tme_m68k_tlb_emulator_off_write != TME_EMULATOR_OFF_UNDEF) { + + /* if fast reading and fast writing aren't to the same memory: */ + if (tlb->tme_m68k_tlb_emulator_off_read + != tlb->tme_m68k_tlb_emulator_off_write) { + + /* we can't support this instruction on this memory, because + we can't split an atomic operation across two memories. + on a real m68k, the CPU can do repeated bus cycles under + one bus lock: */ + supported = FALSE; + break; + } + } + + /* otherwise, this TLB entry does not support both fast reading + and fast writing: */ - /* we *must* guarantee that a read/modify/write cycle be atomic. - unfortunately, the only way we can really do that is to acquire a - single lock, now, that somehow protects all of the things we want - to do, and hold that lock for the duration. - - the only way we can do this is to require that read/modify/write - cycles always involve TLB entries that allow fast reads and - writes. this gives us a single rwlock that we can lock for - writing now and hold until we're done: */ - - /* we invalidate the TLB entry so we can set the TLB rwlock to NULL, - which is seen by the first tme_m68k_readSIZE (or - tme_m68k_writeSIZE, in the bizarre case that that's called first) - as a signal that, after it reloads the TLB entry, it has to lock - the rwlock: */ - tme_bus_tlb_invalidate(&tlb->tme_m68k_tlb_bus_tlb); - tlb->tme_m68k_tlb_bus_rwlock = NULL; - - return (tlb); -} - -/* this finishes a read/modify/write cycle. this works in conjunction - with the tme_m68k_readSIZE() and tme_m68k_writeSIZE() functions to - aggregate many bus transactions into a larger transaction: */ + /* if we have already done a slow read for this address: */ + else if (rmw->tme_m68k_rmw_slow_reads[address_i]) { + + /* this TLB entry must support writing: */ + assert (tlb->tme_m68k_tlb_cycles_ok & TME_BUS_CYCLE_WRITE); + + /* nothing to do: */ + } + + /* otherwise, we have not already done a slow read for this + address: */ + + /* if this TLB entry doesn't support slow reading: */ + else if ((tlb->tme_m68k_tlb_cycles_ok & TME_BUS_CYCLE_READ) == 0) { + + /* we must fill a TLB entry for reading: */ + assert (address_cycles[address_i] == TME_BUS_CYCLE_WRITE); + address_cycles[address_i] = TME_BUS_CYCLE_READ; + + /* restart: */ + break; + } + + /* otherwise, this TLB entry does support slow reading: */ + else { + + /* if the other TLB entry is busy, unbusy it: */ + tlb_i = (tlb == tlbs_all[1]); + if (tlbs_busy[!tlb_i]) { + tme_m68k_tlb_unbusy(tlbs_all[tlb_i]); + tlbs_busy[!tlb_i] = FALSE; + } + + /* this instruction can fault: */ + TME_M68K_INSN_CANFAULT; + + /* do a slow read. if this is the first address, we start a + slow read-modify-write cycle, otherwise we do a normal slow + read cycle: */ + assert (rmw->tme_m68k_rmw_size <= sizeof(ic->tme_m68k_ireg_memx32)); + tme_m68k_read(ic, + tlb, + &ic->_tme_m68k_ea_function_code, + &rmw->tme_m68k_rmw_addresses[address_i], + (((tme_uint8_t *) + (address_i == 0 + ? &ic->tme_m68k_ireg_memx32 + : &ic->tme_m68k_ireg_memy32)) + + (TME_ENDIAN_NATIVE == TME_ENDIAN_BIG + ? (sizeof(ic->tme_m68k_ireg_memx32) + - rmw->tme_m68k_rmw_size) + : 0)), + rmw->tme_m68k_rmw_size, + (address_i == 0 + ? TME_M68K_BUS_CYCLE_RMW + : TME_M68K_BUS_CYCLE_NORMAL)); + + /* we have done a slow read for this address: */ + rmw->tme_m68k_rmw_slow_reads[address_i] = TRUE; + + /* now we need a TLB entry for this address that supports writing: */ + address_cycles[address_i] = TME_BUS_CYCLE_WRITE; + + /* restart: */ + break; + } + + } while (++address_i < rmw->tme_m68k_rmw_address_count); + + /* if this instruction is not supported or we've handled all + addresses, stop now: */ + if (!supported + || address_i >= rmw->tme_m68k_rmw_address_count) { + break; + } + } + + /* unbusy any TLB entries that aren't being used: */ + if (tlbs_busy[0] + && (!supported + || (tlbs_all[0] != rmw->tme_m68k_rmw_tlbs[0] + && tlbs_all[0] != rmw->tme_m68k_rmw_tlbs[1]))) { + tme_m68k_tlb_unbusy(tlbs_all[0]); + } + if (tlbs_busy[1] + && (!supported + || (tlbs_all[1] != rmw->tme_m68k_rmw_tlbs[0] + && tlbs_all[1] != rmw->tme_m68k_rmw_tlbs[1]))) { + tme_m68k_tlb_unbusy(tlbs_all[1]); + } + + /* if this instruction is not supported on this memory: */ + if (!supported) { + + /* cause an illegal instruction exception: */ + TME_M68K_INSN_EXCEPTION(TME_M68K_EXCEPTION_ILL); + } + + /* if this is the cas2 instruction: */ + if (rmw->tme_m68k_rmw_address_count == 2) { + + /* cas2 is a difficult instruction to emulate, since it accesses + two different addresses during one atomic read-modify-write + cycle. + + most host CPUs can't do this, so when threads are not + cooperative, we're forced to suspend all other threads when + running a cas2 instruction: */ + if (!TME_THREADS_COOPERATIVE) { + tme_thread_suspend_others(); + } + + /* the cas2 functions also assume that we have read all operands + into the memory buffers, which means we have to fast-read any + addresses that we haven't already slow-read: */ + address_i = 0; + do { + + /* skip this address if we really did slow read it: */ + if (rmw->tme_m68k_rmw_slow_reads[address_i]) { + continue; + } + + /* get this address and its TLB entry: */ + address = rmw->tme_m68k_rmw_addresses[address_i]; + tlb = rmw->tme_m68k_rmw_tlbs[address_i]; + + /* this TLB entry must support fast reading and fast writing: */ + assert (tlb->tme_m68k_tlb_emulator_off_read != TME_EMULATOR_OFF_UNDEF + && tlb->tme_m68k_tlb_emulator_off_write == tlb->tme_m68k_tlb_emulator_off_read); + + /* do the fast read. all other threads are suspended here, so + we can do a memcpy instead of an atomic read: */ + assert (rmw->tme_m68k_rmw_size <= sizeof(ic->tme_m68k_ireg_memx32)); + buffer_reg + = (address_i == 0 + ? &ic->tme_m68k_ireg_memx32 + : &ic->tme_m68k_ireg_memy32); + memcpy((((tme_uint8_t *) buffer_reg) + + (sizeof(ic->tme_m68k_ireg_memx32) + - rmw->tme_m68k_rmw_size)), + (((tme_uint8_t *) + tlb->tme_m68k_tlb_emulator_off_read) + + address), + rmw->tme_m68k_rmw_size); + + /* byteswap the value read: */ + *buffer_reg = tme_betoh_u32(*buffer_reg); + + } while (++address_i < rmw->tme_m68k_rmw_address_count); + } + + /* return success: */ + return (0); +} + +/* this finishes a read/modify/write cycle: */ void -tme_m68k_rmw_finish(struct tme_m68k *ic, struct tme_m68k_tlb *tlb) +tme_m68k_rmw_finish(struct tme_m68k *ic, + struct tme_m68k_rmw *rmw, + int do_write) { - - /* if we didn't acquire the rwlock, something is wrong: */ - assert(tlb->tme_m68k_tlb_bus_rwlock != NULL); - - /* unlock the lock: */ - tme_rwlock_unlock(tlb->tme_m68k_tlb_bus_rwlock); + struct tme_m68k_tlb *tlbs_all[2]; + int tlbs_busy[2]; + struct tme_m68k_tlb *tlb; + unsigned int tlb_i; + unsigned int address_i; + tme_uint32_t address; + int supported; + tme_uint32_t *buffer_reg; + + /* recover the tlbs_all[] array and tlbs_busy[] information: */ + tlbs_all[0] = rmw->tme_m68k_rmw_tlbs[0]; + tlbs_busy[0] = TRUE; + if (rmw->tme_m68k_rmw_tlbs[1] != NULL + && rmw->tme_m68k_rmw_tlbs[1] != rmw->tme_m68k_rmw_tlbs[0]) { + tlbs_all[1] = rmw->tme_m68k_rmw_tlbs[1]; + tlbs_busy[1] = TRUE; + } + else { + tlbs_all[1] = NULL; + tlbs_busy[1] = FALSE; + } + + /* assume that this instruction is supported: */ + supported = TRUE; + + /* loop over the addresses: */ + address_i = 0; + do { + + /* get this address and TLB entry: */ + address = rmw->tme_m68k_rmw_addresses[address_i]; + tlb = rmw->tme_m68k_rmw_tlbs[address_i]; + + /* get the buffer for this address: */ + buffer_reg + = (address_i == 0 + ? &ic->tme_m68k_ireg_memx32 + : &ic->tme_m68k_ireg_memy32); + + /* if we did a slow read for this operand: */ + if (rmw->tme_m68k_rmw_slow_reads[address_i]) { + + /* if the other TLB entry is busy, unbusy it: */ + tlb_i = (tlb == tlbs_all[1]); + if (tlbs_busy[!tlb_i]) { + tme_m68k_tlb_unbusy(tlbs_all[tlb_i]); + tlbs_busy[!tlb_i] = FALSE; + } + + /* do the slow write for this operand: */ + assert (rmw->tme_m68k_rmw_size <= sizeof(ic->tme_m68k_ireg_memx32)); + tme_m68k_write(ic, + tlb, + &ic->_tme_m68k_ea_function_code, + &rmw->tme_m68k_rmw_addresses[address_i], + (((tme_uint8_t *) buffer_reg) + + (TME_ENDIAN_NATIVE == TME_ENDIAN_BIG + ? (sizeof(ic->tme_m68k_ireg_memx32) + - rmw->tme_m68k_rmw_size) + : 0)), + rmw->tme_m68k_rmw_size, + (address_i == 0 + ? TME_M68K_BUS_CYCLE_RMW + : TME_M68K_BUS_CYCLE_NORMAL)); + + /* if this is the cas2 instruction: */ + if (rmw->tme_m68k_rmw_address_count == 2) { + + /* if a cas2 slow write doesn't fault, it just did a slow + write to device memory, which is actually bad because we + can't do an atomic cas2 involving any device memory at all + (we can't do the dual reads and dual writes all atomically). + + we tried to do the slow write anyways hoping that the slow + write was really to write-protected memory that would + fault, and when we would restart this address would point + to fast-writable memory. + + unfortunately, we can't undo the slow write. we do cause + an illegal instruction exception, to make this problem + visible: */ + supported = FALSE; + break; + } + } + + /* otherwise, if this is the cas2 instruction, and we're writing: */ + else if (rmw->tme_m68k_rmw_address_count == 2 + && do_write) { + + /* this TLB entry must support fast reading and fast writing: */ + assert (tlb->tme_m68k_tlb_emulator_off_read != TME_EMULATOR_OFF_UNDEF + && tlb->tme_m68k_tlb_emulator_off_write == tlb->tme_m68k_tlb_emulator_off_read); + + /* byteswap the value to write: */ + *buffer_reg = tme_htobe_u32(*buffer_reg); + + /* do the fast write. all other threads are suspended here, so + we can do a memcpy instead of an atomic write: */ + assert (rmw->tme_m68k_rmw_size <= sizeof(ic->tme_m68k_ireg_memx32)); + memcpy((((tme_uint8_t *) + tlb->tme_m68k_tlb_emulator_off_read) + + address), + (((tme_uint8_t *) buffer_reg) + + (sizeof(ic->tme_m68k_ireg_memx32) + - rmw->tme_m68k_rmw_size)), + rmw->tme_m68k_rmw_size); + } + + } while (++address_i < rmw->tme_m68k_rmw_address_count); + + /* unbusy all TLB entries: */ + if (tlbs_busy[0]) { + tme_m68k_tlb_unbusy(tlbs_all[0]); + } + if (tlbs_busy[1]) { + tme_m68k_tlb_unbusy(tlbs_all[1]); + } + + /* cas2 is a difficult instruction to emulate, since it accesses two + different addresses during one atomic read-modify-write cycle. + most host CPUs can't do this, so when threads are not + cooperative, we're forced to suspend all other threads when + running a cas2 instruction: */ + if (!TME_THREADS_COOPERATIVE + && rmw->tme_m68k_rmw_address_count > 1) { + tme_thread_resume_others(); + } + + /* if this instruction is not supported on this memory: */ + if (!supported) { + + /* cause an illegal instruction exception: */ + TME_M68K_INSN_EXCEPTION(TME_M68K_EXCEPTION_ILL); + } } /* this handles a bitfield offset. if the bitfield is in memory, @@ -1199,9 +2052,10 @@ tme_m68k_bitfield_offset(struct tme_m68k /* calculate the effective address offset and adjust the bitfield offset to be nonnegative: */ - bf_ea_offset = ((bf_offset < 0) - ? ((bf_offset + 1) / 8) - 1 - : bf_offset / 8); + bf_ea_offset = ((bf_offset < 0 + ? (bf_offset - 7) + : bf_offset) + / 8); bf_offset &= 7; /* if this is our first call to this function for this instruction @@ -1227,11 +2081,11 @@ tme_m68k_bitfield_width(struct tme_m68k specop = ic->_tme_m68k_insn_specop; if (specop & TME_BIT(5)) { bf_width = ic->tme_m68k_ireg_uint32(TME_M68K_IREG_D0 + TME_FIELD_EXTRACTU(specop, 0, 3)); + bf_width &= 31; } else { bf_width = TME_FIELD_EXTRACTU(specop, 0, 5); } - bf_width &= 31; if (bf_width == 0) bf_width = 32; return (bf_width); } @@ -1251,7 +2105,7 @@ _tme_m68k_bitfield_read(struct tme_m68k bf_width = tme_m68k_bitfield_width(ic); /* if this expression is > 32, in a register this means the bitfield - wraps, and in memory this means the bitfield is 5-bytes wide: */ + wraps, and in memory this means the bitfield covers 5 bytes: */ shift = (bf_offset + bf_width); /* if this bitfield is in a register (EA mode field is zero): */ @@ -1274,14 +2128,17 @@ _tme_m68k_bitfield_read(struct tme_m68k /* otherwise, this bitfield is in memory: */ else { + /* this instruction can fault: */ + ic->_tme_m68k_mode_flags |= TME_M68K_EXECUTION_INST_CANFAULT; + /* read in the bytes covering the bitfield: */ bf_bytes = (tme_uint8_t *) &ic->tme_m68k_ireg_memx32; - tme_m68k_read_mem(ic, bf_bytes, (bf_offset + bf_width + 7) >> 3); + tme_m68k_read_mem(ic, bf_bytes, (bf_offset + bf_width + 7) / 8); /* get the raw 32-bit word containing the bitfield: */ bf_value = tme_betoh_u32(ic->tme_m68k_ireg_memx32); - /* if this bitfield is 5 bytes wide, shift in the part from the fifth byte + /* if this bitfield covers 5 bytes, shift in the part from the fifth byte (actually in memy32!) on the right: */ if (shift > 32) { shift -= 32; @@ -1295,12 +2152,12 @@ _tme_m68k_bitfield_read(struct tme_m68k bf_value >>= shift; /* mask the value: */ - bf_value &= TME_BIT(bf_width) - 1; + bf_value &= (0xffffffffUL >> (32 - bf_width)); /* if this is a signed value, sign-extend it: */ if (is_signed && (bf_value & TME_BIT(bf_width - 1))) { - bf_value |= (0xffffffff ^ (TME_BIT(bf_width) - 1)); + bf_value |= (0xffffffffUL << (bf_width - 1)); } /* all bitfield instructions that read the bitfield set the flags: */ @@ -1342,9 +2199,12 @@ tme_m68k_bitfield_write_unsigned(struct bf_width = tme_m68k_bitfield_width(ic); /* if this expression is > 32, in a register this means the bitfield - wraps, and in memory this means the bitfield is 5-bytes wide: */ + wraps, and in memory this means the bitfield covers 5 bytes: */ shift = (bf_offset + bf_width); + /* mask the value: */ + bf_value &= (0xffffffffUL >> (32 - bf_width)); + /* if we're supposed to, set the flags: */ if (set_flags && !TME_M68K_SEQUENCE_RESTARTING) { @@ -1357,9 +2217,6 @@ tme_m68k_bitfield_write_unsigned(struct : TME_M68K_FLAG_Z)); } - /* mask the value: */ - bf_value &= TME_BIT(bf_width) - 1; - /* if this bitfield is in a register (EA mode field is zero): */ if (TME_FIELD_EXTRACTU(ic->_tme_m68k_insn_opcode, 3, 3) == 0) { ireg = (TME_M68K_IREG_D0 @@ -1379,21 +2236,24 @@ tme_m68k_bitfield_write_unsigned(struct /* update the register: */ shift = (32 - (bf_offset + bf_width)); ic->tme_m68k_ireg_uint32(ireg) = ((ic->tme_m68k_ireg_uint32(ireg) - & ((TME_BIT(bf_width) - 1) << shift)) + & ~((0xffffffffUL >> (32 - bf_width)) << shift)) | (bf_value << shift)); } /* otherwise, this bitfield is in memory: */ else { + /* this instruction can fault: */ + ic->_tme_m68k_mode_flags |= TME_M68K_EXECUTION_INST_CANFAULT; + /* read in the bytes covering the bitfield if we haven't yet: */ bf_bytes = (tme_uint8_t *) &ic->tme_m68k_ireg_memx32; - count = (bf_offset + bf_width + 7) >> 3; + count = (bf_offset + bf_width + 7) / 8; if (first_memory) { tme_m68k_read_mem(ic, bf_bytes, count); } - /* if this bitfield is 5 bytes wide, put the part for the fifth + /* if this bitfield covers 5 bytes, put the part for the fifth byte (actually in memy32!) in on the left: */ if (shift > 32) { shift -= 32; @@ -1411,7 +2271,7 @@ tme_m68k_bitfield_write_unsigned(struct shift = (32 - (bf_offset + bf_width)); ic->tme_m68k_ireg_memx32 = tme_htobe_u32((tme_betoh_u32(ic->tme_m68k_ireg_memx32) - & ((TME_BIT(bf_width) - 1) << shift)) + & ~((0xffffffffUL >> (32 - bf_width)) << shift)) | (bf_value << shift)); } @@ -1421,13 +2281,24 @@ tme_m68k_bitfield_write_unsigned(struct #undef first_memory } -#ifdef _TME_M68K_VERIFY /* our global verify hook function: */ +#undef tme_m68k_verify_hook void tme_m68k_verify_hook(void) { } -#endif /* _TME_M68K_VERIFY */ + +void +tme_m68k_kill_cpu(struct tme_m68k *ic) +{ + ic->_tme_m68k_mode = TME_M68K_MODE_DIE; +} + +int _m68k_dead; +void m68k_die(void) +{ + _m68k_dead = 1; +} #if 1 #include @@ -1510,9 +2381,59 @@ tme_m68k_dump(struct tme_m68k *ic) /* dump out instruction decoding information: */ fprintf(stderr, "\n"); - fprintf(stderr, "opcode = 0x%04x specop = 0x%04x specop2 = 0x%04x\n", + fprintf(stderr, "opcode = 0x%04x specop = 0x%04x\n", ic->_tme_m68k_insn_opcode, - ic->_tme_m68k_insn_specop, - ic->_tme_m68k_insn_specop2); + ic->_tme_m68k_insn_specop); +} + +void +tme_m68k_dump_memory(struct tme_m68k *ic, tme_uint32_t address, tme_uint32_t resid) +{ + unsigned int saved_ea_function_code; + tme_uint32_t saved_ea_address; + tme_uint32_t address_display; + tme_uint8_t buffer[16]; + tme_uint32_t count; + tme_uint32_t byte_i; + + /* save any EA function code and address: */ + saved_ea_function_code = ic->_tme_m68k_ea_function_code; + saved_ea_address = ic->_tme_m68k_ea_address; + + /* we always display aligned rows: */ + address_display = address & (((tme_uint32_t) 0) - sizeof(buffer)); + + /* while we have memory to dump: */ + for (; resid > 0; ) { + + /* read more data: */ + byte_i = address % sizeof(buffer); + count = TME_MIN(resid, sizeof(buffer) - byte_i); + ic->_tme_m68k_ea_function_code = TME_M68K_FUNCTION_CODE_DATA(ic); + ic->_tme_m68k_ea_address = address; + tme_m68k_read_mem(ic, &buffer[byte_i], count); + count += byte_i; + + /* display the row: */ + fprintf(stderr, "0x%08x ", address_display); + for (byte_i = 0; + byte_i < count; + byte_i++, address_display++) { + if (address_display < address) { + fprintf(stderr, " "); + } + else { + fprintf(stderr, " %02x", + buffer[byte_i]); + address++; + resid--; + } + } + fputc('\n', stderr); + } + + /* restore any EA function code and address: */ + ic->_tme_m68k_ea_function_code = saved_ea_function_code; + ic->_tme_m68k_ea_address = saved_ea_address; } #endif /* 1 */