/* 3b2_cpu.h: AT&T 3B2 Model 400 IO and  CIO feature cards

   Copyright (c) 2017, Seth J. Morabito

   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 THE AUTHORS OR COPYRIGHT HOLDERS
   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 the author shall
   not be used in advertising or otherwise to promote the sale, use or
   other dealings in this Software without prior written authorization
   from the author.
*/

#include "3b2_io.h"

CIO_STATE  cio[CIO_SLOTS] = { 0 };

struct iolink iotable[] = {
    { MMUBASE,    MMUBASE+MMUSIZE,       &mmu_read,   &mmu_write   },
    { IFBASE,     IFBASE+IFSIZE,         &if_read,    &if_write    },
    { IDBASE,     IDBASE+IDSIZE,         &id_read,    &id_write    },
    { TIMERBASE,  TIMERBASE+TIMERSIZE,   &timer_read, &timer_write },
    { NVRAMBASE,  NVRAMBASE+NVRAMSIZE,   &nvram_read, &nvram_write },
    { CSRBASE,    CSRBASE+CSRSIZE,       &csr_read,   &csr_write   },
    { IUBASE,     IUBASE+IUSIZE,         &iu_read,    &iu_write    },
    { DMAIDBASE,  DMAIDBASE+DMAIDSIZE,   &dmac_read,  &dmac_write  },
    { DMAIUABASE, DMAIUABASE+DMAIUASIZE, &dmac_read,  &dmac_write  },
    { DMAIUBBASE, DMAIUBBASE+DMAIUBSIZE, &dmac_read,  &dmac_write  },
    { DMACBASE,   DMACBASE+DMACSIZE,     &dmac_read,  &dmac_write  },
    { DMAIFBASE,  DMAIFBASE+DMAIFSIZE,   &dmac_read,  &dmac_write  },
    { TODBASE,    TODBASE+TODSIZE,       &tod_read,   &tod_write   },
    { 0, 0, NULL, NULL}
};

void cio_clear(uint8 cid)
{
    cio[cid].id = 0;
    cio[cid].exp_handler = NULL;
    cio[cid].full_handler = NULL;
    cio[cid].sysgen = NULL;
    cio[cid].rqp = 0;
    cio[cid].cqp = 0;
    cio[cid].rqs = 0;
    cio[cid].cqs = 0;
    cio[cid].ivec = 0;
    cio[cid].no_rque = 0;
    cio[cid].ipl = 0;
    cio[cid].intr = FALSE;
    cio[cid].sysgen_s = 0;
    cio[cid].seqbit = 0;
    cio[cid].op = 0;
}

void cio_sysgen(uint8 cid)
{
    uint32 sysgen_p;

    sysgen_p = pread_w(SYSGEN_PTR);

    sim_debug(IO_DBG, &cpu_dev,
              "[%08x] [SYSGEN] Starting sysgen for card %d. sysgen_p=%08x\n",
              R[NUM_PC], cid, sysgen_p);

    /* seqbit is always reset to 0 on completion */
    cio[cid].seqbit = 0;

    cio[cid].rqp = pread_w(sysgen_p);
    cio[cid].cqp = pread_w(sysgen_p + 4);
    cio[cid].rqs = pread_b(sysgen_p + 8);
    cio[cid].cqs = pread_b(sysgen_p + 9);
    cio[cid].ivec = pread_b(sysgen_p + 10);
    cio[cid].no_rque = pread_b(sysgen_p + 11);

    sim_debug(IO_DBG, &cpu_dev,
              "[SYSGEN]  sysgen rqp = %08x\n",
              cio[cid].rqp);
    sim_debug(IO_DBG, &cpu_dev,
              "[SYSGEN]  sysgen cqp = %08x\n",
              cio[cid].cqp);
    sim_debug(IO_DBG, &cpu_dev,
              "[SYSGEN]  sysgen rqs = %02x\n",
              cio[cid].rqs);
    sim_debug(IO_DBG, &cpu_dev,
              "[SYSGEN]  sysgen cqs = %02x\n",
              cio[cid].cqs);
    sim_debug(IO_DBG, &cpu_dev,
              "[SYSGEN]  sysgen ivec = %02x\n",
              cio[cid].ivec);
    sim_debug(IO_DBG, &cpu_dev,
              "[SYSGEN]  sysgen no_rque = %02x\n",
              cio[cid].no_rque);

    /* If the card has a custom sysgen handler, run it */
    if (cio[cid].sysgen != NULL) {
        cio[cid].sysgen(cid);
    } else {
        sim_debug(IO_DBG, &cpu_dev,
                  "[%08x] [cio_sysgen] Not running custom sysgen.\n",
                  R[NUM_PC]);
    }
}

