/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Intel UPI-42/MCS-48 microcontroller emulation.
 *
 * Authors: RichardG, <richardg867@gmail.com>
 *
 *          Copyright 2022 RichardG.
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <86box/plat_unused.h>

#ifdef UPI42_STANDALONE
#    define fatal(...)              \
        {                           \
            upi42_log(__VA_ARGS__); \
            abort();                \
        }
#    define upi42_log(...)       \
        {                        \
            printf(__VA_ARGS__); \
            fflush(stdout);      \
        }
#else
#    include <stdarg.h>
#    define HAVE_STDARG_H
#    include <86box/86box.h>
#    include <86box/device.h>
#    include <86box/io.h>
#    include <86box/timer.h>

#    ifdef ENABLE_UPI42_LOG
int upi42_do_log = ENABLE_UPI42_LOG;

void
upi42_log(const char *fmt, ...)
{
    va_list ap;

    if (upi42_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#    else
#        define upi42_log(fmt, ...)
#    endif
#endif

#define UPI42_REG(upi42, r, op) ((upi42->psw & 0x10) ? (upi42->ram[24 + ((r) &7)] op) : (upi42->ram[(r) &7] op))

#define UPI42_ROM_SHIFT         0  /* actually the mask */
#define UPI42_RAM_SHIFT         16 /* actually the mask */
#define UPI42_TYPE_MCS          (0 << 24)
#define UPI42_TYPE_UPI          (1 << 24)
#define UPI42_EXT_C42           (1 << 25)

#define UPI42_8048              ((1023 << UPI42_ROM_SHIFT) | (63 << UPI42_RAM_SHIFT) | UPI42_TYPE_MCS)
#define UPI42_8049              ((2047 << UPI42_ROM_SHIFT) | (127 << UPI42_RAM_SHIFT) | UPI42_TYPE_MCS)
#define UPI42_8041              ((1023 << UPI42_ROM_SHIFT) | (127 << UPI42_RAM_SHIFT) | UPI42_TYPE_UPI)
#define UPI42_8042              ((2047 << UPI42_ROM_SHIFT) | (255 << UPI42_RAM_SHIFT) | UPI42_TYPE_UPI)
#define UPI42_80C42             ((4095 << UPI42_ROM_SHIFT) | (255 << UPI42_RAM_SHIFT) | UPI42_TYPE_UPI | UPI42_EXT_C42)

typedef struct _upi42_ {
    int (*ops[256])(struct _upi42_ *upi42, uint32_t fetchdat);
    uint32_t type;
    uint8_t  ram[256], *rom,       /* memory */
        ports_in[8], ports_out[8], /* I/O ports */
        dbb_in, dbb_out;           /* UPI-42 data buffer */

    uint8_t rammask, /* RAM mask */
        a,           /* accumulator */
        t,           /* timer counter */
        psw,         /* program status word */
        sts;         /* UPI-42 status */

    uint16_t pc, rommask; /* program counter and ROM mask */

    unsigned int prescaler : 5, tf : 1, skip_timer_inc : 1, /* timer/counter */
        run_timer : 1, run_counter : 1, tcnti : 1,          /* timer/counter enables */
        i : 1, i_raise : 1, tcnti_raise : 1, irq_mask : 1,  /* interrupts */
        t0 : 1, t1 : 1,                                     /* T0/T1 signals */
        flags : 1, dbf : 1, suspend : 1;                    /* UPI-42 flags */

    int cycs; /* cycle counter */

#ifndef UPI42_STANDALONE
    uint8_t  ram_index;
    uint16_t rom_index;
#endif
} upi42_t;

static inline void
upi42_mirror_f0(upi42_t *upi42)
{
    /* Update status register F0 flag to match PSW F0 flag. */
    upi42->sts = ((upi42->psw & 0x20) >> 3) | (upi42->sts & ~0x04);
}

static int
upi42_op_MOV_A_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a = UPI42_REG(upi42, fetchdat, );
    return 1;
}

static int
upi42_op_MOV_Rr_A(upi42_t *upi42, uint32_t fetchdat)
{
    UPI42_REG(upi42, fetchdat, = upi42->a);
    return 1;
}

static int
upi42_op_MOV_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a = upi42->ram[upi42->ram[fetchdat & 1] & upi42->rammask];
    return 1;
}

static int
upi42_op_MOV_indRr_A(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ram[upi42->ram[fetchdat & 1] & upi42->rammask] = upi42->a;
    return 1;
}

static int
upi42_op_MOV_Rr_imm(upi42_t *upi42, uint32_t fetchdat)
{
    UPI42_REG(upi42, fetchdat, = fetchdat >> 8);
    upi42->cycs--;
    return 2;
}

static int
upi42_op_MOV_indRr_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ram[upi42->ram[fetchdat & 1] & upi42->rammask] = fetchdat >> 8;
    upi42->cycs--;
    return 2;
}

static int
upi42_op_MOV_A_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a = fetchdat >> 8;
    upi42->cycs--;
    return 2;
}

static int
upi42_op_MOV_A_PSW(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = upi42->psw;
    upi42_mirror_f0(upi42);
    return 1;
}

static int
upi42_op_MOV_PSW_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->psw = upi42->a;
    upi42_mirror_f0(upi42);
    return 1;
}

static int
upi42_op_MOV_A_T(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = upi42->t;
    return 1;
}

static int
upi42_op_MOV_T_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->t = upi42->a;
    return 1;
}

static int
upi42_op_MOV_STS_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->sts = (upi42->a & 0xf0) | (upi42->sts & 0x0f);
    return 1;
}

static int
upi42_op_MOVP_A_indA(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = upi42->rom[(upi42->pc & 0xff00) | upi42->a];
    upi42->cycs--;
    return 1;
}

static int
upi42_op_MOVP3_A_indA(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = upi42->rom[0x300 | upi42->a];
    upi42->cycs--;
    return 1;
}

static int
upi42_op_XCH_A_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    uint8_t temp = upi42->a;
    upi42->a     = UPI42_REG(upi42, fetchdat, );
    UPI42_REG(upi42, fetchdat, = temp);
    return 1;
}

static int
upi42_op_XCH_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    uint8_t temp = upi42->a;
    uint8_t addr = upi42->ram[fetchdat & 1] & upi42->rammask;
    upi42->a         = upi42->ram[addr];
    upi42->ram[addr] = temp;
    return 1;
}

