diff --git a/devices/common/scsi/sc53c94.cpp b/devices/common/scsi/sc53c94.cpp index ac3dd4d..e6f58be 100644 --- a/devices/common/scsi/sc53c94.cpp +++ b/devices/common/scsi/sc53c94.cpp @@ -21,27 +21,46 @@ along with this program. If not, see . /** @file NCR53C94/Am53CF94 SCSI controller emulation. */ -#include "sc53c94.h" +#include +#include +#include #include +#include #include -Sc53C94::Sc53C94(uint8_t chip_id) +Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) { - this->chip_id = chip_id; + this->chip_id = chip_id; + this->my_bus_id = my_id; + supports_types(HWCompType::SCSI_HOST | HWCompType::SCSI_DEV); reset_device(); } +int Sc53C94::device_postinit() +{ + this->bus_obj = dynamic_cast(gMachineObj->get_comp_by_name("SCSI0")); + this->bus_obj->register_device(7, static_cast(this)); + return 0; +} + void Sc53C94::reset_device() { // part-unique ID to be read using a magic sequence this->set_xfer_count = this->chip_id << 16; - this->clk_factor = 2; - this->sel_timeout = 0; + this->clk_factor = 2; + this->sel_timeout = 0; + this->is_initiator = true; // clear command FIFO this->cmd_fifo_pos = 0; + + // clear data FIFO + this->data_fifo_pos = 0; + this->data_fifo[0] = 0; + + this->seq_step = 0; } uint8_t Sc53C94::read(uint8_t reg_offset) @@ -70,13 +89,25 @@ void Sc53C94::write(uint8_t reg_offset, uint8_t value) case Write::Reg53C94::Command: update_command_reg(value); break; + case Write::Reg53C94::FIFO: + fifo_push(value); + break; + case Write::Reg53C94::Dest_Bus_ID: + this->target_id = value & 7; + break; case Write::Reg53C94::Sel_Timeout: this->sel_timeout = value; break; + case Write::Reg53C94::Sync_Offset: + this->sync_offset = value; + break; case Write::Reg53C94::Clock_Factor: this->clk_factor = value; break; case Write::Reg53C94::Config_1: + if ((value & 7) != this->my_bus_id) { + ABORT_F("SC53C94: HBA bus ID mismatch!"); + } this->config1 = value; break; case Write::Reg53C94::Config_2: @@ -131,6 +162,9 @@ void Sc53C94::exec_command() } } + // simple commands will be executed immediately + // complex commands will be broken into multiple steps + // and handled by the sequencer switch (cmd) { case CMD_NOP: this->on_reset = false; // unblock the command register @@ -147,12 +181,36 @@ void Sc53C94::exec_command() return; case CMD_RESET_BUS: LOG_F(INFO, "SC53C94: resetting SCSI bus..."); + // assert RST line + this->bus_obj->assert_ctrl_line(this->my_bus_id, SCSI_CTRL_RST); + // release RST line after 25 us + my_timer_id = TimerManager::get_instance()->add_oneshot_timer( + USECS_TO_NSECS(25), + [this]() { + this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_RST); + }); + if (!(config1 & 0x40)) { + LOG_F(INFO, "SC53C94: reset interrupt issued"); + this->int_status |= INTSTAT_SRST; + } exec_next_command(); break; + case CMD_SELECT_NO_ATN: + static SeqDesc sel_no_atn_desc[] { + {SeqState::SEL_BEGIN, 2, INTSTAT_SR | INTSTAT_SO}, + {SeqState::CMD_BEGIN, 3, INTSTAT_SR | INTSTAT_SO}, + {SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO}, + }; + this->seq_step = 0; + this->cmd_steps = sel_no_atn_desc; + this->cur_state = SeqState::BUS_FREE; + sequencer(); + LOG_F(INFO, "SC53C94: SELECT W/O ATN command started"); + break; default: LOG_F(ERROR, "SC53C94: invalid/unimplemented command 0x%X", cmd); this->cmd_fifo_pos--; // remove invalid command from FIFO - this->int_status |= 0x40; // set ICMD bit + this->int_status |= INTSTAT_ICMD; } } @@ -166,3 +224,102 @@ void Sc53C94::exec_next_command() } } } + +void Sc53C94::fifo_push(const uint8_t data) +{ + if (this->data_fifo_pos < 16) { + this->data_fifo[this->data_fifo_pos++] = data; + } else { + LOG_F(ERROR, "SC53C94: data FIFO overflow!"); + this->status |= 0x40; // signal IOE/Gross Error + } +} + +void Sc53C94::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 Sc53C94::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->my_bus_id)) { + LOG_F(ERROR, "SC53C94: arbitration error, bus not free!"); + this->bus_obj->release_ctrl_lines(this->my_bus_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->my_bus_id)) { // arbitration won + this->next_state = this->cmd_steps->next_step; + this->seq_defer_state(BUS_CLEAR_DELAY + BUS_SETTLE_DELAY); + } else { // arbitration lost + LOG_F(INFO, "SC53C94: arbitration lost!"); + this->bus_obj->release_ctrl_lines(this->my_bus_id); + this->next_state = SeqState::BUS_FREE; + this->seq_defer_state(BUS_CLEAR_DELAY); + } + break; + case SeqState::SEL_BEGIN: + this->is_initiator = true; + this->bus_obj->begin_selection(this->my_bus_id, this->target_id, + this->cur_cmd != CMD_SELECT_NO_ATN); + 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->my_bus_id, this->target_id)) { + LOG_F(INFO, "SC53C94: selection completed"); + this->cmd_steps++; + } else { // selection timeout + this->seq_step = this->cmd_steps->step_num; + this->int_status |= this->cmd_steps->status; + this->bus_obj->release_ctrl_lines(this->my_bus_id); + this->cur_state = SeqState::IDLE; + exec_next_command(); + } + break; + default: + ABORT_F("SC53C94: unimplemented sequencer state %d", this->cur_state); + } +} + +void Sc53C94::notify(ScsiMsg msg_type, int param) +{ + switch (msg_type) { + case ScsiMsg::CONFIRM_SEL: + if (this->target_id == param) { + // cancel selection timeout timer + TimerManager::get_instance()->cancel_timer(this->seq_timer_id); + this->cur_state = SeqState::SEL_END; + sequencer(); + } else { + LOG_F(WARNING, "SC53C94: ignore invalid selection confirmation message"); + } + break; + default: + LOG_F(WARNING, "SC53C94: ignore notification message, type: %d", msg_type); + } +} diff --git a/devices/common/scsi/sc53c94.h b/devices/common/scsi/sc53c94.h index 79a867e..bb96645 100644 --- a/devices/common/scsi/sc53c94.h +++ b/devices/common/scsi/sc53c94.h @@ -29,6 +29,8 @@ along with this program. If not, see . #ifndef SC_53C94_H #define SC_53C94_H +#include "scsi.h" + #include /** 53C94 read registers */ @@ -59,8 +61,8 @@ namespace Write { Command = 3, Dest_Bus_ID = 4, Sel_Timeout = 5, - Synch_Period = 6, - Synch_Offset = 7, + Sync_Period = 6, + Sync_Offset = 7, Config_1 = 8, Clock_Factor = 9, Test_Mode = 0xA, @@ -74,34 +76,82 @@ namespace Write { /** NCR53C94/Am53CF94 commands. */ enum { - CMD_NOP = 0, - CMD_CLEAR_FIFO = 1, - CMD_RESET_DEVICE = 2, - CMD_RESET_BUS = 3, - CMD_DMA_STOP = 4, + CMD_NOP = 0, + CMD_CLEAR_FIFO = 1, + CMD_RESET_DEVICE = 2, + CMD_RESET_BUS = 3, + CMD_DMA_STOP = 4, + CMD_SELECT_NO_ATN = 0x41, +}; + +/** Interrupt status register bits. */ +enum { + INTSTAT_SRST = 0x80, // bus reset + INTSTAT_ICMD = 0x40, // invalid command + INTSTAT_DIS = 0x20, // disconnected + INTSTAT_SR = 0x10, // service request + INTSTAT_SO = 0x08, // successful operation + INTSTAT_RESEL = 0x04, // reselected + INTSTAT_SELA = 0x02, // selected as a target with attention + INTSTAT_SEL = 0x01, // selected as a target without attention }; enum { - CFG2_ENF = 0x40, // Am53CF94: enable features (ENF) bit + CFG2_ENF = 0x40, // Am53CF94: enable features (ENF) bit }; -class Sc53C94 { +/** Sequencer states. */ +namespace SeqState { + enum { + IDLE = 0, + BUS_FREE, + ARB_BEGIN, + ARB_END, + SEL_BEGIN, + SEL_END, + CMD_BEGIN, + CMD_COMPLETE, + }; +}; + +/** Sequence descriptor for sequencer commands. */ +typedef struct { + int next_step; + int step_num; + int status; +} SeqDesc; + +class Sc53C94 : public ScsiDevice { public: - Sc53C94(uint8_t chip_id=12); + Sc53C94(uint8_t chip_id=12, uint8_t my_id=7); ~Sc53C94() = default; + // HWComponent methods + int device_postinit(); + // 53C94 registers access uint8_t read(uint8_t reg_offset); void write(uint8_t reg_offset, uint8_t value); + // ScsiDevice methods + void notify(ScsiMsg msg_type, int param); + protected: void reset_device(); void update_command_reg(uint8_t cmd); void exec_command(); void exec_next_command(); + void fifo_push(const uint8_t data); + + void sequencer(); + void seq_defer_state(uint64_t delay_ns); private: uint8_t chip_id; + uint8_t my_bus_id; + ScsiBus* bus_obj; + uint32_t my_timer_id; + uint8_t cmd_fifo[2]; uint8_t data_fifo[16]; int cmd_fifo_pos; @@ -110,12 +160,23 @@ private: uint32_t xfer_count; uint32_t set_xfer_count; uint8_t status; + uint8_t target_id; uint8_t int_status; + uint8_t seq_step; uint8_t sel_timeout; + uint8_t sync_offset; uint8_t clk_factor; uint8_t config1; uint8_t config2; uint8_t config3; + + // sequencer state + uint32_t seq_timer_id; + uint32_t cur_state; + uint32_t next_state; + SeqDesc* cmd_steps; + bool is_initiator; + uint8_t cur_cmd; }; #endif // SC_53C94_H