// license:BSD-3-Clause // copyright-holders:Sven Schnelle #include "emu.h" #include "mb87030.h" //#define VERBOSE 1 #include "logmacro.h" DEFINE_DEVICE_TYPE(MB87030, mb87030_device, "mb87030", "Fujitsu MB87030 SCSI controller") mb87030_device::mb87030_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : mb87030_device(mconfig, MB87030, tag, owner, clock) { } mb87030_device::mb87030_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : nscsi_device(mconfig, type, tag, owner, clock), nscsi_slot_card_interface(mconfig, *this, DEVICE_SELF), m_irq_handler(*this), m_dreq_handler(*this) { } void mb87030_device::map(address_map &map) { map(0x00, 0x00).rw(FUNC(mb87030_device::bdid_r), FUNC(mb87030_device::bdid_w)); map(0x01, 0x01).rw(FUNC(mb87030_device::sctl_r), FUNC(mb87030_device::sctl_w)); map(0x02, 0x02).rw(FUNC(mb87030_device::scmd_r), FUNC(mb87030_device::scmd_w)); map(0x03, 0x03).rw(FUNC(mb87030_device::tmod_r), FUNC(mb87030_device::tmod_w)); map(0x04, 0x04).rw(FUNC(mb87030_device::ints_r), FUNC(mb87030_device::ints_w)); map(0x05, 0x05).rw(FUNC(mb87030_device::psns_r), FUNC(mb87030_device::sdgc_w)); map(0x06, 0x06).r(FUNC(mb87030_device::ssts_r)); map(0x07, 0x07).r(FUNC(mb87030_device::serr_r)); map(0x08, 0x08).rw(FUNC(mb87030_device::pctl_r), FUNC(mb87030_device::pctl_w)); map(0x09, 0x09).r(FUNC(mb87030_device::mbc_r)); map(0x0a, 0x0a).rw(FUNC(mb87030_device::dreg_r), FUNC(mb87030_device::dreg_w)); map(0x0b, 0x0b).rw(FUNC(mb87030_device::temp_r), FUNC(mb87030_device::temp_w)); map(0x0c, 0x0c).rw(FUNC(mb87030_device::tch_r), FUNC(mb87030_device::tch_w)); map(0x0d, 0x0d).rw(FUNC(mb87030_device::tcm_r), FUNC(mb87030_device::tcm_w)); map(0x0e, 0x0e).rw(FUNC(mb87030_device::tcl_r), FUNC(mb87030_device::tcl_w)); map(0x0f, 0x0f).rw(FUNC(mb87030_device::exbf_r), FUNC(mb87030_device::exbf_w)); } void mb87030_device::device_reset() { m_bdid = 0; m_sctl = SCTL_RESET_AND_DISABLE; m_scmd = 0; m_tmod = 0; m_ints = 0; m_sdgc = 0; m_ssts = 0; m_serr = 0; m_pctl = 0; m_mbc = 0; m_dreg = 0; m_temp = 0; m_tch = 0; m_tcm = 0; m_tc = 0; m_exbf = 0; m_fifo.clear(); scsi_bus->ctrl_wait(scsi_refid, S_SEL|S_BSY|S_RST, S_ALL); update_state(State::Idle, 0); scsi_set_ctrl(0, S_ALL); scsi_bus->data_w(scsi_refid, 0); update_ssts(); update_ints(); } auto mb87030_device::get_state_name(State state) const { switch (state) { case State::Idle: return "Idle"; case State::ArbitrationWaitBusFree: return "ArbitrationWaitBusFree"; case State::ArbitrationAssertBSY: return "ArbitrationAssertBSY"; case State::ArbitrationWait: return "ArbitrationWait"; case State::ArbitrationAssertSEL: return "ArbitrationAssertSEL"; case State::ArbitrationDeAssertBSY: return "ArbitrationDeAssertBSY"; case State::SelectionWaitBusFree: return "SelectionWaitBusFree"; case State::SelectionWaitBSY: return "SelectionWaitBSY"; case State::SelectionAssertSEL: return "SelectionAssertSEL"; case State::Selection: return "Selection"; case State::TransferWaitReq: return "TransferWaitReq"; case State::TransferSendAck: return "TransferSendAck"; case State::TransferSendData: return "TransferSendData"; case State::TransferSendDataDMAReq: return "TransferSendDataDMAReq"; case State::TransferSendDataDMAResp: return "TransferSendDataDMAResp"; case State::TransferRecvData: return "TransferRecvData"; case State::TransferRecvDataDMAReq: return "TransferRecvDataDMAReq"; case State::TransferRecvDataDMAResp: return "TransferRecvDataDMAResp"; case State::TransferWaitDeassertREQ: return "TransferWaitDeassertREQ"; case State::TransferDeassertACK: return "TransferDeassertACK"; } return "Unknown state"; } void mb87030_device::update_state(mb87030_device::State new_state, int delay, int timeout) { LOG("new state: %s -> %s (delay %d, timeout %d)\n", get_state_name(m_state), get_state_name(new_state), delay, timeout); m_state = new_state; if (delay) m_delay_timer->adjust(clocks_to_attotime(delay)); else m_delay_timer->reset(); if (timeout) m_timer->adjust(clocks_to_attotime(timeout)); else m_timer->reset(); } TIMER_CALLBACK_MEMBER(mb87030_device::timeout) { m_timer->reset(); step(true); } TIMER_CALLBACK_MEMBER(mb87030_device::delay_timeout) { m_delay_timer->reset(); step(false); } void mb87030_device::scsi_command_complete() { m_ints |= INTS_COMMAND_COMPLETE; m_ssts &= ~(SSTS_SPC_BUSY|SSTS_XFER_IN_PROGRESS); update_ints(); update_state(State::Idle); } void mb87030_device::scsi_disconnect_timeout() { scsi_set_ctrl(0, S_ALL); scsi_bus->data_w(scsi_refid, 0); m_ints = INTS_SPC_TIMEOUT; update_ints(); update_state(State::Idle); } void mb87030_device::scsi_disconnect() { LOG("%s: m_tc %d\n", __FUNCTION__, m_tc); m_ssts &= ~(SSTS_INIT_CONNECTED|SSTS_TARG_CONNECTED|SSTS_SPC_BUSY|SSTS_XFER_IN_PROGRESS); m_ints = INTS_DISCONNECTED; update_ints(); update_state(State::Idle); } void mb87030_device::scsi_set_ctrl(uint32_t value, uint32_t mask) { if (m_sctl & SCTL_DIAG_MODE) { m_scsi_ctrl &= ~mask; m_scsi_ctrl |= value; logerror("update m_scsi_ctrl: %02X%s%s%s%s%s%s\n", m_scsi_ctrl, (m_scsi_ctrl & S_REQ) ? " REQ" : "", (m_scsi_ctrl & S_ACK) ? " ACK" : "", (m_scsi_ctrl & S_BSY) ? " BSY" : "", (m_scsi_ctrl & S_MSG) ? " MSG" : "", (m_scsi_ctrl & S_CTL) ? " CTL" : "", (m_scsi_ctrl & S_INP) ? " INP" : ""); } else { scsi_bus->ctrl_w(scsi_refid, value, mask); } } uint32_t mb87030_device::scsi_get_ctrl(void) { if (m_sctl & SCTL_DIAG_MODE) { uint32_t ret = 0; if ((m_sdgc & SDGC_DIAG_IO) || (m_scsi_ctrl & S_INP)) ret |= S_INP; if ((m_sdgc & SDGC_DIAG_CD) || (m_scsi_ctrl & S_CTL)) ret |= S_CTL; if ((m_sdgc & SDGC_DIAG_MSG) || (m_scsi_ctrl & S_MSG)) ret |= S_MSG; if ((m_sdgc & SDGC_DIAG_BSY) || (m_scsi_ctrl & S_BSY)) ret |= S_BSY; if ((m_sdgc & SDGC_DIAG_ACK) || (m_scsi_ctrl & S_ACK)) ret |= S_ACK; if ((m_sdgc & SDGC_DIAG_REQ) || (m_scsi_ctrl & S_REQ)) ret |= S_REQ; if (m_scsi_ctrl & S_SEL) ret |= S_SEL; if (m_scsi_ctrl & S_ATN) ret |= S_ATN; return ret; } else { return scsi_bus->ctrl_r(); } } void mb87030_device::step(bool timeout) { uint32_t ctrl = scsi_get_ctrl(); uint32_t data = scsi_bus->data_r(); LOG("%s: %d %s,%s data %02X ctrl %02X TC %d\n", __FUNCTION__, scsi_refid, get_state_name(m_state), timeout ? " timeout" : "", data, ctrl, m_tc); if (m_sctl & SCTL_RESET_AND_DISABLE && m_state != State::Idle) { scsi_set_ctrl(0, S_ALL); update_state(State::Idle); return; } if ((m_ssts & SSTS_INIT_CONNECTED) && !(ctrl & S_BSY)) { LOG("SCSI disconnect\n"); scsi_disconnect(); scsi_set_ctrl(0, S_ALL); } switch (m_state) { case State::Idle: if (ctrl == 0 && (m_pctl & PCTL_BUS_FREE_IE)) { m_ints |= INTS_DISCONNECTED; update_ints(); } break; case State::ArbitrationWaitBusFree: if (!(ctrl & (S_BSY|S_SEL))) update_state(State::ArbitrationAssertBSY, 1); break; case State::ArbitrationAssertBSY: scsi_set_ctrl(S_BSY, S_BSY); scsi_bus->data_w(scsi_refid, (1 << m_bdid)); update_state(State::ArbitrationWait, 1); break; case State::ArbitrationWait: for (int id = (2 << m_bdid); id <= 0x80; id <<= 1) { LOG("check %d\n", id); if (data & id) { LOG("arbitration lost, winner %d\n", id); scsi_disconnect_timeout(); break; } } LOG("Arbitration won\n"); update_state(State::ArbitrationAssertSEL, 1); break; case State::ArbitrationAssertSEL: scsi_set_ctrl(S_SEL, S_SEL); update_state(State::ArbitrationDeAssertBSY, 1); break; case State::ArbitrationDeAssertBSY: scsi_set_ctrl(0, S_BSY); update_state(State::SelectionAssertSEL, 1); break; case State::SelectionWaitBusFree: if (!(ctrl & (S_BSY|S_SEL))) update_state(State::SelectionAssertSEL, 10); break; case State::SelectionAssertSEL: scsi_bus->data_w(scsi_refid, m_temp); scsi_set_ctrl(0, S_SEL); //XXX: needed? scsi_set_ctrl(S_SEL | (m_send_atn_during_selection ? S_ATN : 0), S_ATN|S_SEL|S_BSY); scsi_bus->ctrl_wait(scsi_refid, S_BSY, S_BSY); update_state(State::SelectionWaitBSY, 0, m_tc / 8); break; case State::SelectionWaitBSY: if (timeout) { LOG("select timeout\n"); scsi_disconnect_timeout(); break; } m_timer->reset(); if ((ctrl & (S_REQ|S_BSY|S_MSG|S_CTL|S_INP)) == S_BSY) update_state(State::Selection, 1); break; case State::Selection: LOG("selection success\n"); scsi_set_ctrl(0, S_SEL); m_ssts |= SSTS_INIT_CONNECTED|SSTS_XFER_IN_PROGRESS; m_ssts &= ~(SSTS_TARG_CONNECTED|SSTS_SPC_BUSY); update_ssts(); scsi_command_complete(); break; case State::TransferWaitReq: if (!m_tc && !(m_scmd & SCMD_TERM_MODE)) { LOG("TransferWaitReq: tc == 0\n"); scsi_bus->data_w(scsi_refid, 0); scsi_command_complete(); break; } if (m_scsi_phase != (ctrl & S_PHASE_MASK)) { LOG("SCSI phase change during transfer\n"); m_ints |= INTS_SERVICE_REQUIRED; m_ssts &= ~SSTS_SPC_BUSY; update_ints(); break; } if (!(ctrl & S_REQ)) { scsi_bus->ctrl_wait(scsi_refid, S_REQ, S_REQ); break; } if (!m_dma_transfer || (m_scmd & SCMD_TERM_MODE)) { update_state((ctrl & S_INP) ? State::TransferRecvData : State::TransferSendData, 1); } else { update_state((ctrl & S_INP) ? State::TransferRecvDataDMAReq : State::TransferSendDataDMAReq, 1); } break; case State::TransferRecvData: if (!m_tc && (m_scmd & SCMD_TERM_MODE)) { update_state(State::TransferSendAck, 10); break; } if (!m_tc || m_fifo.full()) break; m_bus_data = data; update_state(State::TransferSendAck, 10); break; case State::TransferRecvDataDMAReq: m_hdb = data; m_hdb_loaded = true; update_state(State::TransferRecvDataDMAResp, 10); m_dreq_handler(true); break; case State::TransferRecvDataDMAResp: if (m_hdb_loaded) break; update_state(State::TransferSendAck, 10); m_dreq_handler(false); break; case State::TransferSendData: if (m_tc && !m_fifo.empty()) { scsi_bus->data_w(scsi_refid, m_fifo.peek()); update_state(State::TransferSendAck, 10); break; } if (!m_tc && (m_scmd & SCMD_TERM_MODE)) { scsi_bus->data_w(scsi_refid, m_temp); update_state(State::TransferSendAck, 10); break; } break; case State::TransferSendDataDMAReq: m_hdb_loaded = false; update_state(State::TransferSendDataDMAResp, 10); m_dreq_handler(true); break; case State::TransferSendDataDMAResp: if (!m_hdb_loaded) break; m_hdb_loaded = false; m_dreq_handler(false); scsi_bus->data_w(scsi_refid, m_hdb); update_state(State::TransferSendAck, 10); break; case State::TransferSendAck: if (!(m_scmd & SCMD_TERM_MODE) && !(ctrl & S_INP)) m_temp = data; scsi_set_ctrl(S_ACK, S_ACK); scsi_bus->ctrl_wait(scsi_refid, 0, S_REQ); update_state(State::TransferWaitDeassertREQ, 10); break; case State::TransferWaitDeassertREQ: if (!(ctrl & S_REQ)) update_state(State::TransferDeassertACK, 10); break; case State::TransferDeassertACK: m_tc--; if (!m_dma_transfer) { if (!(ctrl & S_INP)) { m_fifo.dequeue(); } else { LOG("pushing read data: %02X\n", m_bus_data); m_fifo.enqueue(m_bus_data); } } update_state(State::TransferWaitReq, 10); scsi_bus->ctrl_wait(scsi_refid, S_REQ, S_REQ); scsi_set_ctrl(0, S_ACK); break; } } void mb87030_device::device_start() { m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mb87030_device::timeout), this)); m_delay_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mb87030_device::delay_timeout), this)); m_irq_handler.resolve_safe(); m_dreq_handler.resolve_safe(); save_item(NAME(m_bdid)); save_item(NAME(m_sctl)); save_item(NAME(m_scmd)); save_item(NAME(m_tmod)); save_item(NAME(m_ints)); save_item(NAME(m_sdgc)); save_item(NAME(m_ssts)); save_item(NAME(m_serr)); save_item(NAME(m_pctl)); save_item(NAME(m_mbc)); save_item(NAME(m_dreg)); save_item(NAME(m_temp)); save_item(NAME(m_tch)); save_item(NAME(m_tcm)); save_item(NAME(m_tc)); save_item(NAME(m_exbf)); save_item(NAME(m_hdb)); save_item(NAME(m_hdb_loaded)); save_item(NAME(m_send_atn_during_selection)); // save_item(NAME(m_fifo)); save_item(NAME(m_scsi_phase)); save_item(NAME(m_scsi_ctrl)); save_item(NAME(m_dma_transfer)); save_item(NAME(m_bus_data)); // save_item(NAME(m_state)); } void mb87030_device::scsi_ctrl_changed() { if (m_delay_timer->remaining() == attotime::never) step(false); } WRITE_LINE_MEMBER(mb87030_device::reset_w) { LOG("%s: %s\n", __FUNCTION__, state); if (state) device_reset(); } void mb87030_device::update_ssts() { if (!m_tc) m_ssts |= SSTS_TC_ZERO; else m_ssts &= ~SSTS_TC_ZERO; if (m_fifo.empty()) m_ssts |= SSTS_DREQ_EMPTY; else m_ssts &= ~SSTS_DREQ_EMPTY; if (m_fifo.full()) m_ssts |= SSTS_DREQ_FULL; else m_ssts &= ~SSTS_DREQ_FULL; } void mb87030_device::update_ints() { LOG("%s: %s\n", __FUNCTION__, (m_ints && (m_sctl & 1)) ? "true" : "false"); m_irq_handler(m_ints && (m_sctl & 1));// || (m_ints & INTS_DISCONNECTED)); } READ8_MEMBER(mb87030_device::bdid_r) { LOG("%s %02X\n", __FUNCTION__, (1 << m_bdid)); return 1 << m_bdid; } WRITE8_MEMBER(mb87030_device::bdid_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_bdid = data & 0x7; } READ8_MEMBER(mb87030_device::sctl_r) { LOG("%s: %02X\n", __FUNCTION__, m_sctl); return m_sctl; } WRITE8_MEMBER(mb87030_device::sctl_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_sctl = data; update_ints(); } READ8_MEMBER(mb87030_device::scmd_r) { LOG("%s: %02X\n", __FUNCTION__, m_scmd); return m_scmd; } WRITE8_MEMBER(mb87030_device::scmd_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_scmd = data; switch (m_scmd & SCMD_CMD_MASK) { case SCMD_CMD_BUS_RELEASE: LOG("%s: Bus release\n", __FUNCTION__); break; case SCMD_CMD_SELECT: LOG("%s: Select\n", __FUNCTION__); if (m_sctl & SCTL_ARBITRATION_ENABLE) update_state(State::ArbitrationWaitBusFree, 10); else update_state(State::SelectionWaitBusFree, 10); step(false); break; case SCMD_CMD_RESET_ATN: LOG("%s: Reset ATN\n", __FUNCTION__); // if (m_state == State::Idle) m_send_atn_during_selection = false; // else scsi_set_ctrl(0, S_ATN); break; case SCMD_CMD_SET_ATN: LOG("%s: Set ATN\n", __FUNCTION__); if (m_state == State::Idle) m_send_atn_during_selection = true; else scsi_set_ctrl(S_ATN, S_ATN); break; case SCMD_CMD_TRANSFER: if (!(m_ssts & (SSTS_INIT_CONNECTED|SSTS_TARG_CONNECTED))) break; m_dma_transfer = !(data & 0x04); LOG("%s Transfer\n", m_dma_transfer ? "DMA" : "Program"); m_ssts |= SSTS_SPC_BUSY|SSTS_XFER_IN_PROGRESS; update_state(State::TransferWaitReq, 5); break; case SCMD_CMD_TRANSFER_PAUSE: LOG("%s: Transfer Pause\n", __FUNCTION__); break; case SCMD_CMD_RESET_ACK_REQ: LOG("%s: Reset ACK/REQ\n", __FUNCTION__); if (m_ssts & SSTS_INIT_CONNECTED) scsi_set_ctrl(0, S_ACK); if (m_ssts & SSTS_TARG_CONNECTED) scsi_set_ctrl(0, S_REQ); break; case SCMD_CMD_SET_ACK_REQ: LOG("%s: Set ACK/REQ\n", __FUNCTION__); if (m_ssts & SSTS_INIT_CONNECTED) { if (scsi_bus->ctrl_r() & S_INP) { m_temp = scsi_bus->data_r(); } else { scsi_bus->data_w(scsi_refid, m_temp); } LOG("set ACK\n"); scsi_set_ctrl(S_ACK, S_ACK); } else { logerror("not connected\n"); } if (m_ssts & SSTS_TARG_CONNECTED) scsi_set_ctrl(S_REQ, S_REQ); break; } } READ8_MEMBER(mb87030_device::tmod_r) { LOG("%s: %02X\n", __FUNCTION__, m_tmod); return m_tmod; } WRITE8_MEMBER(mb87030_device::tmod_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_tmod = data; } READ8_MEMBER(mb87030_device::ints_r) { LOG("%s: %02X\n", __FUNCTION__, m_ints); return m_ints; } WRITE8_MEMBER(mb87030_device::ints_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_ints &= ~(data); update_ints(); } READ8_MEMBER(mb87030_device::psns_r) { uint32_t ctrl = scsi_get_ctrl(); uint8_t ret = (!!(ctrl & S_REQ) << 7) | (!!(ctrl & S_ACK) << 6) | (!!(ctrl & S_ATN) << 5) | (!!(ctrl & S_SEL) << 4) | (!!(ctrl & S_BSY) << 3) | (!!(ctrl & S_MSG) << 2) | (!!(ctrl & S_CTL) << 1) | (!!(ctrl & S_INP)); LOG("%s: %02X\n", __FUNCTION__, ret); return ret; } WRITE8_MEMBER(mb87030_device::sdgc_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_sdgc = data; scsi_ctrl_changed(); } READ8_MEMBER(mb87030_device::ssts_r) { LOG("%s: %02X\n", __FUNCTION__, m_ssts); update_ssts(); return m_ssts; } READ8_MEMBER(mb87030_device::serr_r) { LOG("%s: %02X\n", __FUNCTION__, m_serr); return m_serr; } READ8_MEMBER(mb87030_device::pctl_r) { LOG("%s: %02X\n", __FUNCTION__, m_pctl); return m_pctl; } WRITE8_MEMBER(mb87030_device::pctl_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_pctl = data; m_scsi_phase = m_pctl & 7; if (m_pctl & PCTL_BUS_FREE_IE) step(false); } READ8_MEMBER(mb87030_device::mbc_r) { LOG("%s: %02X\n", __FUNCTION__, m_mbc); return m_mbc; } READ8_MEMBER(mb87030_device::dreg_r) { step(false); if (!m_fifo.empty()) m_dreg = m_fifo.dequeue(); LOG("%s: %02X\n", __FUNCTION__, m_dreg); return m_dreg; } WRITE8_MEMBER(mb87030_device::dreg_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_dreg = data; if (!m_fifo.full()) m_fifo.enqueue(data); step(false); } READ8_MEMBER(mb87030_device::temp_r) { step(false); LOG("%s: %02X\n", __FUNCTION__, m_temp); return m_temp; } WRITE8_MEMBER(mb87030_device::temp_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_temp = data; step(false); } READ8_MEMBER(mb87030_device::tch_r) { uint8_t ret = (m_tc >> 16) & 0xff; LOG("%s: %02X\n", __FUNCTION__, ret); return ret; } READ8_MEMBER(mb87030_device::tcm_r) { uint8_t ret = (m_tc >> 8) & 0xff; LOG("%s: %02X\n", __FUNCTION__, ret); return ret; } READ8_MEMBER(mb87030_device::tcl_r) { uint8_t ret = m_tc & 0xff; LOG("%s: %02X\n", __FUNCTION__, ret); return ret; } WRITE8_MEMBER(mb87030_device::tch_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_tc &= 0x00ffff; m_tc |= data << 16; update_ssts(); } WRITE8_MEMBER(mb87030_device::tcm_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_tc &= 0xff00ff; m_tc |= data << 8; update_ssts(); } WRITE8_MEMBER(mb87030_device::tcl_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_tc &= 0xffff00; m_tc |= data; update_ssts(); } READ8_MEMBER(mb87030_device::exbf_r) { LOG("%s: %02X\n", __FUNCTION__, m_exbf); return m_exbf; } WRITE8_MEMBER(mb87030_device::exbf_w) { LOG("%s: %02X\n", __FUNCTION__, data); m_exbf = data; } void mb87030_device::dma_w(uint8_t data) { if (machine().side_effects_disabled()) return; LOG("dma_w: %02X\n", data); m_hdb = data; m_hdb_loaded = true; step(false); } uint8_t mb87030_device::dma_r(void) { uint8_t val = m_hdb; if (machine().side_effects_disabled()) return 0; LOG("dma_r: %02X\n", val); m_hdb_loaded = false; step(false); return val; }