static int
upi42_op_XCHD_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    uint8_t temp = upi42->a;
    uint8_t addr = upi42->ram[fetchdat & 1] & upi42->rammask;
    upi42->a         = (upi42->a & 0xf0) | (upi42->ram[addr] & 0x0f);
    upi42->ram[addr] = (upi42->ram[addr] & 0xf0) | (temp & 0x0f);
    return 1;
}

static int
upi42_op_SWAP_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = (upi42->a << 4) | (upi42->a >> 4);
    return 1;
}

static int
upi42_op_IN_A_Pp(upi42_t *upi42, uint32_t fetchdat)
{
    int port = fetchdat & 3;
    upi42->a = upi42->ports_in[port] & upi42->ports_out[port];
    upi42->cycs--;
    return 1;
}

static int
upi42_op_IN_A_DBB(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = upi42->dbb_in;
    upi42->sts &= ~0x02; /* clear IBF */
    return 1;
}

static int
upi42_op_OUTL_Pp_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->ports_out[fetchdat & 3] = upi42->a;
    upi42->cycs--;
    return 1;
}

static int
upi42_op_OUT_DBB_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->dbb_out = upi42->a;
    upi42->sts |= 0x01; /* set OBF */
    return 1;
}

static int
upi42_op_MOVD_A_Pp(upi42_t *upi42, uint32_t fetchdat)
{
    int port = 4 | (fetchdat & 3);
    upi42->a = (upi42->ports_in[port] & upi42->ports_out[port]) & 0x0f;
    upi42->cycs--;
    return 1;
}

static int
upi42_op_MOVD_Pp_A(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ports_out[4 | (fetchdat & 3)] = upi42->a & 0x0f;
    upi42->cycs--;
    return 1;
}

static int
upi42_op_ANL_A_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a &= UPI42_REG(upi42, fetchdat, );
    return 1;
}

static int
upi42_op_ORL_A_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a |= UPI42_REG(upi42, fetchdat, );
    return 1;
}

static int
upi42_op_XRL_A_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a ^= UPI42_REG(upi42, fetchdat, );
    return 1;
}

static int
upi42_op_ANL_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a &= upi42->ram[upi42->ram[fetchdat & 1] & upi42->rammask];
    return 1;
}

static int
upi42_op_ORL_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a |= upi42->ram[upi42->ram[fetchdat & 1] & upi42->rammask];
    return 1;
}

static int
upi42_op_XRL_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a ^= upi42->ram[upi42->ram[fetchdat & 1] & upi42->rammask];
    return 1;
}

static int
upi42_op_ANL_A_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a &= fetchdat >> 8;
    upi42->cycs--;
    return 2;
}

static int
upi42_op_ORL_A_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a |= fetchdat >> 8;
    upi42->cycs--;
    return 2;
}

static int
upi42_op_XRL_A_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->a ^= fetchdat >> 8;
    upi42->cycs--;
    return 2;
}

static int
upi42_op_ANL_Pp_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ports_out[fetchdat & 3] &= fetchdat >> 8;
    upi42->cycs--;
    return 2;
}

static int
upi42_op_ORL_Pp_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ports_out[fetchdat & 3] |= fetchdat >> 8;
    upi42->cycs--;
    return 2;
}

static int
upi42_op_ANLD_Pp_A(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ports_out[4 | (fetchdat & 3)] &= upi42->a;
    upi42->cycs--;
    return 1;
}

static int
upi42_op_ORLD_Pp_A(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ports_out[4 | (fetchdat & 3)] |= upi42->a;
    upi42->cycs--;
    return 1;
}

static int
upi42_op_RR_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = (upi42->a << 7) | (upi42->a >> 1);
    return 1;
}

static int
upi42_op_RL_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = (upi42->a >> 7) | (upi42->a << 1);
    return 1;
}

static int
upi42_op_RRC_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    uint8_t temp = upi42->a;
    upi42->a     = (upi42->psw & 0x80) | (temp >> 1);
    upi42->psw   = (temp << 7) | (upi42->psw & ~0x80);
    return 1;
}

static int
upi42_op_RLC_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    uint8_t temp = upi42->a;
    upi42->a     = (temp << 1) | (upi42->psw >> 7);
    upi42->psw   = (temp & 0x80) | (upi42->psw & ~0x80);
    return 1;
}

static int
upi42_op_INC_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a++;
    return 1;
}

static int
upi42_op_INC_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    UPI42_REG(upi42, fetchdat, ++);
    return 1;
}

static int
upi42_op_INC_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->ram[upi42->ram[fetchdat & 1] & upi42->rammask]++;
    return 1;
}

static int
upi42_op_DEC_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a--;
    return 1;
}

static int
upi42_op_DEC_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    UPI42_REG(upi42, fetchdat, --);
    return 1;
}

static int
upi42_op_DJNZ_Rr_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->cycs--;
    UPI42_REG(upi42, fetchdat, --);
    if (UPI42_REG(upi42, fetchdat, )) {
        upi42->pc = (upi42->pc & 0xff00) | ((fetchdat >> 8) & 0xff);
        return 0;
    } else {
        return 2;
    }
}

static int
upi42_op_ADD_A_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    int res    = upi42->a + UPI42_REG(upi42, fetchdat, );
    upi42->a   = res;
    upi42->psw = ((res >> 1) & 0x80) | (upi42->psw & ~0x80);
    return 1;
}

static int
upi42_op_ADDC_A_Rr(upi42_t *upi42, uint32_t fetchdat)
{
    int res    = upi42->a + (upi42->psw >> 7) + UPI42_REG(upi42, fetchdat, );
    upi42->a   = res;
    upi42->psw = ((res >> 1) & 0x80) | (upi42->psw & ~0x80);
    return 1;
}

static int
upi42_op_ADD_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    int res    = upi42->a + upi42->ram[UPI42_REG(upi42, fetchdat, ) & upi42->rammask];
    upi42->a   = res;
    upi42->psw = ((res >> 1) & 0x80) | (upi42->psw & ~0x80);
    return 1;
}