void cio_cexpress(uint8 cid, uint16 esize, cio_entry *cqe, uint8 *app_data)
{
    int32 i;
    uint32 cqp;

    cqp = cio[cid].cqp;

    sim_debug(IO_DBG, &cpu_dev,
              "[%08x] [cio_cexpress] cqp = %08x seqbit = %d\n",
              R[NUM_PC], cqp, cio[cid].seqbit);

    cio[cid].seqbit ^= 1;

    cqe->subdevice |= (cio[cid].seqbit << 6);

    pwrite_h(cqp,     cqe->byte_count);
    pwrite_b(cqp + 2, cqe->subdevice);
    pwrite_b(cqp + 3, cqe->opcode);
    pwrite_w(cqp + 4, cqe->address);

    /* Write application-specific data. */
    for (i = 0; i < (esize - QESIZE); i++) {
        pwrite_b(cqp + 8 + i, app_data[i]);
    }
}

void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize,
                cio_entry *cqe, uint8 *app_data)
{
    int32 i;
    uint32 cqp, top;
    uint16 lp, ulp;

    /* Apply the CMD/STAT bit */
    cqe->subdevice |= (cmd_stat << 7);

    /* Get the physical address of the completion queue
     * in main memory */
    cqp = cio[cid].cqp;

    /* Get the physical address of the first entry in
     * the completion queue */
    top = cqp + esize + LUSIZE;

    /* Get the load pointer. This is a 16-bit absolute offset
     * from the top of the queue to the start of the entry. */
    lp = pread_h(cqp + esize);
    ulp = pread_h(cqp + esize + 2);

    /* Load the entry at the supplied address */
    pwrite_h(top + lp,     cqe->byte_count);
    pwrite_b(top + lp + 2, cqe->subdevice);
    pwrite_b(top + lp + 3, cqe->opcode);
    pwrite_w(top + lp + 4, cqe->address);

    /* Write application-specific data. */
    for (i = 0; i < (esize - QESIZE); i++) {
        pwrite_b(top + lp + 8 + i, app_data[i]);
    }

    /* Increment the load pointer to the next queue location.
     * If we go past the end of the queue, wrap around to the
     * start of the queue */
    if (cio[cid].cqs > 0) {
        lp = (lp + esize) % (esize * cio[cid].cqs);

        /* Store it back to the correct location */
        pwrite_h(cqp + esize, lp);
    } else {
        sim_debug(IO_DBG, &cpu_dev,
                  "[%08x] [cio_cqueue] ERROR! Completion Queue Size is 0!",
                  R[NUM_PC]);
    }
}

/*
 * Retrieve the Express Entry from the Request Queue
 */
void cio_rexpress(uint8 cid, uint16 esize, cio_entry *rqe, uint8 *app_data)
{
    int32 i;
    uint32 rqp;

    rqp = cio[cid].rqp;

    /* Unload the express entry from the request queue */
    rqe->byte_count  = pread_h(rqp);
    rqe->subdevice   = pread_b(rqp + 2);
    rqe->opcode      = pread_b(rqp + 3);
    rqe->address     = pread_w(rqp + 4);

    for (i = 0; i < (esize - QESIZE); i++) {
        app_data[i] = pread_b(rqp + 8 + i);
    }
}

