/*  m68kcpmsim.c: CP/M for Motorola 68000 definitions

 Copyright (c) 2014 - 2023, 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 occurred.

 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/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 functionality. */
#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;
extern uint32 SIMHSleep;

/* 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);

/* Interface to SIMH I/O devices */
extern void out(const uint32 Port, const uint32 Value);
extern uint32 in(const uint32 Port);

static uint32 m68k_fc;                              /* Current function code from CPU */

extern uint32 m68k_registers[M68K_REG_CPU_TYPE + 1];
extern UNIT cpu_unit;
extern uint32 mmiobase;                             /* M68K MMIO base address            */
extern uint32 mmiosize;                             /* M68K MMIO window size             */
extern uint32 m68kvariant;                          /* M68K variant (68000, 68010, etc.) */

#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, (m68k_register_t)reg);
}

void m68k_viewToCPU(void) {
    uint32 reg;
    for (reg = M68K_REG_D0; reg <= M68K_REG_CPU_TYPE; reg++)
        m68k_set_reg((m68k_register_t)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 ((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_init();

    if ((m68kvariant == M68K_CPU_TYPE_INVALID) || (m68kvariant > M68K_CPU_TYPE_SCC68070)) {
        sim_printf("M68K variant %u not supported, using 68000\n", m68kvariant);
        m68kvariant = M68K_CPU_TYPE_68000;
    }
    m68k_set_cpu_type(m68kvariant);
    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
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 (SIMHSleep)
            sim_os_ms_sleep(SIMHSleep);
        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)) {
        sim_interval -= KBD_POLL_WAIT;  // ensure progress
        if (SIMHSleep)
            sim_os_ms_sleep(SIMHSleep);
        ch = sim_poll_kbd();
    }
    if (ch == SCPE_STOP)
        stop_cpu = TRUE;
    return (((ch > 0) && (!stop_cpu)) ? ch & 0xff : 0xff);
}

static int MC6850_status_read(void) {
    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 interrupt 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.\n",
                   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:
            if ((address >= mmiobase) && (address < mmiobase + mmiosize)) {
                /* Memory-mapped I/O */
                return (in(address & 0xff) & 0xff);
            }
            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.\n",
                   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-1) {
        if (cpu_unit.flags & UNIT_CPU_VERBOSE)
            sim_printf("M68K: 0x%08x Attempt to read word from non existing memory 0x%08x.\n",
                   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)(sim_get_time(NULL));
        default:
            break;
    }
    if (address > M68K_MAX_RAM-3) {
        if (cpu_unit.flags & UNIT_CPU_VERBOSE)
            sim_printf("M68K: 0x%08x Attempt to read long from non existing memory 0x%08x.\n",
                   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.\n",
                   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:
            if ((address >= mmiobase) && (address < mmiobase + mmiosize)) {
                /* Memory-mapped I/O */
                out(address & 0xff, value & 0xff);
            }
            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.\n",
                   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-1) {
        if (cpu_unit.flags & UNIT_CPU_VERBOSE)
            sim_printf("M68K: 0x%08x Attempt to write word 0x%04x to non existing memory 0x%08x.\n",
                   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-3) {
        if (cpu_unit.flags & UNIT_CPU_VERBOSE)
            sim_printf("M68K: 0x%08x Attempt to write long 0x%08x to non existing memory 0x%08x.\n",
                   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);
}