static int
upi42_op_ADDC_A_indRr(upi42_t *upi42, uint32_t fetchdat)
{
    int res    = upi42->a + (upi42->psw >> 7) + upi42->ram[UPI42_REG(upi42, fetchdat, ) & upi42->rammask];
    upi42->a   = res;
    upi42->psw = ((res >> 1) & 0x80) | (upi42->psw & ~0x80);
    return 1;
}

static int
upi42_op_ADD_A_imm(upi42_t *upi42, uint32_t fetchdat)
{
    int res    = upi42->a + (fetchdat >> 8);
    upi42->a   = res;
    upi42->psw = ((res >> 1) & 0x80) | (upi42->psw & ~0x80);
    upi42->cycs--;
    return 2;
}

static int
upi42_op_ADDC_A_imm(upi42_t *upi42, uint32_t fetchdat)
{
    int res    = upi42->a + (upi42->psw >> 7) + (fetchdat >> 8);
    upi42->a   = res;
    upi42->psw = ((res >> 1) & 0x80) | (upi42->psw & ~0x80);
    upi42->cycs--;
    return 2;
}

static int
upi42_op_CLR_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = 0;
    return 1;
}

static int
upi42_op_CPL_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->a = ~upi42->a;
    return 1;
}

static int
upi42_op_DA_A(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    if (((upi42->a & 0x0f) > 9) || (upi42->psw & 0x40))
        upi42->a += 6;
    if (((upi42->a >> 4) > 9) || (upi42->psw & 0x80)) {
        int res    = upi42->a + (6 << 4);
        upi42->a   = res;
        upi42->psw = ((res >> 1) & 0x80) | (upi42->psw & ~0x80);
    }
    return 1;
}

static int
upi42_op_CLR_C(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->psw &= ~0x80;
    return 1;
}

static int
upi42_op_CPL_C(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->psw ^= 0x80;
    return 1;
}

static int
upi42_op_CLR_F0(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->psw &= ~0x20;
    upi42_mirror_f0(upi42);
    return 1;
}

static int
upi42_op_CPL_F0(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->psw ^= 0x20;
    upi42_mirror_f0(upi42);
    return 1;
}

static int
upi42_op_CLR_F1(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->sts &= ~0x08;
    return 1;
}

static int
upi42_op_CPL_F1(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->sts ^= 0x08;
    return 1;
}

static int
upi42_op_EN_I(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->i              = 1;
    upi42->skip_timer_inc = 1;
    return 1;
}

static int
upi42_op_DIS_I(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->i              = 0;
    upi42->skip_timer_inc = 1;
    return 1;
}

static int
upi42_op_EN_TCNTI(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->tcnti = 1;
    return 1;
}

static int
upi42_op_DIS_TCNTI(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->tcnti = upi42->tcnti_raise = 0;
    return 1;
}

static int
upi42_op_STRT_T(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->run_timer      = 1;
    upi42->prescaler      = 0;
    upi42->skip_timer_inc = 1;
    return 1;
}

static int
upi42_op_STRT_CNT(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->run_counter    = 1;
    upi42->skip_timer_inc = 1;
    return 1;
}

static int
upi42_op_STOP_TCNT(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->run_timer = upi42->run_counter = 0;
    upi42->skip_timer_inc                 = 1;
    return 1;
}

static int
upi42_op_SEL_PMB0(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->dbf = 0;
    return 1;
}

static int
upi42_op_SEL_PMB1(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->dbf = 1;
    return 1;
}

static int
upi42_op_SEL_RB0(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->psw &= ~0x10;
    return 1;
}

static int
upi42_op_SEL_RB1(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->psw |= 0x10;
    return 1;
}

static int
upi42_op_NOP(UNUSED(upi42_t *upi42), UNUSED(uint32_t fetchdat))
{
    return 1;
}

static int
upi42_op_CALL_imm(upi42_t *upi42, uint32_t fetchdat)
{
    /* Push new frame onto stack. */
    uint8_t sp           = (upi42->psw & 0x07) << 1;
    upi42->ram[8 + sp++] = upi42->pc + 2; /* stack frame format is undocumented! */
    upi42->ram[8 + sp++] = (upi42->psw & 0xf0) | ((upi42->pc >> 8) & 0x07);
    upi42->psw           = (upi42->psw & 0xf8) | (sp >> 1);

    /* Load new program counter. */
    upi42->pc = (upi42->dbf << 11) | ((fetchdat << 3) & 0x0700) | ((fetchdat >> 8) & 0x00ff);

    upi42->cycs--;
    return 0;
}

static int
upi42_op_RET(upi42_t *upi42, uint32_t fetchdat)
{
    /* Pop frame off the stack. */
    uint8_t sp     = (upi42->psw & 0x07) << 1;
    uint8_t frame1 = upi42->ram[8 + --sp];
    uint8_t frame0 = upi42->ram[8 + --sp];
    upi42->psw     = (upi42->psw & 0xf8) | (sp >> 1);

    /* Load new program counter. */
    upi42->pc = ((frame1 & 0x0f) << 8) | frame0;

    /* Load new Program Status Word and unmask interrupts if this is RETR. */
    if (fetchdat & 0x10) {
        upi42->psw = (frame1 & 0xf0) | (upi42->psw & 0x0f);
        upi42_mirror_f0(upi42);

        upi42->irq_mask = 0;
    }

    upi42->cycs--;
    return 0;
}

static int
upi42_op_JMP_imm(upi42_t *upi42, uint32_t fetchdat)
{
    upi42->pc = (upi42->dbf << 11) | ((fetchdat << 3) & 0x0700) | ((fetchdat >> 8) & 0x00ff);
    upi42->cycs--;
    return 0;
}

static int
upi42_op_JMPP_indA(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->pc = (upi42->pc & 0xff00) | upi42->a;
    upi42->cycs--;
    return 0;
}

#define UPI42_COND_JMP_IMM(insn, cond, post)                               \
    static int                                                             \
        upi42_op_##insn##_imm(upi42_t *upi42, uint32_t fetchdat)           \
    {                                                                      \
        if (cond)                                                          \
            upi42->pc = (upi42->pc & 0xff00) | ((fetchdat >> 8) & 0x00ff); \
        post;                                                              \
        upi42->cycs--;                                                     \
        return 2 * !(cond);                                                \
    }