/*
 * Retrieve an entry from the Request Queue. This function
 * returns the load pointer that points to the NEXT available slot.
 * This may be used by callers to determine which queue(s) need to
 * be serviced.
 *
 * Returns SCPE_OK on success, or SCPE_NXM if no entry was found.
 */
t_stat cio_rqueue(uint8 cid, uint8 qnum, uint16 esize,
                  cio_entry *rqe, uint8 *app_data)
{
    int32 i;
    uint32 rqp, top;
    uint16 lp, ulp;

    /* Get the physical address of the request queue in main memory */
    rqp = cio[cid].rqp +
        esize +
        (qnum * (LUSIZE + (esize * cio[cid].rqs)));

    lp = pread_h(rqp);
    ulp = pread_h(rqp + 2);

    if (lp == ulp) {
        return SCPE_NXM;
    }

    top = rqp + LUSIZE;

    /* Retrieve the entry at the supplied address */
    rqe->byte_count  = pread_h(top + ulp);
    rqe->subdevice   = pread_b(top + ulp + 2);
    rqe->opcode      = pread_b(top + ulp + 3);
    rqe->address     = pread_w(top + ulp + 4);

    /* Read application-specific data. */
    for (i = 0; i < (esize - QESIZE); i++) {
        app_data[i] = pread_b(top + ulp + 8 + i);
    }

    /* Increment the unload pointer to the next queue location.  If we
     * go past the end of the queue, wrap around to the start of the
     * queue */
    if (cio[cid].rqs > 0) {
        ulp = (ulp + esize) % (esize * cio[cid].rqs);
        pwrite_h(rqp + 2, ulp);
    }

    return SCPE_OK;
}

/*
 * Return the Load Pointer for the given request queue
 */
uint16 cio_r_lp(uint8 cid, uint8 qnum, uint16 esize)
{
    uint32 rqp;

    rqp = cio[cid].rqp +
        esize +
        (qnum * (LUSIZE + (esize * cio[cid].rqs)));

    return pread_h(rqp);
}

/*
 * Return the Unload Pointer for the given request queue
 */
uint16 cio_r_ulp(uint8 cid, uint8 qnum, uint16 esize)
{
    uint32 rqp;

    rqp = cio[cid].rqp +
        esize +
        (qnum * (LUSIZE + (esize * cio[cid].rqs)));

    return pread_h(rqp + 2);
}

uint16 cio_c_lp(uint8 cid, uint16 esize)
{
    uint32 cqp;
    cqp = cio[cid].cqp + esize;
    return pread_h(cqp);
}

uint16 cio_c_ulp(uint8 cid, uint16 esize)
{
    uint32 cqp;
    cqp = cio[cid].cqp + esize;
    return pread_h(cqp + 2);
}

/*
 * Returns true if there is room in the completion queue
 * for a new entry.
 */
t_bool cio_cqueue_avail(uint8 cid, uint16 esize)
{
    uint32 lp, ulp;

    lp = pread_h(cio[cid].cqp + esize);
    ulp = pread_h(cio[cid].cqp + esize + 2);

    return(((lp + esize) % (cio[cid].cqs * esize)) != ulp);
}

