dingusppc/devices/common/scsi/sc53c94.cpp
joevt 8a1055ed1b sc53c94: Add DBDMA support.
- 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.
2024-04-03 19:19:42 -07:00

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);