UPI42_COND_JMP_IMM(JC, upi42->psw & 0x80, )
UPI42_COND_JMP_IMM(JNC, !(upi42->psw & 0x80), )
UPI42_COND_JMP_IMM(JZ, !upi42->a, )
UPI42_COND_JMP_IMM(JNZ, upi42->a, )
UPI42_COND_JMP_IMM(JT0, upi42->t0, )
UPI42_COND_JMP_IMM(JNT0, !upi42->t0, )
UPI42_COND_JMP_IMM(JT1, upi42->t1, )
UPI42_COND_JMP_IMM(JNT1, !upi42->t1, )
UPI42_COND_JMP_IMM(JF0, upi42->psw & 0x20, )
UPI42_COND_JMP_IMM(JF1, upi42->sts & 0x08, )
UPI42_COND_JMP_IMM(JTF, !upi42->tf, upi42->tf = 0)
UPI42_COND_JMP_IMM(JBb, upi42->a & (1 << ((fetchdat >> 5) & 7)), )
UPI42_COND_JMP_IMM(JNIBF, !(upi42->sts & 0x02), )
UPI42_COND_JMP_IMM(JOBF, upi42->sts & 0x01, )

static int
upi42_op_EN_A20(UNUSED(upi42_t *upi42), UNUSED(uint32_t fetchdat))
{
    /* Enable fast A20 until reset. */
    return 1;
}

static int
upi42_op_EN_DMA(UNUSED(upi42_t *upi42), UNUSED(uint32_t fetchdat))
{
    return 1;
}

static int
upi42_op_EN_FLAGS(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    upi42->flags = 1;
    return 1;
}

static int
upi42_op_SUSPEND(upi42_t *upi42, UNUSED(uint32_t fetchdat))
{
    /* Inhibit execution until reset. */
    upi42->suspend = 1;
    return 1;
}

