scsidevice: refactor states.

This commit is contained in:
Maxim Poliakovski 2022-11-07 12:24:02 +01:00
parent 49331635b2
commit 40a02cc0f7
5 changed files with 142 additions and 76 deletions

View File

@ -79,6 +79,8 @@ enum ScsiMsg : int {
BUS_PHASE_CHANGE,
SEND_CMD_BEGIN,
SEND_CMD_END,
MESSAGE_BEGIN,
MESSAGE_END,
};
enum ScsiCommand : int {
@ -154,17 +156,24 @@ public:
virtual void notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param);
virtual void next_step(ScsiBus* bus_obj);
virtual void prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out);
virtual bool prepare_data() = 0;
virtual bool has_data() = 0;
virtual bool send_bytes(uint8_t* dst_ptr, int count) = 0;
virtual bool has_data() { return this->data_size != 0; };
virtual int send_data(uint8_t* dst_ptr, int count);
virtual int rcv_data(const uint8_t* src_ptr, const int count);
virtual void process_command() = 0;
protected:
uint8_t cmd_buf[16] = {};
int cur_phase;
int scsi_id;
uint8_t cmd_buf[16] = {};
uint8_t msg_buf[16] = {}; // TODO: clarify how big this one should be
int scsi_id;
int initiator_id;
int cur_phase;
uint8_t* data_ptr = nullptr;
int data_size;
uint8_t status;
};
/** This class provides a higher level abstraction for the SCSI bus. */
@ -176,6 +185,8 @@ public:
// low-level state management
void register_device(int id, ScsiDevice* dev_obj);
int current_phase() { return this->cur_phase; };
int get_initiator_id() { return this->initiator_id; };
int get_target_id() { return this->target_id; };
// reading/writing control lines
void assert_ctrl_line(int id, uint16_t mask);
@ -193,11 +204,11 @@ public:
bool begin_selection(int initiator_id, int target_id, bool atn);
void confirm_selection(int target_id);
bool end_selection(int initiator_id, int target_id);
bool transfer_command(uint8_t* dst_ptr);
void disconnect(int dev_id);
bool target_request_data();
bool target_pull_data(uint8_t* dst_ptr, int size);
bool pull_data(const int id, uint8_t* dst_ptr, const int size);
bool push_data(const int id, const uint8_t* src_ptr, const int size);
void target_next_step();
bool negotiate_xfer(int& bytes_in, int& bytes_out);
protected:
void change_bus_phase(int initiator_id);

View File

