| /* m68kcpmsim.c: CP/M for Motorola 68000 definitions | |
| Copyright (c) 2014, Peter Schorn | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| Except as contained in this notice, the name of Peter Schorn shall not | |
| be used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from Peter Schorn. | |
| Based on work by David W. Schultz http://home.earthlink.net/~david.schultz (c) 2014 | |
| MC68000 simulation tailored to support CP/M-68K. It includes: | |
| 16MB of memory. (Flat, function codes and address space types ignored.) | |
| Console I/O using a MC6850 like serial port with interrupts. | |
| Simulated disk system: | |
| Since the intent is to support CP/M-68K and it does disk I/O in 128 byte | |
| chunks, so will this. Control is via several registers mapped into memory: | |
| Offset Function Description | |
| 0 DMA address to read/write data to | |
| 4 drive select disk drive | |
| 8 read sector sector (128 byte) offset on disk | |
| 12 write sector sector (128 byte) offset on disk | |
| 16 status read status of operation | |
| Operation is simple: Set the drive and DMA address and then write the | |
| sector number to the sector register. This write triggers the requested | |
| operation. The status of the operation can be determined by reading the | |
| status register. | |
| A zero indicates that no error occured. | |
| Note that these operations invoke read() and write() system calls directly | |
| so that they will alter the image on the hard disk. KEEP BACKUPS! | |
| In addition Linux will buffer the writes so they may note be really complete | |
| for a while. The BIOS flush function invokes a fsync on all open files. | |
| There are two options for booting CPM: | |
| S-records: This loads CPM in two parts. The first is in cpm400.bin which | |
| is created from the srecords in cpm400.sr. The second is in simbios.bin | |
| which contains the BIOS. Both of these files must be binaries and not | |
| srecords. | |
| If you want to alter the bios, rebuild simbios.bin using: | |
| asl simbios.s | |
| p2bin simbios.p | |
| Use altairz80 cpm68k_s to boot. | |
| Boot track: A CPM loader is in the boot track of simulated drive C. 32K of | |
| data is loaded from that file to memory starting at $400. | |
| Use altairz80 cpm68k to boot. | |
| */ | |
| #include "m68k.h" | |
| /* Read/write macros */ | |
| #define READ_BYTE(BASE, ADDR) (BASE)[ADDR] | |
| #define READ_WORD(BASE, ADDR) (((BASE)[ADDR] << 8) | \ | |
| (BASE)[(ADDR) + 1]) | |
| #define READ_LONG(BASE, ADDR) (((BASE)[ADDR] << 24) | \ | |
| ((BASE)[(ADDR) + 1] << 16) | \ | |
| ((BASE)[(ADDR) + 2] << 8) | \ | |
| (BASE)[(ADDR) + 3]) | |
| #define WRITE_BYTE(BASE, ADDR, VAL) (BASE)[ADDR] = (VAL) & 0xff | |
| #define WRITE_WORD(BASE, ADDR, VAL) (BASE)[ADDR] = ((VAL) >> 8) & 0xff; \ | |
| (BASE)[(ADDR)+1] = (VAL)&0xff | |
| #define WRITE_LONG(BASE, ADDR, VAL) (BASE)[ADDR] = ((VAL) >> 24) & 0xff; \ | |
| (BASE)[(ADDR) + 1] = ((VAL) >> 16) & 0xff; \ | |
| (BASE)[(ADDR) + 2] = ((VAL) >> 8) & 0xff; \ | |
| (BASE)[(ADDR) + 3] = (VAL) & 0xff | |
| /* Memory-mapped IO ports */ | |
| /* 6850 serial port like thing. Implements a reduced set of functionallity. */ | |
| #define MC6850_STAT 0xff1000L // command/status register | |
| #define MC6850_DATA 0xff1002L // receive/transmit data register | |
| /* Memory mapped disk system */ | |
| #define DISK_BASE 0xff0000L | |
| #define DISK_SET_DMA (DISK_BASE) | |
| #define DISK_SET_DRIVE (DISK_BASE + 4) | |
| #define DISK_SET_SECTOR (DISK_BASE + 8) | |
| #define DISK_READ (DISK_BASE + 12) | |
| #define DISK_WRITE (DISK_BASE + 16) | |
| #define DISK_STATUS (DISK_BASE + 20) | |
| #define DISK_FLUSH (DISK_BASE + 24) | |
| /* Miscellaneous */ | |
| #define M68K_GET_TIME (0xff7ff8) // read long to get time in seconds | |
| #define M68K_STOP_CPU (0xff7ffc) // write long to stop CPU and return to SIMH prompt | |
| /* IRQ connections */ | |
| #define IRQ_NMI_DEVICE 7 | |
| #define IRQ_MC6850 5 | |
| extern uint32 PCX; | |
| /* Prototypes */ | |
| static void MC6850_reset(void); | |
| static void m68k_input_device_update(void); | |
| static int nmi_device_ack(void); | |
| static void int_controller_set(uint32 value); | |
| static void int_controller_clear(uint32 value); | |
| /* Data */ | |
| static int m68k_MC6850_control = 0; /* MC6850 control register */ | |
| static int m68k_MC6850_status = 2; /* MC6850 status register */ | |
| static t_stat keyboardCharacter; /* one character buffer */ | |
| static t_bool characterAvailable = FALSE; /* buffer initially empty */ | |
| static uint32 m68k_int_controller_pending = 0; /* list of pending interrupts */ | |
| static uint32 m68k_int_controller_highest_int = 0; /* Highest pending interrupt */ | |
| static uint8 m68k_ram[M68K_MAX_RAM + 1]; /* RAM */ | |
| /* Interface to HDSK device */ | |
| extern void hdsk_prepareRead(void); | |
| extern void hdsk_prepareWrite(void); | |
| extern void hdsk_setSelectedDisk(const int32 disk); | |
| extern void hdsk_setSelectedSector(const int32 sector); | |
| extern void hdsk_setSelectedTrack(const int32 track); | |
| extern void hdsk_setSelectedDMA(const int32 dma); | |
| extern int32 hdsk_getStatus(void); | |
| extern t_bool hdsk_checkParameters(void); | |
| extern int32 hdsk_read(void); | |
| extern int32 hdsk_write(void); | |
| extern int32 hdsk_flush(void); | |
| static uint32 m68k_fc; /* Current function code from CPU */ | |
| extern uint32 m68k_registers[M68K_REG_CPU_TYPE + 1]; | |
| extern UNIT cpu_unit; | |
| #if !UNIX_PLATFORM | |
| extern void pollForCPUStop(void); | |
| #endif | |
| #define M68K_BOOT_LENGTH (32 * 1024) /* size of bootstrap */ | |
| #define M68K_BOOT_PC 0x000400 /* initial PC for boot */ | |
| #define M68K_BOOT_SP 0xfe0000 /* initial SP for boot */ | |
| t_stat m68k_hdsk_boot(const int32 unitno, DEVICE *dptr, | |
| const uint32 verboseMessage, const int32 hdskNumber) { | |
| UNIT *uptr; | |
| size_t i; | |
| if ((unitno < 0) || (unitno >= hdskNumber)) | |
| return SCPE_ARG; | |
| uptr = (dptr -> units) + unitno; | |
| if (((uptr -> flags) & UNIT_ATT) == 0) { | |
| sim_debug(verboseMessage, dptr, "HDSK%d: Boot drive is not attached.\n", unitno); | |
| return SCPE_ARG; | |
| } | |
| if (sim_fseek(uptr -> fileref, 0, SEEK_SET) != 0) { | |
| sim_debug(verboseMessage, dptr, "HDSK%d: Boot error seeking start.\n", unitno); | |
| return SCPE_ARG; | |
| } | |
| i = sim_fread(&m68k_ram[M68K_BOOT_PC], 1, M68K_BOOT_LENGTH, uptr -> fileref); | |
| if (i != M68K_BOOT_LENGTH) { | |
| sim_debug(verboseMessage, dptr, | |
| "HDSK%d: Error: Failed to read %i bytes from boot drive.\n", | |
| unitno, M68K_BOOT_LENGTH); | |
| return SCPE_ARG; | |
| } | |
| // Now put in values for the stack and PC vectors | |
| WRITE_LONG(m68k_ram, 0, M68K_BOOT_SP); // SP | |
| WRITE_LONG(m68k_ram, 4, M68K_BOOT_PC); // PC | |
| m68k_pulse_reset(); // also calls MC6850_reset() | |
| m68k_CPUToView(); | |
| return SCPE_OK; | |
| } | |
| void m68k_CPUToView(void) { | |
| uint32 reg; | |
| for (reg = M68K_REG_D0; reg <= M68K_REG_CPU_TYPE; reg++) | |
| m68k_registers[reg] = m68k_get_reg(NULL, reg); | |
| } | |
| void m68k_viewToCPU(void) { | |
| uint32 reg; | |
| for (reg = M68K_REG_D0; reg <= M68K_REG_CPU_TYPE; reg++) | |
| m68k_set_reg(reg, m68k_registers[reg]); | |
| } | |
| t_stat sim_instr_m68k(void) { | |
| t_stat reason = SCPE_OK; | |
| m68k_viewToCPU(); | |
| while (TRUE) { | |
| if (sim_interval <= 0) { /* check clock queue */ | |
| #if !UNIX_PLATFORM | |
| /* poll on platforms without reliable signalling but not too often */ | |
| pollForCPUStop(); /* following sim_process_event will check for stop */ | |
| #endif | |
| if ((reason = sim_process_event())) | |
| break; | |
| m68k_input_device_update(); | |
| } | |
| if (sim_brk_summ && sim_brk_test(m68k_get_reg(NULL, M68K_REG_PC), SWMASK('E'))) { | |
| /* breakpoint? */ | |
| reason = STOP_IBKPT; /* stop simulation */ | |
| break; | |
| } | |
| PCX = m68k_get_reg(NULL, M68K_REG_PC); | |
| sim_interval--; | |
| m68k_execute(1); | |
| if (stop_cpu) { | |
| reason = SCPE_STOP; | |
| break; | |
| } | |
| } | |
| m68k_CPUToView(); | |
| return reason; | |
| } | |
| void m68k_clear_memory(void ) { | |
| uint32 i; | |
| for (i = 0; i <= M68K_MAX_RAM; i++) | |
| m68k_ram[i] = 0; | |
| } | |
| void m68k_cpu_reset(void) { | |
| WRITE_LONG(m68k_ram, 0, 0x00006000); // SP | |
| WRITE_LONG(m68k_ram, 4, 0x00000200); // PC | |
| m68k_pulse_reset(); // also calls MC6850_reset() | |
| m68k_CPUToView(); | |
| } | |
| /* Implementation for the MC6850 like device | |
| Only those bits of the control register that enable/disable receive and | |
| transmit interrupts are implemented. | |
| In the status register, the Receive Data Register Full, Transmit Data | |
| Register Empty, and IRQ flags are implemented. Although the transmit | |
| data register is always empty. | |
| */ | |
| static void MC6850_reset(void) { | |
| m68k_MC6850_control = 0; | |
| m68k_MC6850_status = 2; | |
| characterAvailable = FALSE; | |
| int_controller_clear(IRQ_MC6850); | |
| } | |
| #define INITIAL_IDLE 100 | |
| #define IDLE_SLEEP 20 | |
| static uint32 idleCount = INITIAL_IDLE; | |
| static void m68k_input_device_update(void) { | |
| if (characterAvailable) { | |
| m68k_MC6850_status |= 1; | |
| if ((m68k_MC6850_control & 0x80) && !(m68k_MC6850_status & 0x80)) { | |
| int_controller_set(IRQ_MC6850); | |
| m68k_MC6850_status |= 0x80; | |
| } | |
| } else if (--idleCount == 0) { | |
| const t_stat ch = sim_poll_kbd(); | |
| idleCount = INITIAL_IDLE; | |
| if (IDLE_SLEEP) | |
| sim_os_ms_sleep(IDLE_SLEEP); | |
| if (ch) { | |
| characterAvailable = TRUE; | |
| keyboardCharacter = ch; | |
| } | |
| } | |
| } | |
| /* wait until character becomes available */ | |
| static uint32 MC6850_data_read(void) { | |
| t_stat ch; | |
| int_controller_clear(IRQ_MC6850); | |
| m68k_MC6850_status &= ~0x81; // clear data ready and interrupt flag | |
| if (characterAvailable) { | |
| ch = keyboardCharacter; | |
| characterAvailable = FALSE; | |
| } else | |
| ch = sim_poll_kbd(); | |
| while ((ch <= 0) && (!stop_cpu)) { | |
| if (IDLE_SLEEP) | |
| sim_os_ms_sleep(IDLE_SLEEP); | |
| ch = sim_poll_kbd(); | |
| } | |
| if (ch == SCPE_STOP) | |
| stop_cpu = TRUE; | |
| return (((ch > 0) && (!stop_cpu)) ? ch & 0xff : 0xff); | |
| } | |
| static int MC6850_status_read() { | |
| return m68k_MC6850_status; | |
| } | |
| /* Implementation for the output device */ | |
| static int MC6850_device_ack(void) { | |
| return M68K_INT_ACK_AUTOVECTOR; | |
| } | |
| static void MC6850_data_write(uint32 value) { | |
| sim_putchar(value); | |
| if ((m68k_MC6850_control & 0x60) == 0x20) { // transmit interupt enabled? | |
| int_controller_clear(IRQ_MC6850); | |
| int_controller_set(IRQ_MC6850); | |
| } | |
| } | |
| static void MC6850_control_write(uint32 val) { | |
| m68k_MC6850_control = val; | |
| } | |
| /* Read data from RAM */ | |
| unsigned int m68k_cpu_read_byte_raw(unsigned int address) { | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to read byte from non existing memory 0x%08x." NLP, | |
| PCX, address); | |
| return 0xff; | |
| } | |
| return READ_BYTE(m68k_ram, address); | |
| } | |
| unsigned int m68k_cpu_read_byte(unsigned int address) { | |
| switch(address) { | |
| case MC6850_DATA: | |
| return MC6850_data_read(); | |
| case MC6850_STAT: | |
| return MC6850_status_read(); | |
| default: | |
| break; | |
| } | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to read byte from non existing memory 0x%08x." NLP, | |
| PCX, address); | |
| return 0xff; | |
| } | |
| return READ_BYTE(m68k_ram, address); | |
| } | |
| unsigned int m68k_cpu_read_word(unsigned int address) { | |
| switch(address) { | |
| case DISK_STATUS: | |
| return hdsk_getStatus(); | |
| default: | |
| break; | |
| } | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to read word from non existing memory 0x%08x." NLP, | |
| PCX, address); | |
| return 0xffff; | |
| } | |
| return READ_WORD(m68k_ram, address); | |
| } | |
| unsigned int m68k_cpu_read_long(unsigned int address) { | |
| switch(address) { | |
| case DISK_STATUS: | |
| return hdsk_getStatus(); | |
| case M68K_GET_TIME: | |
| return (unsigned int)(time(NULL)); | |
| default: | |
| break; | |
| } | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to read long from non existing memory 0x%08x." NLP, | |
| PCX, address); | |
| return 0xffffffff; | |
| } | |
| return READ_LONG(m68k_ram, address); | |
| } | |
| /* Write data to RAM or a device */ | |
| void m68k_cpu_write_byte_raw(unsigned int address, unsigned int value) { | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to write byte 0x%02x to non existing memory 0x%08x." NLP, | |
| PCX, value & 0xff, address); | |
| return; | |
| } | |
| WRITE_BYTE(m68k_ram, address, value); | |
| } | |
| void m68k_cpu_write_byte(unsigned int address, unsigned int value) { | |
| switch(address) { | |
| case MC6850_DATA: | |
| MC6850_data_write(value & 0xff); | |
| return; | |
| case MC6850_STAT: | |
| MC6850_control_write(value & 0xff); | |
| return; | |
| default: | |
| break; | |
| } | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to write byte 0x%02x to non existing memory 0x%08x." NLP, | |
| PCX, value & 0xff, address); | |
| return; | |
| } | |
| WRITE_BYTE(m68k_ram, address, value); | |
| } | |
| void m68k_cpu_write_word(unsigned int address, unsigned int value) { | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to write word 0x%04x to non existing memory 0x%08x." NLP, | |
| PCX, value & 0xffff, address); | |
| return; | |
| } | |
| WRITE_WORD(m68k_ram, address, value); | |
| } | |
| void m68k_cpu_write_long(unsigned int address, unsigned int value) { | |
| switch(address) { | |
| case DISK_SET_DRIVE: | |
| hdsk_setSelectedDisk(value); | |
| return; | |
| case DISK_SET_DMA: | |
| hdsk_setSelectedDMA(value); | |
| return; | |
| case DISK_SET_SECTOR: | |
| hdsk_setSelectedSector(value); | |
| return; | |
| case DISK_READ: | |
| hdsk_setSelectedSector(value); | |
| hdsk_setSelectedTrack(0); | |
| hdsk_prepareRead(); | |
| if (hdsk_checkParameters()) | |
| hdsk_read(); | |
| return; | |
| case DISK_WRITE: | |
| hdsk_setSelectedSector(value); | |
| hdsk_setSelectedTrack(0); | |
| hdsk_prepareWrite(); | |
| if (hdsk_checkParameters()) | |
| hdsk_write(); | |
| return; | |
| case DISK_FLUSH: | |
| hdsk_flush(); | |
| return; | |
| case M68K_STOP_CPU: | |
| stop_cpu = TRUE; | |
| return; | |
| default: | |
| break; | |
| } | |
| if (address > M68K_MAX_RAM) { | |
| if (cpu_unit.flags & UNIT_CPU_VERBOSE) | |
| sim_printf("M68K: 0x%08x Attempt to write long 0x%08x to non existing memory 0x%08x." NLP, | |
| PCX, value, address); | |
| return; | |
| } | |
| WRITE_LONG(m68k_ram, address, value); | |
| } | |
| /* Called when the CPU pulses the RESET line */ | |
| void m68k_cpu_pulse_reset(void) { | |
| MC6850_reset(); | |
| } | |
| /* Called when the CPU changes the function code pins */ | |
| void m68k_cpu_set_fc(unsigned int fc) { | |
| m68k_fc = fc; | |
| } | |
| /* Called when the CPU acknowledges an interrupt */ | |
| int m68k_cpu_irq_ack(int level) { | |
| switch(level) { | |
| case IRQ_NMI_DEVICE: | |
| return nmi_device_ack(); | |
| case IRQ_MC6850: | |
| return MC6850_device_ack(); | |
| } | |
| return M68K_INT_ACK_SPURIOUS; | |
| } | |
| /* Implementation for the NMI device */ | |
| static int nmi_device_ack(void) { | |
| int_controller_clear(IRQ_NMI_DEVICE); | |
| return M68K_INT_ACK_AUTOVECTOR; | |
| } | |
| /* Implementation for the interrupt controller */ | |
| static void int_controller_set(uint32 value) { | |
| const uint32 old_pending = m68k_int_controller_pending; | |
| m68k_int_controller_pending |= (1<<value); | |
| if (old_pending != m68k_int_controller_pending && value > m68k_int_controller_highest_int) { | |
| m68k_int_controller_highest_int = value; | |
| m68k_set_irq(m68k_int_controller_highest_int); | |
| } | |
| } | |
| static void int_controller_clear(uint32 value) { | |
| m68k_int_controller_pending &= ~(1<<value); | |
| for (m68k_int_controller_highest_int = 7; m68k_int_controller_highest_int > 0;m68k_int_controller_highest_int--) | |
| if (m68k_int_controller_pending & (1<<m68k_int_controller_highest_int)) | |
| break; | |
| m68k_set_irq(m68k_int_controller_highest_int); | |
| } | |
| unsigned int m68k_read_disassembler_16(unsigned int address) { | |
| return m68k_cpu_read_word(address); | |
| } | |
| unsigned int m68k_read_disassembler_32(unsigned int address) { | |
| return m68k_cpu_read_long(address); | |
| } |