static const int (*ops_80c42[256])(upi42_t *upi42, uint32_t fetchdat) = {
    // clang-format off
             /* 0 / 8 */            /* 1 / 9 */            /* 2 / a */            /* 3 / b */            /* 4 / c */            /* 5 / d */            /* 6 / e */            /* 7 / f */
    /* 00 */ upi42_op_NOP,          NULL,                  upi42_op_OUT_DBB_A,    upi42_op_ADD_A_imm,    upi42_op_JMP_imm,      upi42_op_EN_I,         NULL,                  upi42_op_DEC_A,
    /* 08 */ upi42_op_IN_A_Pp,      upi42_op_IN_A_Pp,      upi42_op_IN_A_Pp,      NULL,                  upi42_op_MOVD_A_Pp,    upi42_op_MOVD_A_Pp,    upi42_op_MOVD_A_Pp,    upi42_op_MOVD_A_Pp,
    /* 10 */ upi42_op_INC_indRr,    upi42_op_INC_indRr,    upi42_op_JBb_imm,      upi42_op_ADDC_A_imm,   upi42_op_CALL_imm,     upi42_op_DIS_I,        upi42_op_JTF_imm,      upi42_op_INC_A,
    /* 18 */ upi42_op_INC_Rr,       upi42_op_INC_Rr,       upi42_op_INC_Rr,       upi42_op_INC_Rr,       upi42_op_INC_Rr,       upi42_op_INC_Rr,       upi42_op_INC_Rr,       upi42_op_INC_Rr,
    /* 20 */ upi42_op_XCH_A_indRr,  upi42_op_XCH_A_indRr,  upi42_op_IN_A_DBB,     upi42_op_MOV_A_imm,    upi42_op_JMP_imm,      upi42_op_EN_TCNTI,     upi42_op_JNT0_imm,     upi42_op_CLR_A,
    /* 28 */ upi42_op_XCH_A_Rr,     upi42_op_XCH_A_Rr,     upi42_op_XCH_A_Rr,     upi42_op_XCH_A_Rr,     upi42_op_XCH_A_Rr,     upi42_op_XCH_A_Rr,     upi42_op_XCH_A_Rr,     upi42_op_XCH_A_Rr,
    /* 30 */ upi42_op_XCHD_A_indRr, upi42_op_XCHD_A_indRr, upi42_op_JBb_imm,      upi42_op_EN_A20,       upi42_op_CALL_imm,     upi42_op_DIS_TCNTI,    upi42_op_JT0_imm,      upi42_op_CPL_A,
    /* 38 */ upi42_op_OUTL_Pp_A,    upi42_op_OUTL_Pp_A,    upi42_op_OUTL_Pp_A,    upi42_op_OUTL_Pp_A,    upi42_op_MOVD_Pp_A,    upi42_op_MOVD_Pp_A,    upi42_op_MOVD_Pp_A,    upi42_op_MOVD_Pp_A,
    /* 40 */ upi42_op_ORL_A_indRr,  upi42_op_ORL_A_indRr,  upi42_op_MOV_A_T,      upi42_op_ORL_A_imm,    upi42_op_JMP_imm,      upi42_op_STRT_CNT,     upi42_op_JNT1_imm,     upi42_op_SWAP_A,
    /* 48 */ upi42_op_ORL_A_Rr,     upi42_op_ORL_A_Rr,     upi42_op_ORL_A_Rr,     upi42_op_ORL_A_Rr,     upi42_op_ORL_A_Rr,     upi42_op_ORL_A_Rr,     upi42_op_ORL_A_Rr,     upi42_op_ORL_A_Rr,
    /* 50 */ upi42_op_ANL_A_indRr,  upi42_op_ANL_A_indRr,  upi42_op_JBb_imm,      upi42_op_ANL_A_imm,    upi42_op_CALL_imm,     upi42_op_STRT_T,       upi42_op_JT1_imm,      upi42_op_DA_A,
    /* 58 */ upi42_op_ANL_A_Rr,     upi42_op_ANL_A_Rr,     upi42_op_ANL_A_Rr,     upi42_op_ANL_A_Rr,     upi42_op_ANL_A_Rr,     upi42_op_ANL_A_Rr,     upi42_op_ANL_A_Rr,     upi42_op_ANL_A_Rr,
    /* 60 */ upi42_op_ADD_A_indRr,  upi42_op_ADD_A_indRr,  upi42_op_MOV_T_A,      upi42_op_SEL_PMB0,     upi42_op_JMP_imm,      upi42_op_STOP_TCNT,    NULL,                  upi42_op_RRC_A,
    /* 68 */ upi42_op_ADD_A_Rr,     upi42_op_ADD_A_Rr,     upi42_op_ADD_A_Rr,     upi42_op_ADD_A_Rr,     upi42_op_ADD_A_Rr,     upi42_op_ADD_A_Rr,     upi42_op_ADD_A_Rr,     upi42_op_ADD_A_Rr,
    /* 70 */ upi42_op_ADDC_A_indRr, upi42_op_ADDC_A_indRr, upi42_op_JBb_imm,      upi42_op_SEL_PMB1,     upi42_op_CALL_imm,     NULL,                  upi42_op_JF1_imm,      upi42_op_RR_A,
    /* 78 */ upi42_op_ADDC_A_Rr,    upi42_op_ADDC_A_Rr,    upi42_op_ADDC_A_Rr,    upi42_op_ADDC_A_Rr,    upi42_op_ADDC_A_Rr,    upi42_op_ADDC_A_Rr,    upi42_op_ADDC_A_Rr,    upi42_op_ADDC_A_Rr,
    /* 80 */ NULL,                  NULL,                  upi42_op_SUSPEND,      upi42_op_RET,          upi42_op_JMP_imm,      upi42_op_CLR_F0,       upi42_op_JOBF_imm,     NULL,
    /* 88 */ upi42_op_ORL_Pp_imm,   upi42_op_ORL_Pp_imm,   upi42_op_ORL_Pp_imm,   upi42_op_ORL_Pp_imm,   upi42_op_ORLD_Pp_A,    upi42_op_ORLD_Pp_A,    upi42_op_ORLD_Pp_A,    upi42_op_ORLD_Pp_A,
    /* 90 */ upi42_op_MOV_STS_A,    NULL,                  upi42_op_JBb_imm,      upi42_op_RET,          upi42_op_CALL_imm,     upi42_op_CPL_F0,       upi42_op_JNZ_imm,      upi42_op_CLR_C,
    /* 98 */ upi42_op_ANL_Pp_imm,   upi42_op_ANL_Pp_imm,   upi42_op_ANL_Pp_imm,   upi42_op_ANL_Pp_imm,   upi42_op_ANLD_Pp_A,    upi42_op_ANLD_Pp_A,    upi42_op_ANLD_Pp_A,    upi42_op_ANLD_Pp_A,
    /* a0 */ upi42_op_MOV_indRr_A,  upi42_op_MOV_indRr_A,  NULL,                  upi42_op_MOVP_A_indA,  upi42_op_JMP_imm,      upi42_op_CLR_F1,       NULL,                  upi42_op_CPL_C,
    /* a8 */ upi42_op_MOV_Rr_A,     upi42_op_MOV_Rr_A,     upi42_op_MOV_Rr_A,     upi42_op_MOV_Rr_A,     upi42_op_MOV_Rr_A,     upi42_op_MOV_Rr_A,     upi42_op_MOV_Rr_A,     upi42_op_MOV_Rr_A,
    /* b0 */ upi42_op_MOV_indRr_imm,upi42_op_MOV_indRr_imm,upi42_op_JBb_imm,      upi42_op_JMPP_indA,    upi42_op_CALL_imm,     upi42_op_CPL_F1,       upi42_op_JF0_imm,      NULL,
    /* b8 */ upi42_op_MOV_Rr_imm,   upi42_op_MOV_Rr_imm,   upi42_op_MOV_Rr_imm,   upi42_op_MOV_Rr_imm,   upi42_op_MOV_Rr_imm,   upi42_op_MOV_Rr_imm,   upi42_op_MOV_Rr_imm,   upi42_op_MOV_Rr_imm,
    /* c0 */ NULL,                  NULL,                  NULL,                  NULL,                  upi42_op_JMP_imm,      NULL,                  upi42_op_JZ_imm,       upi42_op_MOV_A_PSW,
    /* c8 */ upi42_op_DEC_Rr,       upi42_op_DEC_Rr,       upi42_op_DEC_Rr,       upi42_op_DEC_Rr,       upi42_op_DEC_Rr,       upi42_op_DEC_Rr,       upi42_op_DEC_Rr,       upi42_op_DEC_Rr,
    /* d0 */ upi42_op_XRL_A_indRr,  upi42_op_XRL_A_indRr,  upi42_op_JBb_imm,      upi42_op_XRL_A_imm,    upi42_op_CALL_imm,     NULL,                  upi42_op_JNIBF_imm,    upi42_op_MOV_PSW_A,
    /* d8 */ upi42_op_XRL_A_Rr,     upi42_op_XRL_A_Rr,     upi42_op_XRL_A_Rr,     upi42_op_XRL_A_Rr,     upi42_op_XRL_A_Rr,     upi42_op_XRL_A_Rr,     upi42_op_XRL_A_Rr,     upi42_op_XRL_A_Rr,
    /* e0 */ NULL,                  NULL,                  upi42_op_SUSPEND,      upi42_op_MOVP3_A_indA, upi42_op_JMP_imm,      upi42_op_EN_DMA,       upi42_op_JNC_imm,      upi42_op_RL_A,
    /* e8 */ upi42_op_DJNZ_Rr_imm,  upi42_op_DJNZ_Rr_imm,  upi42_op_DJNZ_Rr_imm,  upi42_op_DJNZ_Rr_imm,  upi42_op_DJNZ_Rr_imm,  upi42_op_DJNZ_Rr_imm,  upi42_op_DJNZ_Rr_imm,  upi42_op_DJNZ_Rr_imm,
    /* f0 */ upi42_op_MOV_A_indRr,  upi42_op_MOV_A_indRr,  upi42_op_JBb_imm,      NULL,                  upi42_op_CALL_imm,     upi42_op_EN_FLAGS,     upi42_op_JC_imm,       upi42_op_RLC_A,
    /* f8 */ upi42_op_MOV_A_Rr,     upi42_op_MOV_A_Rr,     upi42_op_MOV_A_Rr,     upi42_op_MOV_A_Rr,     upi42_op_MOV_A_Rr,     upi42_op_MOV_A_Rr,     upi42_op_MOV_A_Rr,     upi42_op_MOV_A_Rr
    // clang-format on
};