uint32 io_read(uint32 pa, size_t size)
{
    struct iolink *p;
    uint8 cid, reg, data;

    /* Special devices */
    if (pa == MEMSIZE_REG) {

        /* The following values map to memory sizes:
           0x00: 512KB (  524,288 B)
           0x01: 2MB   (2,097,152 B)
           0x02: 1MB   (1,048,576 B)
           0x03: 4MB   (4,194,304 B)
        */
        switch(MEM_SIZE) {
        case 0x80000:  /* 512KB */
            return 0;
        case 0x100000: /* 1MB */
            return 2;
        case 0x200000: /* 2MB */
            return 1;
        case 0x400000: /* 4MB */
            return 3;
        default:
            return 0;
        }
    }

    /* CIO board area */
    if (pa >= CIO_BOTTOM && pa < CIO_TOP) {
        cid = CID(pa);
        reg = pa - CADDR(cid);

        if (cio[cid].id == 0) {
            /* Nothing lives here */
            sim_debug(IO_DBG, &cpu_dev,
                      "[READ] [%08x] No card at cid=%d reg=%d\n",
                      R[NUM_PC], cid, reg);
            csr_data |= CSRTIMO;
            cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
            return 0;
        }

        /* A normal SYSGEN sequence is: RESET -> INT0 -> INT1.
         * However, there's a bug in the 3B2/400 DGMON test suite that
         * runs on every startup. This diagnostic code performs a
         * SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle
         * both orders. */

        switch (reg) {
        case IOF_ID:
        case IOF_VEC:
            switch(cio[cid].sysgen_s) {
            case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
            case CIO_INT0: /* We've seen an INT0 but not an INT1. */
                cio[cid].sysgen_s |= CIO_INT0;
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT0) ID\n",
                          R[NUM_PC], cid);
                /* Return the correct byte of our board ID */
                if (reg == IOF_ID) {
                    data = (cio[cid].id >> 8) & 0xff;
                } else {
                    data = (cio[cid].id & 0xff);
                }
                break;
            case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */
                cio[cid].sysgen_s |= CIO_INT0;
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT0) SYSGEN\n",
                          R[NUM_PC], cid);
                cio_sysgen(cid);
                data = cio[cid].ivec;
                break;
            case CIO_SYSGEN: /* We've already sysgen'ed */
                cio[cid].sysgen_s |= CIO_INT0; /* This must come BEFORE the exp_handler */
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT0) EXPRESS JOB\n",
                          R[NUM_PC], cid);
                cio[cid].exp_handler(cid);
                data = cio[cid].ivec;
                break;
            default:
                /* This should never happen */
                stop_reason = STOP_ERR;
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n",
                          R[NUM_PC], cid, cio[cid].sysgen_s);
                data = 0;
                break;
            }

            return data;
        case IOF_CTRL:
            switch(cio[cid].sysgen_s) {
            case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
            case CIO_INT1:     /* We've seen an INT1 but not an INT0 */
                /* There's nothing to do in this instance */
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT1) IGNORED\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT1;
                break;
            case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT1) SYSGEN\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT1;
                cio_sysgen(cid);
                break;
            case CIO_SYSGEN: /* We've already sysgen'ed */
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT1) FULL\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT1; /* This must come BEFORE the full handler */
                cio[cid].full_handler(cid);
                break;
            default:
                /* This should never happen */
                stop_reason = STOP_ERR;
                sim_debug(IO_DBG, &cpu_dev,
                          "[READ] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n",
                          R[NUM_PC], cid, cio[cid].sysgen_s);
                break;
            }

            return 0; /* Data returned is arbitrary */
        case IOF_STAT:
            sim_debug(IO_DBG, &cpu_dev,
                      "[READ] [%08x] (%d RESET)\n",
                      R[NUM_PC], cid);
            cio[cid].sysgen_s = 0;
            return 0; /* Data returned is arbitrary */
        default:
            /* We should never reach here, but if we do, there's
             * nothing listening. */
            sim_debug(IO_DBG, &cpu_dev,
                      "[READ] [%08x] No card at cid=%d reg=%d\n",
                      R[NUM_PC], cid, reg);
            csr_data |= CSRTIMO;
            cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
            return 0;
        }
    }

    /* Memory-mapped IO devices */
    for (p = &iotable[0]; p->low != 0; p++) {
        if ((pa >= p->low) && (pa < p->high) && p->read) {
            return p->read(pa, size);
        }
    }

    /* Not found. */
    sim_debug(IO_DBG, &cpu_dev,
              "[%08x] [io_read] ADDR=%08x: No device found.\n",
              R[NUM_PC], pa);
    csr_data |= CSRTIMO;
    cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
    return 0;
}

