diff options
Diffstat (limited to 'src/devices/machine/ncr5385.cpp')
-rw-r--r-- | src/devices/machine/ncr5385.cpp | 899 |
1 files changed, 812 insertions, 87 deletions
diff --git a/src/devices/machine/ncr5385.cpp b/src/devices/machine/ncr5385.cpp index 6c1f4a3bb4e..10451885345 100644 --- a/src/devices/machine/ncr5385.cpp +++ b/src/devices/machine/ncr5385.cpp @@ -1,120 +1,845 @@ // license:BSD-3-Clause // copyright-holders:Ryan Holtz -/*********************************************************************** - NCR 5385E SCSI Controller - - TOOD: - - Everything. - -***********************************************************************/ +/* + * NCR 5385 SCSI Protocol Controller + * + * Sources: + * - NCR 5385 SCSI Protocol Controller, 1983, NCR Corporation, Dayton, Ohio, USA + * - NCR SCSI Engineering Notebook, 1984, NCR Microelectronics + * + * TODO: + * - target mode send/receive + * - disconnect/reselection + */ #include "emu.h" #include "ncr5385.h" -DEFINE_DEVICE_TYPE(NCR5385, ncr5385_device, "ncr5385", "NCR 5385E SCSI Controller") +#define LOG_GENERAL (1U << 0) +#define LOG_REGW (1U << 1) +#define LOG_REGR (1U << 2) +#define LOG_STATE (1U << 3) +#define LOG_DMA (1U << 4) +#define LOG_COMMAND (1U << 5) + +//#define VERBOSE (LOG_GENERAL|LOG_REGW|LOG_REGR|LOG_STATE|LOG_DMA|LOG_COMMAND) +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(NCR5385, ncr5385_device, "ncr5385", "NCR 5385 SCSI Protocol Controller") -ncr5385_device::ncr5385_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig, NCR5385, tag, owner, clock) +// FIXME: would be better to reuse from nscsi_full_device +unsigned constexpr SCSI_ARB_DELAY = 2'400; +unsigned constexpr SCSI_BUS_CLEAR = 800; +unsigned constexpr SCSI_BUS_FREE = 800; +unsigned constexpr SCSI_BUS_SETTLE = 400; +unsigned constexpr SCSI_BUS_SKEW = 10; +unsigned constexpr SCSI_RST_HOLD = 25'000; +unsigned constexpr SCSI_SEL_TIMEOUT = 250'000'000; + +ncr5385_device::ncr5385_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) + : nscsi_device(mconfig, NCR5385, tag, owner, clock) + , nscsi_slot_card_interface(mconfig, *this, DEVICE_SELF) , m_int(*this) + , m_dreq(*this) + , m_int_state(false) + , m_dreq_state(false) { } +enum state : u32 +{ + IDLE, + DIAGNOSTIC, + + ARB_BUS_FREE, + ARB_START, + ARB_EVALUATE, + SEL_START, + SEL_DELAY, + SEL_WAIT_BSY, + SEL_COMPLETE, + SEL_WAIT_REQ, + + XFI_START, + XFI_IN_REQ, + XFI_IN_DRQ, + XFI_IN_ACK, + XFI_OUT_REQ, + XFI_OUT_DRQ, + XFI_OUT_ACK, + XFI_OUT_PAD, +}; + +enum mode : u8 +{ + DISCONNECTED, + INITIATOR, + TARGET, +}; + +enum diag_mask : u8 +{ + DIAG_SELF = 0x07, // self-diagnostic status + DIAG_CMD = 0x38, // diagnostic command status + DIAG_DONE = 0x80, // self-diagnostic complete + + DIAG_CMD_GP = 0x18, // diagnostic good parity + DIAG_CMD_BP = 0x20, // diagnostic bad parity +}; + +enum int_mask : u8 +{ + INT_FUNC_COMPLETE = 0x01, + INT_BUS_SERVICE = 0x02, + INT_DISCONNECTED = 0x04, + INT_SELECTED = 0x08, + INT_RESELECTED = 0x10, + INT_INVALID_CMD = 0x40, +}; + +enum aux_status_mask : u8 +{ + AUX_STATUS_TC_ZERO = 0x02, + AUX_STATUS_PAUSED = 0x04, + AUX_STATUS_IO = 0x08, + AUX_STATUS_CD = 0x10, + AUX_STATUS_MSG = 0x20, + AUX_STATUS_PARITY_ERR = 0x40, + AUX_STATUS_DATA_FULL = 0x80, +}; + +enum cmd_mask : u8 +{ + CMD_INT = 0x08, // interrupting + CMD_SBX = 0x40, // single byte transfer + CMD_DMA = 0x80, // DMA mode +}; + void ncr5385_device::device_start() { - m_int.resolve_safe(); + save_item(NAME(m_dat)); + save_item(NAME(m_cmd)); + save_item(NAME(m_ctl)); + save_item(NAME(m_dst_id)); + save_item(NAME(m_aux_status)); + save_item(NAME(m_own_id)); + save_item(NAME(m_int_status)); + save_item(NAME(m_src_id)); + save_item(NAME(m_dia_status)); + save_item(NAME(m_cnt)); + + save_item(NAME(m_state)); + save_item(NAME(m_phase)); + save_item(NAME(m_mode)); + save_item(NAME(m_sbx)); + + save_item(NAME(m_int_state)); + save_item(NAME(m_dreq_state)); + + m_state_timer = timer_alloc(timer_expired_delegate(FUNC(ncr5385_device::state_timer), this)); + + m_dia_status = DIAG_DONE; } void ncr5385_device::device_reset() { - m_state = STATE_IDLE; - m_int_reg = 0; - m_ctrl_reg = 0; - m_aux_status_reg = AUX_STATUS_TC_ZERO; - m_diag_status_reg = DIAG_COMPLETE; + m_cmd = 0; + m_ctl = 0; + m_dst_id = 0; + m_aux_status = AUX_STATUS_TC_ZERO; + m_int_status = 0; + m_src_id = 0; + m_dia_status &= (DIAG_DONE | DIAG_SELF); + m_cnt = 0; + + m_state = IDLE; + m_mode = DISCONNECTED; + m_sbx = false; + + // monitor all control lines (device has no RST line) + scsi_bus->ctrl_wait(scsi_refid, S_ALL & ~S_RST, S_ALL & ~S_RST); + + update_int(); } -void ncr5385_device::write(offs_t offset, uint8_t data) +void ncr5385_device::scsi_ctrl_changed() { - switch (offset) + u32 const ctrl = scsi_bus->ctrl_r(); + + static char const *const nscsi_phase[] = { "DATA OUT", "DATA IN", "COMMAND", "STATUS", "*", "*", "MESSAGE OUT", "MESSAGE IN" }; + + if ((ctrl & S_BSY) && !(ctrl & S_SEL)) { - case 0x0: // Data Register - switch (m_state) - { - case STATE_DIAGNOSTIC_GOOD_PARITY: - m_aux_status_reg &= ~AUX_STATUS_PARITY_ERR; - m_aux_status_reg |= AUX_STATUS_DATA_FULL; - m_int_reg = INT_FUNC_COMPLETE; - m_diag_status_reg = DIAG_COMPLETE | DIAG_TURN_GOOD_PARITY; - m_state = STATE_IDLE; - m_int(1); - logerror("%s: ncr5385_w: data=%02x (diagnostic w/ good parity)\n", machine().describe_context(), data); - break; - case STATE_DIAGNOSTIC_BAD_PARITY: - m_aux_status_reg |= AUX_STATUS_PARITY_ERR | AUX_STATUS_DATA_FULL; - m_int_reg = INT_FUNC_COMPLETE; - m_diag_status_reg = DIAG_COMPLETE | DIAG_TURN_BAD_PARITY; - m_state = STATE_IDLE; - m_int(1); - logerror("%s: ncr5385_w: data=%02x (diagnostic w/ bad parity)\n", machine().describe_context(), data); - break; - default: - logerror("%s: ncr5385_w: data=%02x\n", machine().describe_context(), data); - break; - } + LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x phase %s%s%s\n", ctrl, nscsi_phase[ctrl & S_PHASE_MASK], + ctrl & S_REQ ? " REQ" : "", ctrl & S_ACK ? " ACK" : ""); + + if (m_state != IDLE) + m_state_timer->adjust(attotime::zero); + } + else if (ctrl & S_BSY) + LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x arbitration/selection\n", ctrl); + else + { + LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x BUS FREE\n", ctrl); + + if (m_mode == INITIATOR) + { + m_mode = DISCONNECTED; + m_int_status |= INT_DISCONNECTED; + update_int(); + } + } +} + +void ncr5385_device::map(address_map &map) +{ + map(0x0, 0x0).rw(FUNC(ncr5385_device::dat_r), FUNC(ncr5385_device::dat_w)); + map(0x1, 0x1).rw(FUNC(ncr5385_device::cmd_r), FUNC(ncr5385_device::cmd_w)); + map(0x2, 0x2).rw(FUNC(ncr5385_device::ctl_r), FUNC(ncr5385_device::ctl_w)); + map(0x3, 0x3).rw(FUNC(ncr5385_device::dst_id_r), FUNC(ncr5385_device::dst_id_w)); + map(0x4, 0x4).r(FUNC(ncr5385_device::aux_status_r)); + map(0x5, 0x5).r(FUNC(ncr5385_device::own_id_r)); + map(0x6, 0x6).r(FUNC(ncr5385_device::int_status_r)); + map(0x7, 0x7).r(FUNC(ncr5385_device::src_id_r)); + map(0x9, 0x9).r(FUNC(ncr5385_device::dia_status_r)); + map(0xc, 0xc).rw(FUNC(ncr5385_device::cnt_r<2>), FUNC(ncr5385_device::cnt_w<2>)); + map(0xd, 0xd).rw(FUNC(ncr5385_device::cnt_r<1>), FUNC(ncr5385_device::cnt_w<1>)); + map(0xe, 0xe).rw(FUNC(ncr5385_device::cnt_r<0>), FUNC(ncr5385_device::cnt_w<0>)); + map(0xf, 0xf).rw(FUNC(ncr5385_device::tst_r), FUNC(ncr5385_device::tst_w)); +} + +u8 ncr5385_device::dat_r() +{ + if (m_aux_status & AUX_STATUS_DATA_FULL) + { + m_aux_status &= ~AUX_STATUS_DATA_FULL; + + if (m_state != IDLE) + m_state_timer->adjust(attotime::zero); + } + else + logerror("data register empty (%s)\n", machine().describe_context()); + + return m_dat; +} + +u8 ncr5385_device::cmd_r() +{ + return m_cmd; +} + +u8 ncr5385_device::ctl_r() +{ + return m_ctl; +} + +u8 ncr5385_device::dst_id_r() +{ + return m_dst_id; +} + +u8 ncr5385_device::aux_status_r() +{ + u8 data = m_aux_status; + + if (!m_int_status) + { + // return current phase + u32 const ctrl = scsi_bus->ctrl_r(); + if (ctrl & S_MSG) + data |= AUX_STATUS_MSG; + if (ctrl & S_CTL) + data |= AUX_STATUS_CD; + if (ctrl & S_INP) + data |= AUX_STATUS_IO; + } + LOGMASKED(LOG_REGR, "aux_status_r 0x%02x (%s)\n", data, machine().describe_context()); + + return data; +} + +u8 ncr5385_device::own_id_r() +{ + return m_own_id; +} + +u8 ncr5385_device::int_status_r() +{ + u8 const data = m_int_status; + LOGMASKED(LOG_REGR, "int_status_r 0x%02x (%s)\n", data, machine().describe_context()); + m_aux_status &= ~AUX_STATUS_PARITY_ERR; + m_int_status = 0; + update_int(); + + if (m_state != IDLE) + m_state_timer->adjust(attotime::zero); + + return data; +} + +u8 ncr5385_device::src_id_r() +{ + return m_src_id; +} + +u8 ncr5385_device::dia_status_r() +{ + return m_dia_status; +} + +template <unsigned N> u8 ncr5385_device::cnt_r() +{ + return u8(m_cnt >> (N * 8)); +} + +u8 ncr5385_device::tst_r() +{ + return 0; +} + +void ncr5385_device::dat_w(u8 data) +{ + LOGMASKED(LOG_REGW, "dat_w 0x%02x (%s)\n", data, machine().describe_context()); + + if (!(m_aux_status & AUX_STATUS_DATA_FULL)) + { + m_dat = data; + m_aux_status |= AUX_STATUS_DATA_FULL; + + if (m_state != IDLE) + m_state_timer->adjust(attotime::zero); + } + else + logerror("data register full\n"); +} + +void ncr5385_device::cmd_w(u8 data) +{ + LOGMASKED(LOG_REGW, "cmd_w 0x%02x (%s)\n", data, machine().describe_context()); + if (!(data & 0x18)) + { + // immediate commands + + switch (data & 0x1f) + { + case 0x00: + LOGMASKED(LOG_COMMAND, "reset\n"); + reset(); break; - case 0x1: // Command Register - switch (data & 0x3f) - { - case 0x00: // Chip Reset - logerror("%s: ncr5385_w: command: reset\n", machine().describe_context()); - m_state = STATE_IDLE; - m_int_reg = 0; - m_aux_status_reg = AUX_STATUS_TC_ZERO; - m_diag_status_reg = DIAG_COMPLETE; - m_int(0); - break; - case 0x0b: // Diagnostic - logerror("%s: ncr5385_w: command: diagnostic (%s parity)\n", machine().describe_context(), BIT(data, 6) ? "bad" : "good"); - if (BIT(data, 6)) - m_state = STATE_DIAGNOSTIC_BAD_PARITY; - else - m_state = STATE_DIAGNOSTIC_GOOD_PARITY; - break; - default: - logerror("%s: ncr5385_w: command: %02x\n", machine().describe_context(), data); - break; - } + case 0x01: // disconnect + LOGMASKED(LOG_COMMAND, "disconnect\n"); + m_mode = DISCONNECTED; + break; + case 0x02: // pause + LOGMASKED(LOG_COMMAND, "pause\n"); + break; + case 0x03: // set atn + LOGMASKED(LOG_COMMAND, "set atn\n"); + scsi_bus->ctrl_w(scsi_refid, S_ATN, S_ATN); + break; + case 0x04: // message accepted + LOGMASKED(LOG_COMMAND, "message accepted\n"); + scsi_bus->ctrl_w(scsi_refid, 0, S_ACK); + break; + case 0x05: // chip disabled + LOGMASKED(LOG_COMMAND, "chip disabled\n"); + break; + case 0x06: case 0x07: + // reserved + break; + } + } + else + { + // interrupting commands + m_aux_status &= ~AUX_STATUS_DATA_FULL; + m_cmd = data; + + switch (data & 0x1f) + { + case 0x08: // select w/atn + LOGMASKED(LOG_COMMAND, "select %d w/atn (timeout %d)\n", m_dst_id, attotime::from_ticks(m_cnt * 1024, clock()).to_string()); + m_state = ARB_BUS_FREE; + m_state_timer->adjust(attotime::zero); + break; + case 0x09: // select w/o atn + LOGMASKED(LOG_COMMAND, "select %d w/o atn (timeout %d)\n", m_dst_id, attotime::from_ticks(m_cnt * 1024, clock()).to_string()); + m_state = ARB_BUS_FREE; + m_state_timer->adjust(attotime::zero); + break; + case 0x0a: // reselect + LOGMASKED(LOG_COMMAND, "reselect\n"); + break; + case 0x0b: // diagnostic + LOGMASKED(LOG_COMMAND, "diagnostic (%s parity)\n", BIT(data, 6) ? "bad" : "good"); + m_state = DIAGNOSTIC; + break; + case 0x0c: // receive command + LOGMASKED(LOG_COMMAND, "receive command\n"); + break; + case 0x0d: // receive data + LOGMASKED(LOG_COMMAND, "receive data\n"); break; - case 0x2: // Control Register - m_ctrl_reg = data & 0x07; - logerror("%s: ncr5385_w: control: parity_en=%d, reselect_en=%d, select_en=%d\n", machine().describe_context(), BIT(data, CTRL_PARITY_BIT), BIT(data, CTRL_RESELECT_BIT), BIT(data, CTRL_SELECT_BIT)); + case 0x0e: // receive message out + LOGMASKED(LOG_COMMAND, "receive message out\n"); break; - default: - logerror("%s: ncr5385_w: %x=%02x\n", machine().describe_context(), offset, data); + case 0x0f: // receive unspecified info out + LOGMASKED(LOG_COMMAND, "receive unspecified info out\n"); break; + case 0x10: // send status + LOGMASKED(LOG_COMMAND, "send status\n"); + break; + case 0x11: // send data + LOGMASKED(LOG_COMMAND, "send data\n"); + break; + case 0x12: // send message in + LOGMASKED(LOG_COMMAND, "send message in\n"); + break; + case 0x13: // send unspecified info in + LOGMASKED(LOG_COMMAND, "send unspecified info in\n"); + break; + case 0x14: // transfer info + if (data & CMD_SBX) + LOGMASKED(LOG_COMMAND, "transfer info (%s, single byte)\n", (data & CMD_DMA) ? "dma" : "pio"); + else + LOGMASKED(LOG_COMMAND, "transfer info (%s, count=%d)\n", (data & CMD_DMA) ? "dma" : "pio", m_cnt); + m_state = XFI_START; + m_sbx = data & CMD_SBX; + m_state_timer->adjust(attotime::zero); + break; + case 0x15: // transfer pad + if (data & CMD_SBX) + LOGMASKED(LOG_COMMAND, "transfer pad (%s, single byte)\n", (data & CMD_DMA) ? "dma" : "pio"); + else + LOGMASKED(LOG_COMMAND, "transfer pad (%s, count=%d)\n", (data & CMD_DMA) ? "dma" : "pio", m_cnt); + m_state = XFI_START; + m_sbx = data & CMD_SBX; + m_state_timer->adjust(attotime::zero); + break; + case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1a: case 0x1b: + case 0x1c: case 0x1d: case 0x1e: case 0x1f: + // reserved + LOGMASKED(LOG_COMMAND, "reserved / invalid cmd\n"); + m_int_status |= INT_INVALID_CMD; + update_int(); + break; + } + } +} + +void ncr5385_device::ctl_w(u8 data) +{ + LOGMASKED(LOG_REGW, "ctl_w 0x%02x (%s)\n", data, machine().describe_context()); + + m_ctl = data & 7; +} + +void ncr5385_device::dst_id_w(u8 data) +{ + LOGMASKED(LOG_REGW, "dst_id_w 0x%02x (%s)\n", data, machine().describe_context()); + m_dst_id = (data & 7); +} + +template <unsigned N> void ncr5385_device::cnt_w(u8 data) +{ + m_cnt = (m_cnt & ~(u32(0xff) << (N * 8))) | (u32(data) << (N * 8)); + + if (m_cnt) + m_aux_status &= ~AUX_STATUS_TC_ZERO; + else + m_aux_status |= AUX_STATUS_TC_ZERO; +} + +void ncr5385_device::tst_w(u8 data) +{ + LOGMASKED(LOG_REGW, "tst_w 0x%02x (%s)\n", data, machine().describe_context()); +} + +u8 ncr5385_device::dma_r() +{ + u8 const data = m_dat; + m_aux_status &= ~AUX_STATUS_DATA_FULL; + + set_dreq(false); + m_state_timer->adjust(attotime::zero); + + return data; +} + +void ncr5385_device::dma_w(u8 data) +{ + m_dat = data; + m_aux_status |= AUX_STATUS_DATA_FULL; + + set_dreq(false); + m_state_timer->adjust(attotime::zero); +} + +void ncr5385_device::state_timer(s32 param) +{ + // step state machine + int const delay = state_step(); + + // check for data stall + if (delay < 0) + return; + + // repeat until idle + if (m_state != IDLE) + m_state_timer->adjust(attotime::from_nsec(delay)); +} + +int ncr5385_device::state_step() +{ + u32 const ctrl = scsi_bus->ctrl_r(); + int delay = 0; + + u8 const oid = 1 << m_own_id; + u8 const tid = 1 << m_dst_id; + + switch (m_state) + { + case DIAGNOSTIC: + m_dia_status &= DIAG_DONE | DIAG_SELF; + if (BIT(m_cmd, 6)) + { + m_aux_status |= AUX_STATUS_PARITY_ERR; + m_dia_status |= DIAG_CMD_BP; + } + else + { + m_aux_status &= ~AUX_STATUS_PARITY_ERR; + m_dia_status |= DIAG_CMD_GP; + } + + m_int_status |= INT_FUNC_COMPLETE; + m_state = IDLE; + + update_int(); + break; + + case ARB_BUS_FREE: + LOGMASKED(LOG_STATE, "arbitration: waiting for bus free\n"); + if (!(ctrl & (S_SEL | S_BSY | S_RST))) + { + m_state = ARB_START; + delay = SCSI_BUS_FREE; + } + break; + case ARB_START: + LOGMASKED(LOG_STATE, "arbitration: started\n"); + m_state = ARB_EVALUATE; + delay = SCSI_ARB_DELAY; + + // assert own ID and BSY + scsi_bus->data_w(scsi_refid, oid); + scsi_bus->ctrl_w(scsi_refid, S_BSY, S_BSY); + break; + case ARB_EVALUATE: + // check if SEL asserted, or if there's a higher ID on the bus + if ((ctrl & S_SEL) || (scsi_bus->data_r() & ~((oid - 1) | oid))) + { + LOGMASKED(LOG_STATE, "arbitration: lost\n"); + m_state = ARB_BUS_FREE; + + // clear data and BSY + scsi_bus->data_w(scsi_refid, 0); + scsi_bus->ctrl_w(scsi_refid, 0, S_BSY); + } + else + { + LOGMASKED(LOG_STATE, "arbitration: won\n"); + m_state = SEL_START; + delay = SCSI_BUS_CLEAR + SCSI_BUS_SETTLE; + } + break; + + case SEL_START: + LOGMASKED(LOG_STATE, "selection: SEL asserted\n"); + m_state = SEL_DELAY; + delay = SCSI_BUS_SKEW * 2; + + // assert own and target ID and SEL + scsi_bus->data_w(scsi_refid, oid | tid); + scsi_bus->ctrl_w(scsi_refid, S_SEL, S_SEL); + break; + case SEL_DELAY: + LOGMASKED(LOG_STATE, "selection: BSY cleared\n"); + m_state = SEL_WAIT_BSY; + delay = SCSI_SEL_TIMEOUT; + + // clear BSY, optionally assert ATN + if (!BIT(m_cmd, 0)) + scsi_bus->ctrl_w(scsi_refid, S_ATN, S_BSY | S_ATN); + else + scsi_bus->ctrl_w(scsi_refid, 0, S_BSY); + break; + case SEL_WAIT_BSY: + if (ctrl & S_BSY) + { + LOGMASKED(LOG_STATE, "selection: BSY asserted by target\n"); + m_state = SEL_COMPLETE; + delay = SCSI_BUS_SKEW * 2; + } + else + { + LOGMASKED(LOG_STATE, "selection: timed out\n"); + m_int_status |= INT_DISCONNECTED; + m_state = IDLE; + + scsi_bus->ctrl_w(scsi_refid, 0, S_ATN | S_SEL); + + update_int(); + } + break; + case SEL_COMPLETE: + LOGMASKED(LOG_STATE, "selection: complete\n"); + m_int_status |= INT_FUNC_COMPLETE; + m_mode = INITIATOR; + m_state = SEL_WAIT_REQ; + delay = -1; + + update_int(); + + // clear data and SEL + scsi_bus->data_w(scsi_refid, 0); + scsi_bus->ctrl_w(scsi_refid, 0, S_SEL); + break; + case SEL_WAIT_REQ: + // don't generate bus service interrupt until the function complete is cleared + if ((ctrl & S_REQ) && !m_int_state) + { + LOGMASKED(LOG_STATE, "selection: REQ asserted by target\n"); + m_int_status |= INT_BUS_SERVICE; + m_state = IDLE; + + update_int(); + } + else + delay = -1; + break; + + case XFI_START: + m_phase = ctrl & S_PHASE_MASK; + m_state = (ctrl & S_INP) ? XFI_IN_REQ : XFI_OUT_REQ; + break; + + case XFI_IN_REQ: + // TODO: disconnect + if (ctrl & S_REQ) + { + if (remaining() && (ctrl & S_PHASE_MASK) == m_phase) + { + m_state = XFI_IN_DRQ; + + // transfer pad in doesn't transfer any data + if (!BIT(m_cmd, 0)) + { + m_aux_status |= AUX_STATUS_DATA_FULL; + m_dat = scsi_bus->data_r(); + + if (m_cmd & CMD_DMA) + set_dreq(true); + + delay = -1; + } + } + else + { + LOGMASKED(LOG_STATE, "xfi_in: %s\n", remaining() ? "phase change" : "transfer complete"); + + m_int_status |= INT_BUS_SERVICE; + m_state = IDLE; + + update_int(); + } + } + else + delay = -1; + break; + case XFI_IN_DRQ: + m_state = XFI_IN_ACK; + + LOGMASKED(LOG_STATE, "xfi_in: data 0x%02x\n", m_dat); + + // assert ACK + scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK); + break; + case XFI_IN_ACK: + if (!(ctrl & S_REQ)) + { + m_state = XFI_IN_REQ; + + if (!(m_cmd & CMD_SBX)) + { + m_cnt--; + + LOGMASKED(LOG_STATE, "xfi_in: %d remaining\n", m_cnt); + + if (!m_cnt) + m_aux_status |= AUX_STATUS_TC_ZERO; + } + else + m_sbx = false; + + // clear ACK except after last byte of message input phase + if (!remaining() && (ctrl & S_PHASE_MASK) == S_PHASE_MSG_IN) + { + m_int_status |= INT_FUNC_COMPLETE; + m_state = IDLE; + + update_int(); + } + else + scsi_bus->ctrl_w(scsi_refid, 0, S_ACK); + } + else + delay = -1; + break; + + case XFI_OUT_REQ: + if (ctrl & S_REQ) + { + // TODO: disconnect + if (remaining() && (ctrl & S_PHASE_MASK) == m_phase) + { + m_state = XFI_OUT_DRQ; + + // FIXME: only one byte dma for transfer pad + if (m_cmd & CMD_DMA) + set_dreq(true); + + if (!(m_aux_status & AUX_STATUS_DATA_FULL)) + delay = -1; + } + else + { + LOGMASKED(LOG_STATE, "xfi_out: %s\n", remaining() ? "phase change" : "transfer complete"); + m_int_status |= INT_BUS_SERVICE; + m_state = IDLE; + + update_int(); + } + } + else + delay = -1; + break; + case XFI_OUT_DRQ: + m_state = XFI_OUT_ACK; + m_aux_status &= ~AUX_STATUS_DATA_FULL; + + LOGMASKED(LOG_STATE, "xfi_out: data 0x%02x\n", m_dat); + + // assert data and ACK + scsi_bus->data_w(scsi_refid, m_dat); + if (remaining(1) && (ctrl & S_PHASE_MASK) == S_PHASE_MSG_OUT) + scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK | S_ATN); + else + scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK); + break; + case XFI_OUT_ACK: + if (!(ctrl & S_REQ)) + { + if (BIT(m_cmd, 0)) + m_state = XFI_OUT_PAD; + else + m_state = XFI_OUT_REQ; + + if (!(m_cmd & CMD_SBX)) + { + m_cnt--; + + LOGMASKED(LOG_STATE, "xfi_out: %d remaining\n", m_cnt); + + if (!m_cnt) + m_aux_status |= AUX_STATUS_TC_ZERO; + } + else + m_sbx = false; + + // clear data and ACK + scsi_bus->data_w(scsi_refid, 0); + scsi_bus->ctrl_w(scsi_refid, 0, S_ACK); + } + else + delay = -1; + break; + case XFI_OUT_PAD: + if (ctrl & S_REQ) + { + // TODO: disconnect + if (remaining() && (ctrl & S_PHASE_MASK) == m_phase) + m_state = XFI_OUT_DRQ; + else + { + LOGMASKED(LOG_STATE, "xfi_out: %s\n", remaining() ? "phase change" : "transfer complete"); + m_int_status |= INT_BUS_SERVICE; + m_state = IDLE; + + update_int(); + } + } + break; } + + return delay; } -uint8_t ncr5385_device::read(offs_t offset) +/* + * Returns a boolean indicating whether any data remains to be transferred: + * + * - for single byte transfer commands, m_sbx indicates data remaining + * - alternatively, return if there's a specific amount of data to transfer + * - otherwise, check if there's any data to transfer + */ +bool ncr5385_device::remaining(u32 const count) const { - switch (offset) + if (m_cmd & CMD_SBX) + return m_sbx; + else if (count) + return m_cnt == count; + else + return m_cnt; +} + +void ncr5385_device::set_dreq(bool dreq) +{ + if (m_dreq_state != dreq) { - case 0x2: - logerror("%s: ncr5385_r: control (%02x)\n", machine().describe_context(), m_ctrl_reg); - return m_ctrl_reg; - case 0x4: - logerror("%s: ncr5385_r: aux status (%02x)\n", machine().describe_context(), m_aux_status_reg); - return m_aux_status_reg; - case 0x6: - logerror("%s: ncr5385_r: interrupt (%02x)\n", machine().describe_context(), m_int_reg); - m_int(1); - return m_int_reg; - case 0x9: - logerror("%s: ncr5385_r: diagnostic status (%02x)\n", machine().describe_context(), m_diag_status_reg); - return m_diag_status_reg; - default: - logerror("%s: ncr5385_r: %x (%02x)\n", machine().describe_context(), offset, 0); - return 0; + LOGMASKED(LOG_DMA, "set_dreq %d\n", dreq); + + m_dreq_state = dreq; + m_dreq(m_dreq_state); + } +} + +void ncr5385_device::update_int() +{ + bool const int_state = m_int_status & (INT_FUNC_COMPLETE | INT_BUS_SERVICE | + INT_DISCONNECTED | INT_SELECTED | INT_RESELECTED | INT_INVALID_CMD); + + if (m_int_state != int_state) + { + LOG("update_int %d\n", int_state); + + m_aux_status &= ~(AUX_STATUS_MSG | AUX_STATUS_CD | AUX_STATUS_IO); + if (int_state) + { + m_cmd = 0; + + // latch current phase + u32 const ctrl = scsi_bus->ctrl_r(); + if (ctrl & S_MSG) + m_aux_status |= AUX_STATUS_MSG; + if (ctrl & S_CTL) + m_aux_status |= AUX_STATUS_CD; + if (ctrl & S_INP) + m_aux_status |= AUX_STATUS_IO; + } + + m_int_state = int_state; + m_int(m_int_state); } } |