diff --git a/devices/common/scsi/mesh.cpp b/devices/common/scsi/mesh.cpp index 8f4ef7c..78518b5 100644 --- a/devices/common/scsi/mesh.cpp +++ b/devices/common/scsi/mesh.cpp @@ -21,6 +21,8 @@ along with this program. If not, see . /** @file MESH (Macintosh Enhanced SCSI Hardware) controller emulation. */ +#include +#include #include #include #include @@ -34,6 +36,10 @@ int MeshController::device_postinit() { this->bus_obj = dynamic_cast(gMachineObj->get_comp_by_name("SCSI0")); + this->int_ctrl = dynamic_cast( + gMachineObj->get_comp_by_type(HWCompType::INT_CTRL)); + this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI1); + return 0; } @@ -51,12 +57,20 @@ void MeshController::reset(bool is_hard_reset) uint8_t MeshController::read(uint8_t reg_offset) { switch(reg_offset) { + case MeshReg::Sequence: + return this->cur_cmd; case MeshReg::BusStatus0: return this->bus_obj->test_ctrl_lines(0xFFU); case MeshReg::BusStatus1: return this->bus_obj->test_ctrl_lines(0xE000U) >> 8; + case MeshReg::Exception: + return 0; + case MeshReg::Error: + return 0; case MeshReg::IntMask: return this->int_mask; + case MeshReg::Interrupt: + return this->int_stat; case MeshReg::MeshID: return this->chip_id; // tell them who we are default: @@ -93,6 +107,7 @@ void MeshController::write(uint8_t reg_offset, uint8_t value) break; case MeshReg::Interrupt: this->int_stat &= ~(value & INT_MASK); // clear requested interrupt bits + update_irq(); break; case MeshReg::SourceID: this->src_id = value; @@ -103,6 +118,9 @@ void MeshController::write(uint8_t reg_offset, uint8_t value) case MeshReg::SyncParms: this->sync_params = value; break; + case MeshReg::SelTimeOut: + LOG_F(9, "MESH: selection timeout set to 0x%x", value); + break; default: LOG_F(WARNING, "MESH: write to unimplemented register at offset 0x%x", reg_offset); @@ -111,20 +129,115 @@ void MeshController::write(uint8_t reg_offset, uint8_t value) void MeshController::perform_command(const uint8_t cmd) { - this->cur_cmd = cmd & 0xF; + this->cur_cmd = cmd; this->int_stat &= ~INT_CMD_DONE; - switch (this->cur_cmd) { + switch (this->cur_cmd & 0xF) { + case SeqCmd::Arbitrate: + this->cur_state = SeqState::BUS_FREE; + this->sequencer(); + break; + case SeqCmd::Select: + this->cur_state = SeqState::SEL_BEGIN; + this->sequencer(); + break; + case SeqCmd::DisReselect: + LOG_F(INFO, "MESH: DisReselect command requested"); + break; case SeqCmd::ResetMesh: this->reset(false); this->int_stat |= INT_CMD_DONE; break; + case SeqCmd::FlushFIFO: + LOG_F(INFO, "MESH: FlushFIFO command requested"); + break; default: LOG_F(ERROR, "MESH: unsupported sequencer command 0x%X", this->cur_cmd); } } +void MeshController::seq_defer_state(uint64_t delay_ns) +{ + seq_timer_id = TimerManager::get_instance()->add_oneshot_timer( + delay_ns, + [this]() { + // re-enter the sequencer with the state specified in next_state + this->cur_state = this->next_state; + this->sequencer(); + }); +} + +void MeshController::sequencer() +{ + switch (this->cur_state) { + case SeqState::IDLE: + break; + case SeqState::BUS_FREE: + if (this->bus_obj->current_phase() == ScsiPhase::BUS_FREE) { + this->next_state = SeqState::ARB_BEGIN; + this->seq_defer_state(BUS_FREE_DELAY + BUS_SETTLE_DELAY); + } else { // continue waiting + this->next_state = SeqState::BUS_FREE; + this->seq_defer_state(BUS_FREE_DELAY); + } + break; + case SeqState::ARB_BEGIN: + if (!this->bus_obj->begin_arbitration(this->src_id)) { + LOG_F(ERROR, "MESH: arbitration error, bus not free!"); + this->bus_obj->release_ctrl_lines(this->src_id); + this->next_state = SeqState::BUS_FREE; + this->seq_defer_state(BUS_CLEAR_DELAY); + break; + } + this->next_state = SeqState::ARB_END; + this->seq_defer_state(ARB_DELAY); + break; + case SeqState::ARB_END: + if (this->bus_obj->end_arbitration(this->src_id) && + !this->bus_obj->test_ctrl_lines(SCSI_CTRL_SEL)) { // arbitration won + this->bus_obj->assert_ctrl_line(this->src_id, SCSI_CTRL_SEL); + } else { // arbitration lost + LOG_F(INFO, "MESH: arbitration lost!"); + this->bus_obj->release_ctrl_lines(this->src_id); + this->exception |= EXC_ARB_LOST; + this->int_stat |= INT_EXCEPTION; + } + this->int_stat |= INT_CMD_DONE; + update_irq(); + break; + case SeqState::SEL_BEGIN: + this->bus_obj->begin_selection(this->src_id, this->dst_id, this->cur_cmd & 0x20); + this->next_state = SeqState::SEL_END; + this->seq_defer_state(SEL_TIME_OUT); + break; + case SeqState::SEL_END: + if (this->bus_obj->end_selection(this->src_id, this->dst_id)) { + this->bus_obj->release_ctrl_line(this->src_id, SCSI_CTRL_SEL); + LOG_F(9, "MESH: selection completed"); + } else { // selection timeout + this->bus_obj->disconnect(this->src_id); + this->cur_state = SeqState::IDLE; + this->exception |= EXC_SEL_TIMEOUT; + this->int_stat |= INT_EXCEPTION; + } + this->int_stat |= INT_CMD_DONE; + update_irq(); + break; + default: + ABORT_F("MESH: unimplemented sequencer state %d", this->cur_state); + } +} + +void MeshController::update_irq() +{ + uint8_t new_irq = !!(this->int_stat & this->int_mask); + if (new_irq != this->irq) { + this->irq = new_irq; + this->int_ctrl->ack_int(this->irq_id, new_irq); + } +} + static const DeviceDescription Mesh_Descriptor = { MeshController::create, {}, {} }; diff --git a/devices/common/scsi/mesh.h b/devices/common/scsi/mesh.h index 5d8316b..8384eb7 100644 --- a/devices/common/scsi/mesh.h +++ b/devices/common/scsi/mesh.h @@ -25,6 +25,7 @@ along with this program. If not, see . #define MESH_H #include +#include #include #include @@ -59,7 +60,17 @@ enum MeshReg : uint8_t { enum SeqCmd : uint8_t { NoOperation = 0, Arbitrate = 1, + Select = 2, + DisReselect = 0xD, ResetMesh = 0xE, + FlushFIFO = 0xF, +}; + +// Exception register bits. +enum { + EXC_SEL_TIMEOUT = 1 << 0, + EXC_PHASE_MM = 1 << 1, + EXC_ARB_LOST = 1 << 2, }; // Interrupt register bits. @@ -70,6 +81,25 @@ enum { INT_MASK = INT_CMD_DONE | INT_EXCEPTION | INT_ERROR }; + +enum SeqState : uint32_t { + IDLE = 0, + BUS_FREE, + ARB_BEGIN, + ARB_END, + SEL_BEGIN, + SEL_END, + SEND_MSG, + SEND_CMD, + CMD_COMPLETE, + XFER_BEGIN, + XFER_END, + SEND_DATA, + RCV_DATA, + RCV_STATUS, + RCV_MESSAGE, +}; + }; // namespace MeshScsi class MeshController : public HWComponent { @@ -95,6 +125,9 @@ public: protected: void reset(bool is_hard_reset); void perform_command(const uint8_t cmd); + void seq_defer_state(uint64_t delay_ns); + void sequencer(); + void update_irq(); private: uint8_t chip_id; @@ -104,9 +137,21 @@ private: uint8_t src_id; uint8_t dst_id; uint8_t cur_cmd; + uint8_t error; + uint8_t exception; ScsiBus* bus_obj; uint16_t bus_stat; + + // Sequencer state + uint32_t seq_timer_id; + uint32_t cur_state; + uint32_t next_state; + + // interrupt related stuff + InterruptCtrl* int_ctrl = nullptr; + uint32_t irq_id = 0; + uint8_t irq = 0; }; #endif // MESH_H