void io_write(uint32 pa, uint32 val, size_t size)
{
    struct iolink *p;
    uint8 cid, reg;

    /* Feature Card Area */
    if (pa >= CIO_BOTTOM && pa < CIO_TOP) {
        cid = CID(pa);
        reg = pa - CADDR(cid);

        if (cio[cid].id == 0) {
            /* Nothing lives here */
            sim_debug(IO_DBG, &cpu_dev,
                      "[WRITE] [%08x] No card at cid=%d reg=%d\n",
                      R[NUM_PC], cid, reg);
            csr_data |= CSRTIMO;
            cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
            return;
        }

        /* A normal SYSGEN sequence is: RESET -> INT0 -> INT1.
         * However, there's a bug in the 3B2/400 DGMON test suite that
         * runs on every startup. This diagnostic code performs a
         * SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle
         * both orders. */

        switch (reg) {
        case IOF_ID:
        case IOF_VEC:
            switch(cio[cid].sysgen_s) {
            case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
            case CIO_INT0:     /* We've seen an INT0 but not an INT1. */
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT0) ID\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT0;
                break;
            case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT0) SYSGEN\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT0;
                cio_sysgen(cid);
                break;
            case CIO_SYSGEN: /* We've already sysgen'ed */
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT0) EXPRESS JOB\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT0;
                cio[cid].exp_handler(cid);
                break;
            default:
                /* This should never happen */
                stop_reason = STOP_ERR;
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n",
                          R[NUM_PC], cid, cio[cid].sysgen_s);
                break;
            }

            return;
        case IOF_CTRL:
            switch(cio[cid].sysgen_s) {
            case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
            case CIO_INT1:     /* We've seen an INT1 but not an INT0 */
                /* There's nothing to do in this instance */
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT1) IGNORED\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT1;
                break;
            case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT1) SYSGEN\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT1;
                cio_sysgen(cid);
                break;
            case CIO_SYSGEN: /* We've already sysgen'ed */
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT1) FULL\n",
                          R[NUM_PC], cid);
                cio[cid].sysgen_s |= CIO_INT1;
                cio[cid].full_handler(cid);
                break;
            default:
                /* This should never happen */
                stop_reason = STOP_ERR;
                sim_debug(IO_DBG, &cpu_dev,
                          "[WRITE] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n",
                          R[NUM_PC], cid, cio[cid].sysgen_s);
                break;
            }

            return;
        case IOF_STAT:
            sim_debug(IO_DBG, &cpu_dev,
                      "[WRITE] [%08x] (%d RESET)\n",
                      R[NUM_PC], cid);
            cio[cid].sysgen_s = 0;
            return;
        default:
            /* We should never reach here, but if we do, there's
             * nothing listening. */
            sim_debug(IO_DBG, &cpu_dev,
                      "[WRITE] [%08x] No card at cid=%d reg=%d\n",
                      R[NUM_PC], cid, reg);
            csr_data |= CSRTIMO;
            cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
            return;
        }
    }

    /* Memory-mapped IO devices */
    for (p = &iotable[0]; p->low != 0; p++) {
        if ((pa >= p->low) && (pa < p->high) && p->write) {
            p->write(pa, val, size);
            return;
        }
    }

    /* Not found. */
    sim_debug(IO_DBG, &cpu_dev,
              "[%08x] [io_write] ADDR=%08x: No device found.\n",
              R[NUM_PC], pa);
    csr_data |= CSRTIMO;
    cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
}


/* For debugging only */
void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type,
                uint16 esize, cio_entry *entry, uint8 *app_data)
{
    sim_debug(dbits, dev,
              "*** %s ENTRY: byte_count=%04x, subdevice=%02x, opcode=%d, address=%08x\n",
              type, entry->byte_count, entry->subdevice,
              entry->opcode, entry->address);
}