@ -134,6 +134,9 @@ int ScsiBus::switch_phase(int id, int new_phase)
case ScsiPhase::STATUS:
this->release_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_IO);
break;
case ScsiPhase::MESSAGE_OUT:
this->release_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG);
break;
}
// enter new phase (low-level)
@ -147,6 +150,9 @@ int ScsiBus::switch_phase(int id, int new_phase)
case ScsiPhase::STATUS:
this->assert_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_IO);
break;
case ScsiPhase::MESSAGE_OUT:
this->assert_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG);
break;
}
// switch the bus to the new phase (high-level)
@ -223,53 +229,24 @@ bool ScsiBus::end_selection(int initiator_id, int target_id)
return this->target_id == target_id;
}
bool ScsiBus::transfer_command(uint8_t* dst_ptr)
{
static uint8_t cmd_to_cdb_length[8] = {6, 10, 10, 6, 6, 12, 6, 6};
this->switch_phase(target_id, ScsiPhase::COMMAND);
this->devices[this->initiator_id]->notify(this, ScsiMsg::SEND_CMD_BEGIN, 0);
// attempt to transfer the shortest Command Description Block (CDB)
if (!this->devices[this->initiator_id]->send_bytes(dst_ptr, 6)) {
LOG_F(ERROR, "ScsiBus: error while transferring CDB!");
return false;
}
// transfer the remaining CDB bytes if any
int cdb_length = cmd_to_cdb_length[dst_ptr[0] >> 5];
if (cdb_length > 6) {
if (!this->devices[this->initiator_id]->send_bytes(dst_ptr + 6, cdb_length - 6)) {
LOG_F(ERROR, "ScsiBus: error while transferring CDB!");
return false;
}
}
this->devices[this->initiator_id]->notify(this, ScsiMsg::SEND_CMD_END, 0);
return true;
}
bool ScsiBus::target_request_data()
{
if (this->devices[this->target_id]->prepare_data()) {
this->assert_ctrl_line(target_id, SCSI_CTRL_REQ);
return true;
} else {
return false;
}
}
bool ScsiBus::target_pull_data(uint8_t* dst_ptr, int size)
bool ScsiBus::pull_data(const int id, uint8_t* dst_ptr, const int size)
{
if (dst_ptr == nullptr || !size) {
return false;
}
// transfer the requested number of bytes from target to initiator
if (!this->devices[this->target_id]->send_bytes(dst_ptr, size)) {
LOG_F(ERROR, "ScsiBus: error while transferring data from target!");
if (!this->devices[id]->send_data(dst_ptr, size)) {
LOG_F(ERROR, "ScsiBus: error while transferring T->I data!");
return false;
}
return true;
}
bool ScsiBus::push_data(const int id, const uint8_t* src_ptr, const int size)
{
if (!this->devices[id]->rcv_data(src_ptr, size)) {
LOG_F(ERROR, "ScsiBus: error while transferring I->T data!");
return false;
}
@ -281,6 +258,13 @@ void ScsiBus::target_next_step()
this->devices[this->target_id]->next_step(this);
}
bool ScsiBus::negotiate_xfer(int& bytes_in, int& bytes_out)
{
this->devices[this->target_id]->prepare_xfer(this, bytes_in, bytes_out);
return true;
}
void ScsiBus::disconnect(int dev_id)
{
this->release_ctrl_lines(dev_id);

View File

@ -66,8 +66,13 @@ void ScsiHardDisk::process_command() {
uint8_t page_code = 0;
uint8_t subpage_code = 0;
// assume successful command execution
this->status = ScsiStatus::GOOD;
uint8_t* cmd = this->cmd_buf;
LOG_F(INFO, "SCSI-HD: processing command 0x%X", cmd[0]);
switch (cmd[0]) {
case ScsiCommand::TEST_UNIT_READY:
test_unit_ready();
@ -130,6 +135,8 @@ bool ScsiHardDisk::prepare_data() {
switch (this->cur_phase) {
case ScsiPhase::DATA_IN:
this->data_ptr = (uint8_t*)this->img_buffer;
this->data_size = this->cur_buf_cnt;
break;
case ScsiPhase::STATUS:
if (!error) {
@ -151,18 +158,6 @@ bool ScsiHardDisk::prepare_data() {
return true;
}
bool ScsiHardDisk::send_bytes(uint8_t* dst_ptr, int count) {
if (dst_ptr == nullptr || !count || count > this->cur_buf_cnt) {
return false;
}
std::memcpy(dst_ptr, &this->img_buffer[this->cur_buf_pos], count);
this->cur_buf_pos += count;
this->cur_buf_cnt -= count;
return true;
}
int ScsiHardDisk::test_unit_ready() {
if (img_path.empty() || img_path == " ") {
return ScsiError::DEV_NOT_READY;
@ -246,7 +241,7 @@ void ScsiHardDisk::read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) {
this->hdd_img.read(img_buffer, transfer_size);
this->cur_buf_cnt = transfer_size;
this->msg_code = ScsiMessage::COMMAND_COMPLETE;
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
}
void ScsiHardDisk::write(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) {

View File

@ -42,7 +42,6 @@ public:
void insert_image(std::string filename);
void process_command();
bool prepare_data();
bool has_data() { return this->cur_buf_cnt != 0; };
bool send_bytes(uint8_t* dst_ptr, int count);
int test_unit_ready();
@ -65,12 +64,11 @@ private:
uint64_t img_size;
char img_buffer[1 << 17];
uint64_t file_offset = 0;
uint8_t status = ScsiError::NO_ERROR;
// SCSI transfer pointers
uint32_t cur_buf_pos = 0;
uint32_t cur_buf_cnt = 0;
uint8_t error = 0;
uint8_t error = ScsiError::NO_ERROR;
uint8_t msg_code = 0;
//inquiry info

View File

@ -44,13 +44,22 @@ void ScsiDevice::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
return;
bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_BSY);
bus_obj->confirm_selection(this->scsi_id);
this->initiator_id = bus_obj->get_initiator_id();
if (bus_obj->test_ctrl_lines(SCSI_CTRL_ATN)) {
LOG_F(WARNING, "ScsiDevice: MESSAGE_OUT isn't supported yet");
this->cur_phase = ScsiPhase::MESSAGE_OUT;
bus_obj->switch_phase(this->scsi_id, ScsiPhase::MESSAGE_OUT);
LOG_F(INFO, "ScsiDevice: received message 0x%X", this->msg_buf[0]);
}
bus_obj->transfer_command(this->cmd_buf);
this->cur_phase = ScsiPhase::COMMAND;
bus_obj->switch_phase(this->scsi_id, ScsiPhase::COMMAND);
this->process_command();
bus_obj->switch_phase(this->scsi_id, ScsiPhase::DATA_IN);
this->cur_phase = ScsiPhase::DATA_IN;
bus_obj->switch_phase(this->scsi_id, ScsiPhase::DATA_IN);
if (this->prepare_data()) {
bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_REQ);
} else {
ABORT_F("ScsiDevice: prepare_data() failed");
}
});
}
break;
@ -61,27 +70,96 @@ void ScsiDevice::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
void ScsiDevice::next_step(ScsiBus* bus_obj)
{
switch (this->cur_phase) {
case ScsiPhase::COMMAND:
this->process_command();
bus_obj->switch_phase(this->scsi_id, ScsiPhase::DATA_IN);
this->cur_phase = ScsiPhase::DATA_IN;
break;
case ScsiPhase::DATA_IN:
if (this->has_data()) {
LOG_F(WARNING, "ScsiDevice: attempt to leave DATA_IN phase with xfer != 0");
} else {
bus_obj->switch_phase(this->scsi_id, ScsiPhase::STATUS);
if (!this->has_data()) {
this->cur_phase = ScsiPhase::STATUS;
bus_obj->switch_phase(this->scsi_id, ScsiPhase::STATUS);
}
break;
case ScsiPhase::STATUS:
bus_obj->switch_phase(this->scsi_id, ScsiPhase::MESSAGE_IN);
this->cur_phase = ScsiPhase::MESSAGE_IN;
bus_obj->switch_phase(this->scsi_id, ScsiPhase::MESSAGE_IN);
break;
case ScsiPhase::MESSAGE_IN:
this->cur_phase = ScsiPhase::BUS_FREE;
break;
/* fall-through */
case ScsiPhase::BUS_FREE:
bus_obj->release_ctrl_lines(this->scsi_id);
bus_obj->switch_phase(this->scsi_id, ScsiPhase::BUS_FREE);
this->cur_phase = ScsiPhase::BUS_FREE;
break;
default:
LOG_F(WARNING, "ScsiDevice: nothing to do for the current phase %d", this->cur_phase);
LOG_F(WARNING, "ScsiDevice: nothing to do for phase %d", this->cur_phase);
}
}
void ScsiDevice::prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out)
{
this->cur_phase = bus_obj->current_phase();
switch (this->cur_phase) {
case ScsiPhase::COMMAND:
this->data_ptr = this->cmd_buf;
this->data_size = bytes_in;
break;
case ScsiPhase::STATUS:
this->data_ptr = &this->status;
this->data_size = 1;
bytes_out = 1;
break;
case ScsiPhase::DATA_IN:
bytes_out = this->data_size;
break;
case ScsiPhase::MESSAGE_OUT:
this->data_ptr = this->msg_buf;
this->data_size = bytes_in;
break;
case ScsiPhase::MESSAGE_IN:
this->data_ptr = this->msg_buf;
this->data_size = 1;
bytes_out = 1;
break;
default:
ABORT_F("ScsiDevice: unhandled phase %d in prepare_xfer()", this->cur_phase);
}
}
int ScsiDevice::send_data(uint8_t* dst_ptr, const int count)
{
if (dst_ptr == nullptr || !count) {
return 0;
}
int actual_count = std::min(this->data_size, count);
std::memcpy(dst_ptr, this->data_ptr, actual_count);
this->data_ptr += actual_count;
this->data_size -= actual_count;
return actual_count;
}
int ScsiDevice::rcv_data(const uint8_t* src_ptr, const int count)
{
uint8_t* dst_ptr;
switch (this->cur_phase) {
case ScsiPhase::COMMAND:
dst_ptr = this->cmd_buf;
break;
case ScsiPhase::MESSAGE_OUT:
dst_ptr = this->msg_buf;
break;
default:
ABORT_F("ScsiDevice: invalid phase %d in rcv_data()", this->cur_phase);
}
std::memcpy(dst_ptr, src_ptr, count);
return count;
}