static void
upi42_exec(void *priv)
{
    upi42_t *upi42 = (upi42_t *) priv;

    /* Skip everything if we're suspended, or just process timer if we're in a multi-cycle instruction. */
    if (upi42->suspend)
        return;
    else if (++upi42->cycs < 0)
        goto timer;

    /* Trigger interrupt if requested. */
    if (upi42->irq_mask) {
        /* Masked, we're currently in an ISR. */
    } else if (upi42->i_raise) {
        /* External interrupt. Higher priority than the timer interrupt. */
        upi42->irq_mask = 1;
        upi42->i_raise  = 0;

        upi42->pc -= 2;
        upi42->cycs++;
        upi42_op_CALL_imm(upi42, 3 << 8);
        return;
    } else if (upi42->tcnti_raise) {
        /* Timer interrupt. */
        upi42->irq_mask    = 1;
        upi42->tcnti_raise = 0;

        upi42->pc -= 2;
        upi42->cycs++;
        upi42_op_CALL_imm(upi42, 7 << 8);
        return;
    }

    /* Fetch instruction. */
    uint32_t fetchdat = AS_U32(upi42->rom[upi42->pc]);

    /* Decode instruction. */
    uint8_t insn = fetchdat & 0xff;
    if (upi42->ops[insn]) {
        /* Execute instruction. */
        int pc_inc = upi42->ops[insn](upi42, fetchdat);

        /* Increment lower 11 bits of the program counter. */
        upi42->pc = (upi42->pc & 0xf800) | ((upi42->pc + pc_inc) & 0x07ff);

        /* Decrement cycle counter. Multi-cycle instructions also decrement within their code. */
        upi42->cycs--;
    } else {
        fatal("UPI42: Unknown opcode %02X (%08X)\n", insn, fetchdat);
        return;
    }

timer:
    /* Process timer. */
    if (!upi42->run_timer) {
        /* Timer disabled. */
    } else if (upi42->skip_timer_inc) {
        /* Some instructions don't increment the timer. */
        upi42->skip_timer_inc = 0;
    } else {
        /* Increment counter once the prescaler overflows,
           and set timer flag once the main value overflows. */
        if ((++upi42->prescaler == 0) && (++upi42->t == 0)) {
            upi42->tf = 1;

            /* Fire counter interrupt if enabled. */
            if (upi42->tcnti)
                upi42->tcnti_raise = 1;
        }
    }
}

uint8_t
upi42_port_read(void *priv, int port)
{
    const upi42_t *upi42 = (upi42_t *) priv;

    /* Read base port value. */
    port &= 7;
    uint8_t ret = upi42->ports_in[port] & upi42->ports_out[port];

    /* Apply special meanings. */
    switch (port) {
        default:
            break;
    }

    upi42_log("UPI42: port_read(%d) = %02X\n", port, ret);
    return ret;
}

void
upi42_port_write(void *priv, int port, uint8_t val)
{
    upi42_t *upi42 = (upi42_t *) priv;

    port &= 7;
    upi42_log("UPI42: port_write(%d, %02X)\n", port, val);

    /* Set input level. */
    upi42->ports_in[port] = val;
}

/* NOTE: The dbb/sts/cmd functions use I/O handler signatures; port is ignored. */

uint8_t
upi42_dbb_read(UNUSED(uint16_t port), void *priv)
{
    upi42_t *upi42 = (upi42_t *) priv;

    uint8_t ret = upi42->dbb_out;
    upi42_log("UPI42: dbb_read(%04X) = %02X\n", port, ret);
    upi42->sts &= ~0x01; /* clear OBF */
    return ret;
}

void
upi42_dbb_write(UNUSED(uint16_t port), uint8_t val, void *priv)
{
    upi42_t *upi42 = (upi42_t *) priv;

    upi42_log("UPI42: dbb_write(%04X, %02X)\n", port, val);
    upi42->dbb_in = val;
    upi42->sts    = (upi42->sts & ~0x08) | 0x02; /* clear F1 and set IBF */
    if (upi42->i)                                /* fire IBF interrupt if enabled */
        upi42->i_raise = 1;
}

uint8_t
upi42_sts_read(UNUSED(uint16_t port), void *priv)
{
    const upi42_t *upi42 = (upi42_t *) priv;

    uint8_t ret = upi42->sts;
    upi42_log("UPI42: sts_read(%04X) = %02X\n", port, ret);
    return ret;
}

void
upi42_cmd_write(UNUSED(uint16_t port), uint8_t val, void *priv)
{
    upi42_t *upi42 = (upi42_t *) priv;

    upi42_log("UPI42: cmd_write(%04X, %02X)\n", port, val);
    upi42->dbb_in = val;
    upi42->sts |= 0x0a; /* set F1 and IBF */
    if (upi42->i)       /* fire IBF interrupt if enabled */
        upi42->i_raise = 1;
}

void
upi42_reset(upi42_t *upi42)
{
    upi42->pc      = 0; /* program counter */
    upi42->psw     = 0; /* stack pointer, register bank and F0 */
    upi42->dbf     = 0; /* ROM bank */
    upi42->i       = 0; /* external interrupt */
    upi42->tcnti   = 0; /* timer/counter interrupt */
    upi42->tf      = 0; /* timer flag */
    upi42->sts     = 0; /* F1 */
    upi42->flags   = 0; /* UPI-42 buffer interrupts */
    upi42->suspend = 0; /* 80C42 suspend flag */
}

void
upi42_do_init(upi32_t type, uint8_t *rom)
{
    memset(upi42, 0x00, sizeof(upi42_t));
    upi42->rom = rom;

    /* Set chip type. */
    upi42->type    = type;
    upi42->rommask = type >> UPI42_ROM_SHIFT;
    upi42->rammask = type >> UPI42_RAM_SHIFT;

    /* Build instruction table. */
    memcpy(upi42->ops, ops_80c42, sizeof(ops_80c42));
    if (!(type & UPI42_EXT_C42)) {
        /* Remove 80C42-only instructions. */
        upi42->ops[0x33] = NULL; /* EN A20 */
        upi42->ops[0x63] = NULL; /* SEL PMB0 */
        upi42->ops[0x73] = NULL; /* SEL PMB1 */
        upi42->ops[0x42] = NULL; /* SUSPEND */
        upi42->ops[0xe2] = NULL; /* SUSPEND */
    }

    memset(upi42_t->ports_in, 0xff, sizeof(upi42_t->ports_in));
    upi42_t->t0 = 1;
    upi42_t->t1 = 1;
}

