mirror of
https://github.com/dingusdev/dingusppc.git
synced 2024-06-16 06:29:31 +00:00
8a1055ed1b
- For pdm/amic, real_dma_xfer is called when SCSI_DMA_Ctrl has the run bit set. - For tnt/grandcentral, dma_wait is called when the DBDMA is started (run bit is set). It will call real_dma_xfer when the phase and sequence are DATA_IN/RCV_DATA or DATA_OUT/SEND_DATA. - dma_wait and real_dma_xfer uses a one shot timer instead of a loop to continue doing DMA while also giving time to the CPU. This and the above changes handles the case where the DBDMA is started before setting up the transfer phase and sequence. - dma_stop will stop the one shot timer when the DBDMA channel is stopped.
761 lines
24 KiB
C++
761 lines
24 KiB
C++
/*
|
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
|
Copyright (C) 2018-24 divingkatae and maximum
|
|
(theweirdo) spatium
|
|
|
|
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** @file NCR53C94/Am53CF94 SCSI controller emulation. */
|
|
|
|
#include <core/timermanager.h>
|
|
#include <devices/common/dmacore.h>
|
|
#include <devices/common/hwcomponent.h>
|
|
#include <devices/common/hwinterrupt.h>
|
|
#include <devices/common/scsi/sc53c94.h>
|
|
#include <devices/deviceregistry.h>
|
|
#include <loguru.hpp>
|
|
#include <machines/machinebase.h>
|
|
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
|
|
Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) : ScsiDevice("SC53C94", my_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()
|
|
{
|
|
ScsiBus* bus = dynamic_cast<ScsiBus*>(gMachineObj->get_comp_by_name("ScsiCurio"));
|
|
if (bus) {
|
|
bus->register_device(7, static_cast<ScsiDevice*>(this));
|
|
bus->attach_scsi_devices("");
|
|
}
|
|
|
|
this->int_ctrl = dynamic_cast<InterruptCtrl*>(
|
|
gMachineObj->get_comp_by_type(HWCompType::INT_CTRL));
|
|
this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI_CURIO);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Sc53C94::reset_device()
|
|
{
|
|
// part-unique ID to be read using a magic sequence
|
|
this->xfer_count = this->chip_id << 16;
|
|
|
|
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;
|
|
|
|
this->status = 0;
|
|
}
|
|
|
|
uint8_t Sc53C94::read(uint8_t reg_offset)
|
|
{
|
|
uint8_t status, int_status;
|
|
|
|
switch (reg_offset) {
|
|
case Read::Reg53C94::Xfer_Cnt_LSB:
|
|
return this->xfer_count & 0xFFU;
|
|
case Read::Reg53C94::Xfer_Cnt_MSB:
|
|
return (this->xfer_count >> 8) & 0xFFU;
|
|
case Read::Reg53C94::FIFO:
|
|
return this->fifo_pop();
|
|
case Read::Reg53C94::Command:
|
|
return this->cmd_fifo[0];
|
|
case Read::Reg53C94::Status:
|
|
status = bus_obj->test_ctrl_lines(SCSI_CTRL_MSG | SCSI_CTRL_CD | SCSI_CTRL_IO);
|
|
return (this->status & 0xF8) | status;
|
|
case Read::Reg53C94::Int_Status:
|
|
int_status = this->int_status;
|
|
this->seq_step = 0;
|
|
this->int_status = 0;
|
|
this->update_irq();
|
|
return int_status;
|
|
case Read::Reg53C94::Seq_Step:
|
|
return this->seq_step;
|
|
case Read::Reg53C94::FIFO_Flags:
|
|
return (this->seq_step << 5) | (this->data_fifo_pos & 0x1F);
|
|
case Read::Reg53C94::Config_1:
|
|
return this->config1;
|
|
case Read::Reg53C94::Config_3:
|
|
return this->config3;
|
|
case Read::Reg53C94::Xfer_Cnt_Hi:
|
|
if (this->config2 & CFG2_ENF) {
|
|
return (this->xfer_count >> 16) & 0xFFU;
|
|
}
|
|
break;
|
|
default:
|
|
LOG_F(INFO, "%s: reading from register %d", this->name.c_str(), reg_offset);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Sc53C94::write(uint8_t reg_offset, uint8_t value)
|
|
{
|
|
switch (reg_offset) {
|
|
case Write::Reg53C94::Xfer_Cnt_LSB:
|
|
this->set_xfer_count = (this->set_xfer_count & ~0xFFU) | value;
|
|
break;
|
|
case Write::Reg53C94::Xfer_Cnt_MSB:
|
|
this->set_xfer_count = (this->set_xfer_count & ~0xFF00U) | (value << 8);
|
|
break;
|
|
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("%s: HBA bus ID mismatch!", this->name.c_str());
|
|
}
|
|
this->config1 = value;
|
|
break;
|
|
case Write::Reg53C94::Config_2:
|
|
this->config2 = value;
|
|
break;
|
|
case Write::Reg53C94::Config_3:
|
|
this->config3 = value;
|
|
break;
|
|
default:
|
|
LOG_F(INFO, "%s: writing 0x%X to %d register", this->name.c_str(), value,
|
|
reg_offset);
|
|
}
|
|
}
|
|
|
|
uint16_t Sc53C94::pseudo_dma_read()
|
|
{
|
|
uint16_t data_word;
|
|
bool is_done = false;
|
|
|
|
if (this->data_fifo_pos >= 2) {
|
|
// remove one word from FIFO
|
|
data_word = (this->data_fifo[0] << 8) | this->data_fifo[1];
|
|
this->data_fifo_pos -= 2;
|
|
std:memmove(this->data_fifo, &this->data_fifo[2], this->data_fifo_pos);
|
|
|
|
// update DMA status
|
|
if (this->is_dma_cmd) {
|
|
this->xfer_count -= 2;
|
|
if (!this->xfer_count) {
|
|
is_done = true;
|
|
this->status |= STAT_TC; // signal zero transfer count
|
|
this->cur_state = SeqState::XFER_END;
|
|
this->sequencer();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
LOG_F(ERROR, "SC53C94: FIFO underrun %d", data_fifo_pos);
|
|
data_word = 0;
|
|
}
|
|
|
|
// see if we need to refill FIFO
|
|
if (!this->data_fifo_pos && !is_done) {
|
|
this->sequencer();
|
|
}
|
|
|
|
return data_word;
|
|
}
|
|
|
|
void Sc53C94::pseudo_dma_write(uint16_t data) {
|
|
this->fifo_push((data >> 8) & 0xFFU);
|
|
this->fifo_push(data & 0xFFU);
|
|
|
|
// update DMA status
|
|
if (this->is_dma_cmd) {
|
|
this->xfer_count -= 2;
|
|
if (!this->xfer_count) {
|
|
this->status |= STAT_TC; // signal zero transfer count
|
|
//this->cur_state = SeqState::XFER_END;
|
|
this->sequencer();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sc53C94::update_command_reg(uint8_t cmd)
|
|
{
|
|
if (this->on_reset && (cmd & CMD_OPCODE) != CMD_NOP) {
|
|
LOG_F(WARNING, "%s: command register blocked after RESET!", this->name.c_str());
|
|
return;
|
|
}
|
|
|
|
// NOTE: Reset Device (chip), Reset Bus and DMA Stop commands execute
|
|
// immediately while all others are placed into the command FIFO
|
|
switch (cmd & CMD_OPCODE) {
|
|
case CMD_RESET_DEVICE:
|
|
case CMD_RESET_BUS:
|
|
case CMD_DMA_STOP:
|
|
this->cmd_fifo_pos = 0; // put them at the bottom of the command FIFO
|
|
}
|
|
|
|
if (this->cmd_fifo_pos < 2) {
|
|
// put new command into the command FIFO
|
|
this->cmd_fifo[this->cmd_fifo_pos++] = cmd;
|
|
if (this->cmd_fifo_pos == 1) {
|
|
exec_command();
|
|
}
|
|
} else {
|
|
LOG_F(ERROR, "%s: the top of the command FIFO overwritten!", this->name.c_str());
|
|
this->status |= STAT_GE; // signal IOE/Gross Error
|
|
}
|
|
}
|
|
|
|
void Sc53C94::exec_command()
|
|
{
|
|
uint8_t cmd = this->cur_cmd = this->cmd_fifo[0] & 0x7F;
|
|
|
|
this->is_dma_cmd = !!(this->cmd_fifo[0] & 0x80);
|
|
|
|
if (this->is_dma_cmd) {
|
|
if (this->config2 & CFG2_ENF) { // extended mode: 24-bit
|
|
this->xfer_count = this->set_xfer_count & 0xFFFFFFUL;
|
|
} else { // standard mode: 16-bit
|
|
this->xfer_count = this->set_xfer_count & 0xFFFFUL;
|
|
if (!this->xfer_count) {
|
|
this->xfer_count = 65536;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
exec_next_command();
|
|
break;
|
|
case CMD_CLEAR_FIFO:
|
|
this->data_fifo_pos = 0; // set the bottom of the data FIFO to zero
|
|
this->data_fifo[0] = 0;
|
|
exec_next_command();
|
|
break;
|
|
case CMD_RESET_DEVICE:
|
|
reset_device();
|
|
this->on_reset = true; // block the command register
|
|
return;
|
|
case CMD_RESET_BUS:
|
|
LOG_F(INFO, "%s: resetting SCSI bus...", this->name.c_str());
|
|
// assert RST line
|
|
this->bus_obj->assert_ctrl_line(this->my_bus_id, SCSI_CTRL_RST);
|
|
// release RST line after 25 us
|
|
if (my_timer_id) {
|
|
TimerManager::get_instance()->cancel_timer(this->my_timer_id);
|
|
my_timer_id = 0;
|
|
}
|
|
my_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
USECS_TO_NSECS(25),
|
|
[this]() {
|
|
my_timer_id = 0;
|
|
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_RST);
|
|
});
|
|
if (!(config1 & 0x40)) {
|
|
LOG_F(INFO, "%s: reset interrupt issued", this->name.c_str());
|
|
this->int_status = INTSTAT_SRST;
|
|
}
|
|
exec_next_command();
|
|
break;
|
|
case CMD_XFER:
|
|
if (!this->is_initiator) {
|
|
// clear command FIFO
|
|
this->cmd_fifo_pos = 0;
|
|
this->int_status = INTSTAT_ICMD;
|
|
this->update_irq();
|
|
} else {
|
|
this->seq_step = 0;
|
|
this->cmd_steps = nullptr;
|
|
this->cur_state = SeqState::XFER_BEGIN;
|
|
this->sequencer();
|
|
}
|
|
break;
|
|
case CMD_COMPLETE_STEPS:
|
|
static SeqDesc * complete_steps_desc = new SeqDesc[3]{
|
|
{(CMD_COMPLETE_STEPS << 8) + 1, SeqState::RCV_STATUS, 0, 0},
|
|
{(CMD_COMPLETE_STEPS << 8) + 2, SeqState::RCV_MESSAGE, 0, 0},
|
|
{(CMD_COMPLETE_STEPS << 8) + 3, SeqState::CMD_COMPLETE, 0, INTSTAT_SR}
|
|
};
|
|
if (this->bus_obj->current_phase() != ScsiPhase::STATUS) {
|
|
ABORT_F("%s: complete steps only works in the STATUS phase", this->name.c_str());
|
|
}
|
|
this->seq_step = 0;
|
|
this->cmd_steps = complete_steps_desc;
|
|
this->cur_state = this->cmd_steps->next_step;
|
|
this->sequencer();
|
|
break;
|
|
case CMD_MSG_ACCEPTED:
|
|
if (this->is_initiator) {
|
|
this->bus_obj->target_next_step();
|
|
}
|
|
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_ACK);
|
|
this->int_status = INTSTAT_SR;
|
|
this->int_status |= INTSTAT_DIS; // TODO: handle target disconnection properly
|
|
this->update_irq();
|
|
exec_next_command();
|
|
break;
|
|
case CMD_SELECT_NO_ATN:
|
|
static SeqDesc * sel_no_atn_desc = new SeqDesc[3]{
|
|
{(CMD_SELECT_NO_ATN << 8) + 1, SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
|
|
{(CMD_SELECT_NO_ATN << 8) + 2, SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
|
|
{(CMD_SELECT_NO_ATN << 8) + 3, 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;
|
|
this->sequencer();
|
|
LOG_F(9, "%s: SELECT W/O ATN command started", this->name.c_str());
|
|
break;
|
|
case CMD_SELECT_WITH_ATN:
|
|
static SeqDesc * sel_with_atn_desc = new SeqDesc[4]{
|
|
{(CMD_SELECT_WITH_ATN << 8) + 1, SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
|
|
{(CMD_SELECT_WITH_ATN << 8) + 2, SeqState::SEND_MSG, 2, INTSTAT_SR | INTSTAT_SO},
|
|
{(CMD_SELECT_WITH_ATN << 8) + 3, SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
|
|
{(CMD_SELECT_WITH_ATN << 8) + 4, SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO},
|
|
};
|
|
this->seq_step = 0;
|
|
this->bytes_out = 1; // set message length
|
|
this->cmd_steps = sel_with_atn_desc;
|
|
this->cur_state = SeqState::BUS_FREE;
|
|
this->sequencer();
|
|
LOG_F(9, "%s: SELECT WITH ATN command started", this->name.c_str());
|
|
break;
|
|
case CMD_ENA_SEL_RESEL:
|
|
exec_next_command();
|
|
break;
|
|
default:
|
|
LOG_F(ERROR, "%s: invalid/unimplemented command 0x%X", this->name.c_str(), cmd);
|
|
this->cmd_fifo_pos--; // remove invalid command from FIFO
|
|
this->int_status = INTSTAT_ICMD;
|
|
this->update_irq();
|
|
}
|
|
}
|
|
|
|
void Sc53C94::exec_next_command()
|
|
{
|
|
if (this->cmd_fifo_pos) { // skip empty command FIFO
|
|
this->cmd_fifo_pos--; // remove completed command
|
|
if (this->cmd_fifo_pos) { // is there another command in the FIFO?
|
|
this->cmd_fifo[0] = this->cmd_fifo[1]; // top -> bottom
|
|
exec_command(); // execute it
|
|
}
|
|
}
|
|
}
|
|
|
|
#define DATA_FIFO_MAX 16
|
|
|
|
void Sc53C94::fifo_push(const uint8_t data)
|
|
{
|
|
if (this->data_fifo_pos < DATA_FIFO_MAX) {
|
|
this->data_fifo[this->data_fifo_pos++] = data;
|
|
} else {
|
|
LOG_F(ERROR, "%s: data FIFO overflow!", this->name.c_str());
|
|
this->status |= STAT_GE; // signal IOE/Gross Error
|
|
}
|
|
}
|
|
|
|
uint8_t Sc53C94::fifo_pop()
|
|
{
|
|
uint8_t data = 0;
|
|
|
|
if (this->data_fifo_pos < 1) {
|
|
LOG_F(ERROR, "%s: data FIFO underflow!", this->name.c_str());
|
|
this->status |= STAT_GE; // signal IOE/Gross Error
|
|
} else {
|
|
data = this->data_fifo[0];
|
|
this->data_fifo_pos--;
|
|
std:memmove(this->data_fifo, &this->data_fifo[1], this->data_fifo_pos);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
void Sc53C94::seq_defer_state(uint64_t delay_ns)
|
|
{
|
|
if (seq_timer_id) {
|
|
TimerManager::get_instance()->cancel_timer(this->seq_timer_id);
|
|
seq_timer_id = 0;
|
|
}
|
|
|
|
seq_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
delay_ns,
|
|
[this]() {
|
|
// re-enter the sequencer with the state specified in next_state
|
|
this->seq_timer_id = 0;
|
|
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, "%s: arbitration error, bus not free!", this->name.c_str());
|
|
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, "%s: arbitration lost!", this->name.c_str());
|
|
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)) {
|
|
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_SEL);
|
|
LOG_F(9, "%s: selection completed", this->name.c_str());
|
|
} else { // selection timeout
|
|
this->seq_step = this->cmd_steps->step_num;
|
|
this->int_status = this->cmd_steps->status;
|
|
this->bus_obj->disconnect(this->my_bus_id);
|
|
this->cur_state = SeqState::IDLE;
|
|
this->update_irq();
|
|
exec_next_command();
|
|
}
|
|
break;
|
|
case SeqState::SEND_MSG:
|
|
if (this->data_fifo_pos < 1 && this->is_dma_cmd) {
|
|
this->drq_cb(1);
|
|
this->int_status = INTSTAT_SR;
|
|
this->update_irq();
|
|
break;
|
|
}
|
|
this->bus_obj->target_xfer_data();
|
|
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_ATN);
|
|
break;
|
|
case SeqState::SEND_CMD:
|
|
if (this->data_fifo_pos < 1 && this->is_dma_cmd) {
|
|
this->drq_cb(1);
|
|
this->int_status |= INTSTAT_SR;
|
|
this->update_irq();
|
|
break;
|
|
}
|
|
this->bus_obj->target_xfer_data();
|
|
break;
|
|
case SeqState::CMD_COMPLETE:
|
|
this->seq_step = this->cmd_steps->step_num;
|
|
this->int_status = this->cmd_steps->status;
|
|
this->update_irq();
|
|
exec_next_command();
|
|
break;
|
|
case SeqState::XFER_BEGIN:
|
|
this->cur_bus_phase = this->bus_obj->current_phase();
|
|
switch (this->cur_bus_phase) {
|
|
case ScsiPhase::DATA_OUT:
|
|
if (this->is_dma_cmd) {
|
|
this->cur_state = SeqState::SEND_DATA;
|
|
break;
|
|
}
|
|
this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos);
|
|
this->data_fifo_pos = 0;
|
|
this->cur_state = SeqState::XFER_END;
|
|
this->sequencer();
|
|
break;
|
|
case ScsiPhase::DATA_IN:
|
|
this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out);
|
|
this->cur_state = SeqState::RCV_DATA;
|
|
this->rcv_data();
|
|
if (!(this->is_dma_cmd)) {
|
|
this->cur_state = SeqState::XFER_END;
|
|
this->sequencer();
|
|
}
|
|
}
|
|
break;
|
|
case SeqState::XFER_END:
|
|
if (this->is_initiator) {
|
|
this->bus_obj->target_next_step();
|
|
}
|
|
this->int_status = INTSTAT_SR;
|
|
this->update_irq();
|
|
exec_next_command();
|
|
break;
|
|
case SeqState::SEND_DATA:
|
|
break;
|
|
case SeqState::RCV_DATA:
|
|
// check for unexpected bus phase changes
|
|
if (this->bus_obj->current_phase() != this->cur_bus_phase) {
|
|
this->cmd_fifo_pos = 0; // clear command FIFO
|
|
this->int_status = INTSTAT_SR;
|
|
this->update_irq();
|
|
} else {
|
|
this->rcv_data();
|
|
}
|
|
break;
|
|
case SeqState::RCV_STATUS:
|
|
case SeqState::RCV_MESSAGE:
|
|
this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out);
|
|
this->rcv_data();
|
|
if (this->is_initiator) {
|
|
if (this->cur_state == SeqState::RCV_STATUS) {
|
|
this->bus_obj->target_next_step();
|
|
} else if (this->cur_state == SeqState::RCV_MESSAGE) {
|
|
this->bus_obj->assert_ctrl_line(this->my_bus_id, SCSI_CTRL_ACK);
|
|
this->cmd_steps++;
|
|
this->cur_state = this->cmd_steps->next_step;
|
|
this->sequencer();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
ABORT_F("%s: unimplemented sequencer state %d", this->name.c_str(), this->cur_state);
|
|
}
|
|
}
|
|
|
|
void Sc53C94::update_irq()
|
|
{
|
|
uint8_t new_irq = !!(this->int_status != 0);
|
|
if (new_irq != this->irq) {
|
|
this->irq = new_irq;
|
|
this->status = (this->status & ~STAT_INT) | (new_irq << 7);
|
|
this->int_ctrl->ack_int(this->irq_id, new_irq);
|
|
}
|
|
}
|
|
|
|
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);
|
|
seq_timer_id = 0;
|
|
this->cur_state = SeqState::SEL_END;
|
|
this->sequencer();
|
|
} else {
|
|
LOG_F(WARNING, "%s: ignore invalid selection confirmation message",
|
|
this->name.c_str());
|
|
}
|
|
break;
|
|
case ScsiMsg::BUS_PHASE_CHANGE:
|
|
this->cur_bus_phase = param;
|
|
if (param != ScsiPhase::BUS_FREE && this->cmd_steps != nullptr) {
|
|
this->cmd_steps++;
|
|
this->cur_state = this->cmd_steps->next_step;
|
|
this->sequencer();
|
|
}
|
|
break;
|
|
default:
|
|
LOG_F(9, "%s: ignore notification message, type: %d", this->name.c_str(),
|
|
msg_type);
|
|
}
|
|
}
|
|
|
|
int Sc53C94::send_data(uint8_t* dst_ptr, int count)
|
|
{
|
|
if (dst_ptr == nullptr || !count) {
|
|
return 0;
|
|
}
|
|
|
|
int actual_count = std::min(this->data_fifo_pos, count);
|
|
|
|
// move data out of the data FIFO
|
|
std::memcpy(dst_ptr, this->data_fifo, actual_count);
|
|
|
|
// remove the just readed data from the data FIFO
|
|
this->data_fifo_pos -= actual_count;
|
|
if (this->data_fifo_pos > 0) {
|
|
std::memmove(this->data_fifo, &this->data_fifo[actual_count], this->data_fifo_pos);
|
|
} else if (this->cur_bus_phase == ScsiPhase::DATA_OUT) {
|
|
this->cmd_steps++;
|
|
this->cur_state = this->cmd_steps->next_step;
|
|
this->sequencer();
|
|
}
|
|
|
|
return actual_count;
|
|
}
|
|
|
|
bool Sc53C94::rcv_data()
|
|
{
|
|
int req_count;
|
|
|
|
// return if REQ line is negated
|
|
if (!this->bus_obj->test_ctrl_lines(SCSI_CTRL_REQ)) {
|
|
return false;
|
|
}
|
|
|
|
if (this->is_dma_cmd && this->cur_bus_phase == ScsiPhase::DATA_IN) {
|
|
req_count = std::min((int)this->xfer_count, DATA_FIFO_MAX - this->data_fifo_pos);
|
|
} else {
|
|
req_count = 1;
|
|
}
|
|
|
|
this->bus_obj->pull_data(this->target_id, &this->data_fifo[this->data_fifo_pos], req_count);
|
|
this->data_fifo_pos += req_count;
|
|
return true;
|
|
}
|
|
|
|
void Sc53C94::real_dma_xfer_out()
|
|
{
|
|
// transfer data from host's memory to target
|
|
|
|
if (this->xfer_count) {
|
|
uint32_t got_bytes;
|
|
uint8_t* src_ptr;
|
|
this->dma_ch->pull_data(std::min((int)this->xfer_count, DATA_FIFO_MAX),
|
|
&got_bytes, &src_ptr);
|
|
std::memcpy(this->data_fifo, src_ptr, got_bytes);
|
|
this->data_fifo_pos = got_bytes;
|
|
this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos);
|
|
|
|
this->xfer_count -= this->data_fifo_pos;
|
|
this->data_fifo_pos = 0;
|
|
if (!this->xfer_count) {
|
|
this->status |= STAT_TC; // signal zero transfer count
|
|
this->cur_state = SeqState::XFER_END;
|
|
this->sequencer();
|
|
}
|
|
}
|
|
|
|
if (this->xfer_count) {
|
|
this->dma_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
10000,
|
|
[this]() {
|
|
// re-enter the sequencer with the state specified in next_state
|
|
this->dma_timer_id = 0;
|
|
this->real_dma_xfer_out();
|
|
});
|
|
}
|
|
}
|
|
|
|
void Sc53C94::real_dma_xfer_in()
|
|
{
|
|
bool is_done = false;
|
|
|
|
// transfer data from target to host's memory
|
|
|
|
if (this->xfer_count && this->data_fifo_pos) {
|
|
this->dma_ch->push_data((char*)this->data_fifo, this->data_fifo_pos);
|
|
|
|
this->xfer_count -= this->data_fifo_pos;
|
|
this->data_fifo_pos = 0;
|
|
if (!this->xfer_count) {
|
|
is_done = true;
|
|
this->status |= STAT_TC; // signal zero transfer count
|
|
this->cur_state = SeqState::XFER_END;
|
|
this->sequencer();
|
|
}
|
|
}
|
|
|
|
// see if we need to refill FIFO
|
|
if (!this->data_fifo_pos && !is_done) {
|
|
this->sequencer();
|
|
this->dma_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
10000,
|
|
[this]() {
|
|
// re-enter the sequencer with the state specified in next_state
|
|
this->dma_timer_id = 0;
|
|
this->real_dma_xfer_in();
|
|
});
|
|
}
|
|
}
|
|
|
|
void Sc53C94::dma_wait() {
|
|
if (this->cur_bus_phase == ScsiPhase::DATA_IN && this->cur_state == SeqState::RCV_DATA) {
|
|
real_dma_xfer_in();
|
|
}
|
|
else if (this->cur_bus_phase == ScsiPhase::DATA_OUT && this->cur_state == SeqState::SEND_DATA) {
|
|
real_dma_xfer_out();
|
|
}
|
|
else {
|
|
this->dma_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
10000,
|
|
[this]() {
|
|
this->dma_timer_id = 0;
|
|
this->dma_wait();
|
|
});
|
|
}
|
|
}
|
|
|
|
void Sc53C94::dma_start()
|
|
{
|
|
dma_wait();
|
|
}
|
|
|
|
void Sc53C94::dma_stop()
|
|
{
|
|
if (this->dma_timer_id) {
|
|
TimerManager::get_instance()->cancel_timer(this->dma_timer_id);
|
|
this->dma_timer_id = 0;
|
|
}
|
|
}
|
|
|
|
static const PropMap Sc53C94_properties = {
|
|
{"hdd_img", new StrProperty("")},
|
|
{"cdr_img", new StrProperty("")},
|
|
};
|
|
|
|
static const DeviceDescription Sc53C94_Descriptor = {
|
|
Sc53C94::create, {}, Sc53C94_properties
|
|
};
|
|
|
|
REGISTER_DEVICE(Sc53C94, Sc53C94_Descriptor);
|