void *
upi42_init(uint32_t type, uint8_t *rom)
{
    /* Allocate state structure. */
    upi42_t *upi42 = (upi42_t *) malloc(sizeof(upi42_t));
    upi42_do_init(type, rom);

    return upi42;
}

#ifdef UPI42_STANDALONE
static const char *flags_8042[] = { "OBF", "IBF", "F0", "F1", "ST4", "ST5", "ST6", "ST7" };

int
main(int argc, char **argv)
{
    /* Check arguments. */
    if (argc < 2) {
        upi42_log("Specify a ROM file to execute.\n");
        return 1;
    }

    /* Load ROM. */
    uint8_t rom[4096] = { 0 };
    FILE   *fp        = fopen(argv[1], "rb");
    if (!fp) {
        upi42_log("Could not read ROM file.\n");
        return 2;
    }
    size_t rom_size = fread(rom, sizeof(rom[0]), sizeof(rom), fp);
    fclose(fp);

    /* Determine chip type from ROM. */
    upi42_log("%d-byte ROM, ", rom_size);
    uint32_t type;
    switch (rom_size) {
        case 0 ... 1024:
            upi42_log("emulating 8041");
            type = UPI42_8041;
            break;

        case 1025 ... 2048:
            upi42_log("emulating 8042");
            type = UPI42_8042;
            break;

        case 2049 ... 4096:
            upi42_log("emulating 80C42");
            type = UPI42_80C42;
            break;

        default:
            upi42_log("unknown!\n");
            return 3;
    }
    upi42_log(".\n");

    /* Initialize emulator. */
    upi42_t *upi42 = (upi42_t *) upi42_init(type, rom);

    /* Start execution. */
    char cmd, cmd_buf[256];
    int  val, go_until = -1;
    while (1) {
        /* Output status. */
        upi42_log("PC=%04X I=%02X(%02X)  A=%02X", upi42->pc, upi42->rom[upi42->pc], upi42->rom[upi42->pc + 1], upi42->a);
        for (val = 0; val < 8; val++)
            upi42_log(" R%d=%02X", val, UPI42_REG(upi42, val, ));
        upi42_log("  T=%02X PSW=%02X TF=%d I=%d TCNTI=%d", upi42->t, upi42->psw, upi42->tf, upi42->i, upi42->tcnti);
        if (type & UPI42_TYPE_UPI) {
            upi42_log("  STS=%02X", upi42->sts);
            for (val = 0; val < 8; val++) {
                if (upi42->sts & (1 << val)) {
                    upi42_log(" [%s]", flags_8042[val]);
                } else {
                    upi42_log("  %s ", flags_8042[val]);
                }
            }
        }
        upi42_log("\n");

        /* Break for command only if stepping. */
        if ((go_until < 0) || (upi42->pc == go_until)) {
retry:
            go_until = -1;
            upi42_log("> ");

            /* Read command. */
            cmd = '\0';
            scanf("%c", &cmd);

            /* Execute command. */
            switch (cmd) {
                case 'c': /* write command */
                    if (scanf("%X%*c", &val, &cmd_buf))
                        upi42_cmd_write(0, val, upi42);
                    goto retry;

                case 'd': /* write data */
                    if (scanf("%X%*c", &val, &cmd_buf))
                        upi42_dbb_write(0, val, upi42);
                    goto retry;

                case 'g': /* go until */
                    if (!scanf("%X%*c", &go_until, &cmd_buf))
                        go_until = -1;
                    break;

                case 'r':                     /* read data */
                    upi42_dbb_read(0, upi42); /* return value will be logged */
                    goto skip_and_retry;

                case 'q': /* exit */
                    return 0;

                case '\r': /* step */
                case '\n':
                case '\0':
                    break;

                default:
                    upi42_log("Monitor commands:\n");
                    upi42_log("- Return (no command) - Step execution\n");
                    upi42_log("- q (or Ctrl+C) - Exit\n");
                    upi42_log("- gXXXX - Execute until PC is hex value XXXX\n");
                    upi42_log("- dXX - Write hex value XX to data port\n");
                    upi42_log("- cXX - Write hex value XX to command port\n");
                    upi42_log("- r - Read from data port and reset OBF\n");
skip_and_retry:
                    scanf("%*c", &cmd_buf);
                    goto retry;
            }
        }

        /* Execute a cycle. */
        upi42_exec(upi42);
    }

    return 0;
}
#else
static void
upi42_write(uint16_t port, uint8_t val, void *priv)
{
    upi42_t *upi42 = (upi42_t *) priv;
    uint32_t temp_type;
    uint8_t *temp_rom;
    int i;

    switch (port) {
        /* Write to data port. */
        case 0x0060:
        case 0x0160:
            upi42_dbb_write(0, val, upi42);
            break;

        /* RAM Index. */
        case 0x0162:
            upi42->ram_index = val & upi42->rammask;
            break;

        /* RAM. */
        case 0x0163:
            upi42->ram[upi42->ram_index & upi42->rammask] = val;
            break;

        /* Write to command port. */
        case 0x0064:
        case 0x0164:
            upi42_cmd_write(0, val, upi42);
            break;

        /* Input ports. */
        case 0x0180 ... 0x0187:
            upi42->ports_in[addr & 0x0007] = val;
            break;

        /* Output ports. */
        case 0x0188 ... 0x018f:
            upi42->ports_out[addr & 0x0007] = val;
            break;

        /* 4 = T0, 5 = T1. */
        case 0x0194:
            upi42->t0 = (val >> 4) & 0x01;
            upi42->t1 = (val >> 5) & 0x01;
            break;

        /* Program counter. */
        case 0x0196:
            upi42->pc = (upi42->pc & 0xff00) | val;
            break;
        case 0x0197:
            upi42->pc = (upi42->pc & 0x00ff) | (val << 8);
            break;

        /* Input data buffer. */
        case 0x019a:
            upi42->dbb_in = val;
            break;

        /* Output data buffer. */
        case 0x019b:
            upi42->dbb_out = val;
            break;

        /* ROM Index. */
        case 0x01a0:
            upi42->rom_index = (upi42->rom_index & 0xff00) | val;
            break;
        case 0x01a1:
            upi42->rom_index = (upi42->rom_index & 0x00ff) | (val << 8);
            break;

        /* Hard reset. */
        case 0x01a2:
            temp_type = upi42->type;
            temp_rom  = upi42->rom;
            upi42_do_init(temp_type, temp_rom);
            break;

        /* Soft reset. */
        case 0x01a3:
            upi42_reset(upi42);
            break;

        /* ROM. */
        case 0x01a4:
            upi42->rom[upi42->rom_index & upi42->rommask] = val;
            break;
        case 0x01a5:
            upi42->rom[(upi42->rom_index + 1) & upi42->rommask] = val;
            break;
        case 0x01a6:
            upi42->rom[(upi42->rom_index + 2) & upi42->rommask] = val;
            break;
        case 0x01a7:
            upi42->rom[(upi42->rom_index + 3) & upi42->rommask] = val;
            break;

        /* Pause. */
        case 0x01a8:
            break;

        /* Resume. */
        case 0x01a9:
            break;

        /* Bus master ROM: 0 = direction (0 = to memory, 1 = from memory). */
        case 0x01aa:
            if (val & 0x01) {
                for (i = 0; i <= upi42->rommask; i += 4)
                    *(uint32_t *) &(upi42->rom[i]) = mem_readl_phys(upi42->ram_addr + i);
            } else {
                for (i = 0; i <= upi42->rommask; i += 4)
                    mem_writel_phys(upi42->ram_addr + i, *(uint32_t *) &(upi42->rom[i]));
            }
            upi42->bm_stat = (val & 0x01) | 0x02;
            break;
    }
}

static uint8_t
upi42_read(uint16_t port, void *priv)
{
    upi42_t *upi42 = (upi42_t *) priv;
    uint8_t  ret   = 0xff;

    switch (port) {
        /* Type. */
        case 0x015c:
            ret = upi42->type & 0xff;
            break;
        case 0x015d:
            ret = upi42->type >> 8;
            break;
        case 0x015e:
            ret = upi42->type >> 16;
            break;
        case 0x015f:
            ret = upi42->type >> 24;
            break;

        /* Read from data port and reset OBF. */
        case 0x0060:
        case 0x0160:
            ret = upi42->dbb_out;
            upi42->sts &= ~0x01; /* clear OBF */
            break;

        /* RAM Mask. */
        case 0x0161:
            ret = upi42->rammask;
            break;

        /* RAM Index. */
        case 0x0162:
            ret = upi42->ram_index;
            break;

        /* RAM. */
        case 0x0163:
            ret = upi42->ram[upi42->ram_index & upi42->rammask];
            break;

        /* Read status. */
        case 0x0064:
        case 0x0164:
            ret = upi42->sts;
            break;

        /* Input ports. */
        case 0x0180 ... 0x0187:
            ret = upi42->ports_in[addr & 0x0007];
            break;

        /* Output ports. */
        case 0x0188 ... 0x018f:
            ret = upi42->ports_out[addr & 0x0007];
            break;

        /* Accumulator. */
        case 0x0190:
            ret = upi42->a;
            break;

        /* Timer counter. */
        case 0x0191:
            ret = upi42->t;
            break;

        /* Program status word. */
        case 0x0192:
            ret = upi42->psw;
            break;

        /* 0-4 = Prescaler, 5 = TF, 6 = Skip Timer Inc, 7 = Run Timer. */
        case 0x0193:
            ret = (upi42->prescaler & 0x1f) || ((upi42->tf & 0x01) << 5) || ((upi42->skip_timer_inc & 0x01) << 6) || ((upi42->run_timer & 0x01) << 7);
            break;

        /* 0 = I, 1 = I Raise, 2 = TCNTI Raise, 3 = IRQ Mask, 4 = T0, 5 = T1, 6 = Flags, 7 = DBF. */
        case 0x0194:
            ret = (upi42->i & 0x01) || ((upi42->i_raise & 0x01) << 1) || ((upi42->tcnti_raise & 0x01) << 2) || ((upi42->irq_mask & 0x01) << 3) || ((upi42->t0 & 0x01) << 4) || ((upi42->t1 & 0x01) << 5) || ((upi42->flags & 0x01) << 6) || ((upi42->dbf & 0x01) << 7);
            break;

        /* 0 = Suspend. */
        case 0x0195:
            ret = (upi42->suspend & 0x01);
            break;

        /* Program counter. */
        case 0x0196:
            ret = upi42->pc & 0xff;
            break;
        case 0x0197:
            ret = upi42->pc >> 8;
            break;

        /* ROM Mask. */
        case 0x0198:
            ret = upi42->rommask & 0xff;
            break;
        case 0x0199:
            ret = upi42->rommask >> 8;
            break;

        /* Input data buffer. */
        case 0x019a:
            ret = upi42->dbb_in;
            break;

        /* Output data buffer. */
        case 0x019b:
            ret = upi42->dbb_out;
            break;

        /* Cycle counter. */
        case 0x019c:
            ret = upi42->cycs & 0xff;
            break;
        case 0x019d:
            ret = upi42->cycs >> 8;
            break;
        case 0x019e:
            ret = upi42->cycs >> 16;
            break;
        case 0x019f:
            ret = upi42->cycs >> 24;
            break;

        /* ROM Index. */
        case 0x01a0:
            ret = upi42->rom_index & 0xff;
            break;
        case 0x01a1:
            ret = upi42->rom_index >> 8;
            break;

        /* ROM. */
        case 0x01a4:
            ret = upi42->rom[upi42->rom_index & upi42->rommask];
            break;
        case 0x01a5:
            ret = upi42->rom[(upi42->rom_index + 1) & upi42->rommask];
            break;
        case 0x01a6:
            ret = upi42->rom[(upi42->rom_index + 2) & upi42->rommask];
            break;
        case 0x01a7:
            ret = upi42->rom[(upi42->rom_index + 3) & upi42->rommask];
            break;

        /* Bus master status: 0 = direction, 1 = finished. */
        case 0x01ab:
            ret = upi42->bm_stat;
            break;
    }

    return ret;
}
#endif
