mirror of
https://github.com/dingusdev/dingusppc.git
synced 2026-10-25 01:02:27 +00:00
447 lines
14 KiB
C++
447 lines
14 KiB
C++
/*
|
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
|
Copyright (C) 2018-26 The DingusPPC Development Team
|
|
(See CREDITS.MD for more details)
|
|
|
|
(You may also contact divingkxt or powermax2286 on Discord)
|
|
|
|
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/>.
|
|
*/
|
|
|
|
#include <core/memaccess.h>
|
|
#include <devices/common/scsi/scsicommoncmds.h>
|
|
#include <loguru.hpp>
|
|
|
|
ScsiCommonCmds::ScsiCommonCmds() {
|
|
this->enable_cmd(ScsiCommand::TEST_UNIT_READY);
|
|
this->enable_cmd(ScsiCommand::REQ_SENSE);
|
|
this->enable_cmd(ScsiCommand::INQUIRY);
|
|
this->enable_cmd(ScsiCommand::MODE_SENSE_6);
|
|
this->enable_cmd(ScsiCommand::MODE_SENSE_10);
|
|
}
|
|
|
|
void ScsiCommonCmds::process_command() {
|
|
int next_phase;
|
|
|
|
switch(this->cdb_ptr[0]) {
|
|
case ScsiCommand::TEST_UNIT_READY:
|
|
next_phase = this->test_unit_ready();
|
|
break;
|
|
case ScsiCommand::REQ_SENSE:
|
|
next_phase = this->request_sense();
|
|
break;
|
|
case ScsiCommand::INQUIRY:
|
|
next_phase = this->inquiry();
|
|
break;
|
|
case ScsiCommand::MODE_SENSE_6:
|
|
case ScsiCommand::MODE_SENSE_10:
|
|
next_phase = this->mode_sense();
|
|
break;
|
|
default:
|
|
this->invalid_command();
|
|
next_phase = ScsiPhase::STATUS;
|
|
}
|
|
|
|
phy_impl->switch_phase(next_phase);
|
|
}
|
|
|
|
// maps command group number to CDB length (SCSI-2 standard §7.2.1)
|
|
static const int CmdGroupLen[8] = {6, 10, 10, CMD_GRP_RESERVED, CMD_GRP_RESERVED, 12,
|
|
CMD_GRP_VENDOR_SPECIFIC, CMD_GRP_VENDOR_SPECIFIC};
|
|
|
|
int ScsiCommonCmds::get_cdb_length(uint8_t op_code) {
|
|
return CmdGroupLen[op_code >> 5];
|
|
}
|
|
|
|
int ScsiCommonCmds::verify_cdb() {
|
|
if (phy_impl->get_phy_id() == PHY_ID_ATAPI)
|
|
return 0; // don't try to verify ATAPI packets
|
|
|
|
int cdb_len = this->get_cdb_length(this->cdb_ptr[0]);
|
|
|
|
if (cdb_len < 0)
|
|
return 0; // don't know how to verify non-standard CDBs
|
|
|
|
// check the LUN field except for INQUIRY and REQ_SENSE
|
|
if ((this->cdb_ptr[0] != ScsiCommand::INQUIRY) &&
|
|
(this->cdb_ptr[0] != ScsiCommand::REQ_SENSE))
|
|
{
|
|
if (phy_impl->last_sel_has_attention() &&
|
|
(phy_impl->last_sel_msg() & ScsiMessage::IDENTIFY))
|
|
{
|
|
uint8_t lun = phy_impl->last_sel_msg() & 7;
|
|
|
|
if (!this->lun_supported(lun)) {
|
|
LOG_F(ERROR, "unsupported LUN 0x%X", lun);
|
|
this->invalid_lun();
|
|
phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key);
|
|
return -1;
|
|
}
|
|
} else if (this->cdb_ptr[1] >> 5)
|
|
LOG_F(WARNING, "non-zero LUN 0x%X in CDB", this->cdb_ptr[1] >> 5);
|
|
}
|
|
|
|
// check the control field
|
|
uint8_t ctrl_byte = this->cdb_ptr[cdb_len - 1];
|
|
|
|
if (ctrl_byte & 3) {
|
|
if (this->linked_cmds_supported())
|
|
this->link_ctrl = ctrl_byte;
|
|
else {
|
|
LOG_F(ERROR, "flag/link bit set in CDB control: 0x%X", ctrl_byte);
|
|
this->invalid_cdb();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ScsiCommonCmds::test_unit_ready() {
|
|
// Assume that LUN is okay and the status has been set to GOOD
|
|
|
|
if (!phy_impl->is_device_ready()) {
|
|
this->sense_key = ScsiSense::NOT_READY;
|
|
this->asc = phy_impl->not_ready_reason();
|
|
this->ascq = 0;
|
|
phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key);
|
|
}
|
|
|
|
return ScsiPhase::STATUS;
|
|
}
|
|
|
|
int ScsiCommonCmds::inquiry() {
|
|
int alloc_len = this->get_xfer_len();
|
|
int resp_len = 36; // standard response length
|
|
|
|
// go directly to the STATUS phase if alloc_len = 0
|
|
if (!alloc_len) {
|
|
LOG_F(WARNING, "INQUIRY: skip data transfer because alloc_len = 0");
|
|
return ScsiPhase::STATUS;
|
|
}
|
|
|
|
if (phy_impl->get_phy_id() == PHY_ID_SCSI) {
|
|
bool is_invalid = false;
|
|
|
|
if (this->cdb_ptr[1] & 1) {
|
|
LOG_F(ERROR, "INQUIRY: EVPD=1 not supported");
|
|
this->set_field_pointer(1);
|
|
this->set_bit_pointer(0);
|
|
is_invalid = true;
|
|
}
|
|
|
|
if (this->cdb_ptr[2]) {
|
|
LOG_F(ERROR, "INQUIRY: non-zero page code not supported");
|
|
this->set_field_pointer(2);
|
|
is_invalid = true;
|
|
}
|
|
|
|
if (is_invalid) {
|
|
this->invalid_cdb();
|
|
return ScsiPhase::STATUS;
|
|
}
|
|
}
|
|
|
|
std::memset(this->buf_ptr, 0, resp_len);
|
|
|
|
uint8_t lun = this->get_lun();
|
|
|
|
if (lun) {
|
|
// tell the host there is no device for this LUN
|
|
this->buf_ptr[0] = (3 << 5) | ScsiDevType::UNKNOWN;
|
|
LOG_F(WARNING, "INQUIRY: no device for LUN %d", lun);
|
|
} else {
|
|
this->buf_ptr[0] = this->dev_type;
|
|
this->buf_ptr[1] = ((this->is_removable) ? 0x80 : 0) | this->dev_type_mod;
|
|
this->buf_ptr[2] = this->std_versions;
|
|
this->buf_ptr[3] = this->resp_fmt;
|
|
this->buf_ptr[4] = 31; // additional length (n-5)
|
|
this->buf_ptr[7] = this->cap_flags;
|
|
std::memcpy(&this->buf_ptr[ 8], vendor_id, sizeof(this->vendor_id));
|
|
std::memcpy(&this->buf_ptr[16], product_id, sizeof(this->product_id));
|
|
std::memcpy(&this->buf_ptr[32], revision_id, sizeof(this->revision_id));
|
|
}
|
|
|
|
phy_impl->set_xfer_len(std::min(alloc_len, resp_len));
|
|
|
|
return ScsiPhase::DATA_IN;
|
|
}
|
|
|
|
int ScsiCommonCmds::request_sense() {
|
|
int alloc_len = this->get_xfer_len();
|
|
int resp_len = 18; // standard response length
|
|
|
|
// go directly to the STATUS phase if alloc_len = 0
|
|
if (!alloc_len) {
|
|
LOG_F(WARNING, "REQ_SENSE: skip data transfer because alloc_len = 0");
|
|
return ScsiPhase::STATUS;
|
|
}
|
|
|
|
std::memset(this->buf_ptr, 0, resp_len);
|
|
|
|
this->buf_ptr[0] = 0x80 | 0x70; // valid bit + current errors
|
|
this->buf_ptr[7] = 10; // additional sense length
|
|
|
|
uint8_t lun = this->get_lun();
|
|
|
|
if (lun) // unsupported LUN
|
|
this->invalid_lun();
|
|
|
|
this->buf_ptr[ 2] = this->sense_key;
|
|
this->buf_ptr[12] = this->asc;
|
|
this->buf_ptr[13] = this->ascq;
|
|
|
|
if (this->sense_key == ScsiSense::ILLEGAL_REQ && this->field_ptr_valid) {
|
|
this->buf_ptr[15] = this->is_cdb_err ? 0xC0 : 0x80;
|
|
|
|
WRITE_WORD_BE_A(&this->buf_ptr[16], this->field_ptr);
|
|
|
|
if (this->bit_ptr_valid)
|
|
this->buf_ptr[15] |= (this->bit_ptr & 7) | 8;
|
|
}
|
|
|
|
this->reset_sense(); // clear last sense information
|
|
|
|
phy_impl->set_xfer_len(std::min(alloc_len, resp_len));
|
|
|
|
return ScsiPhase::DATA_IN;
|
|
}
|
|
|
|
const char* Page_Control_Modes[4] = {
|
|
"CURRENT_VALUES",
|
|
"CHANGEABLE_VALUES",
|
|
"DEFAULT_VALUES",
|
|
"SAVED_VALUES"
|
|
};
|
|
|
|
int ScsiCommonCmds::mode_sense() {
|
|
uint8_t page_ctrl = this->cdb_ptr[2] >> 6;
|
|
|
|
if (page_ctrl == PageControl::SAVED_VALUES && !this->supports_params_saving()) {
|
|
LOG_F(ERROR, "MODE_SENSE: Page control mode (%s) not supported",
|
|
Page_Control_Modes[page_ctrl]);
|
|
this->set_field_pointer(2); // error in the 3rd byte
|
|
this->set_bit_pointer(7); // starting with bit 7
|
|
this->illegal_request(ScsiError::SAVING_NOT_SUPPORTED, 0);
|
|
return ScsiPhase::STATUS;
|
|
}
|
|
|
|
uint8_t page_code = this->cdb_ptr[2] & 0x3F;
|
|
uint8_t subpage_code = this->cdb_ptr[3];
|
|
|
|
// MODE_SENSE_10 returns a bigger response
|
|
bool is_long_resp = this->cdb_ptr[0] == ScsiCommand::MODE_SENSE_10;
|
|
|
|
int alloc_len = this->get_xfer_len();
|
|
int resp_len = 0;
|
|
int resp_max_len = is_long_resp ? 65536 : 256;
|
|
|
|
uint8_t* out_ptr = this->buf_ptr;
|
|
|
|
uint8_t medium_type, dev_flags;
|
|
|
|
this->get_medium_type(medium_type, dev_flags);
|
|
|
|
// format response header
|
|
if (is_long_resp) { // MODE_SENSE_10
|
|
WRITE_WORD_BE_A(out_ptr, 0); // zero data_length -> to be adjusted later
|
|
out_ptr[2] = medium_type;
|
|
out_ptr[3] = dev_flags;
|
|
resp_len = 8; // initial response length
|
|
out_ptr += 8; // move data pointer past block descriptor length
|
|
WRITE_WORD_BE_A(&out_ptr[-2], 0); // zero block descriptor length
|
|
} else {
|
|
out_ptr[0] = 0; // zero data_length -> to be adjusted later
|
|
out_ptr[1] = medium_type;
|
|
out_ptr[2] = dev_flags;
|
|
resp_len = 4; // initial response length
|
|
out_ptr += 4; // move data pointer past block descriptor length
|
|
out_ptr[-1] = 0; // zero block descriptor length
|
|
}
|
|
|
|
// return block descriptor(s) unless
|
|
// it's ATAPI that doesn't use block descriptors
|
|
// block descriptors are disabled by the DBD bit
|
|
if (phy_impl->get_phy_id() == PHY_ID_SCSI) {
|
|
if (!(this->cdb_ptr[1] & 8)) { // if DBD=0
|
|
int desc_len = this->format_block_descriptors(out_ptr);
|
|
if (is_long_resp)
|
|
WRITE_WORD_BE_A(&out_ptr[-2], desc_len);
|
|
else
|
|
out_ptr[-1] = desc_len;
|
|
out_ptr += desc_len;
|
|
resp_len += desc_len;
|
|
} else
|
|
LOG_F(WARNING, "MODE_SENSE: block descriptor(s) disabled");
|
|
}
|
|
|
|
// return all pages if requested
|
|
if (page_code == ModePage::ALL_PAGES) {
|
|
for (const auto& [page_num, formatter] : this->getters) {
|
|
if (!page_num)
|
|
continue; // skip vendor specific page 0 - shall be returned last
|
|
|
|
int page_len = this->get_one_page(page_ctrl, page_num, 0,
|
|
out_ptr, resp_max_len - resp_len);
|
|
if (page_len < 0)
|
|
return ScsiPhase::STATUS;
|
|
|
|
out_ptr += page_len;
|
|
resp_len += page_len;
|
|
}
|
|
|
|
if (this->getters.count(0))
|
|
page_code = 0; // page 0 exists -> let the following code return it last
|
|
}
|
|
|
|
// return one page
|
|
if (page_code != ModePage::ALL_PAGES) {
|
|
if (this->getters.count(page_code)) {
|
|
int page_len = this->get_one_page(page_ctrl, page_code, subpage_code,
|
|
out_ptr, resp_max_len - resp_len);
|
|
if (page_len < 0)
|
|
return ScsiPhase::STATUS;
|
|
|
|
resp_len += page_len;
|
|
} else {
|
|
LOG_F(ERROR, "MODE_SENSE: page 0x%X not supported", page_code);
|
|
this->set_field_pointer(2); // error in the 3rd byte
|
|
this->set_bit_pointer(5); // starting with bit 5
|
|
this->invalid_cdb();
|
|
return ScsiPhase::STATUS;
|
|
}
|
|
}
|
|
|
|
// stuff final response into response header
|
|
if (is_long_resp)
|
|
WRITE_WORD_BE_A(&this->buf_ptr[0], resp_len - 2);
|
|
else
|
|
this->buf_ptr[0] = resp_len - 1;
|
|
|
|
phy_impl->set_xfer_len(std::min(alloc_len, resp_len));
|
|
|
|
return ScsiPhase::DATA_IN;
|
|
}
|
|
|
|
int ScsiCommonCmds::get_one_page(uint8_t ctrl, uint8_t page, uint8_t subpage,
|
|
uint8_t* out_ptr, int avail_len)
|
|
{
|
|
int len, err;
|
|
|
|
try_again:
|
|
len = this->getters[page](ctrl, subpage, &out_ptr[2], avail_len - 2);
|
|
if (len < 0) {
|
|
err = len;
|
|
if (err == FORMAT_ERR_DATA_TOO_BIG) {
|
|
ABORT_F("Internal buffer too small!");
|
|
// increase buffer size
|
|
// goto try_again;
|
|
} else if (err == FORMAT_ERR_BAD_CONTROL) {
|
|
LOG_F(ERROR, "MODE_SENSE: Page control (%s) not supported for page 0x%X",
|
|
Page_Control_Modes[ctrl], page);
|
|
this->set_field_pointer(2); // error in the 3rd byte
|
|
this->set_bit_pointer(7); // starting with bit 7
|
|
} else if (err == FORMAT_ERR_BAD_SUBPAGE) {
|
|
LOG_F(ERROR, "MODE_SENSE: unsupported subpage 0x%X for page 0x%X",
|
|
subpage, page);
|
|
this->set_field_pointer(3); // error in the 4th byte
|
|
} else
|
|
ABORT_F("MODE_SENSE: unknown getter error %d", err);
|
|
|
|
this->invalid_cdb();
|
|
return err;
|
|
}
|
|
|
|
out_ptr[0] = page;
|
|
out_ptr[1] = len;
|
|
|
|
return len + 2;
|
|
}
|
|
|
|
uint8_t ScsiCommonCmds::get_lun() {
|
|
if (phy_impl->get_phy_id() == PHY_ID_ATAPI)
|
|
return 0; // ATAPI doesn't support SCSI LUNs so bail out early
|
|
|
|
if (phy_impl->last_sel_has_attention() &&
|
|
(phy_impl->last_sel_msg() & ScsiMessage::IDENTIFY))
|
|
return phy_impl->last_sel_msg() & 7;
|
|
else
|
|
return this->cdb_ptr[1] >> 5;
|
|
}
|
|
|
|
uint32_t ScsiCommonCmds::get_xfer_len() {
|
|
int cmd_len = this->get_cdb_length(this->cdb_ptr[0]);
|
|
|
|
switch(cmd_len) {
|
|
case 6:
|
|
return this->cdb_ptr[4];
|
|
case 10:
|
|
return READ_WORD_BE_U(&this->cdb_ptr[7]);
|
|
case 12:
|
|
return READ_DWORD_BE_U(&this->cdb_ptr[6]);
|
|
default:
|
|
ABORT_F("unsupported command length %d in get_xfer_len", cmd_len);
|
|
}
|
|
}
|
|
|
|
void ScsiCommonCmds::reset_sense() {
|
|
this->sense_key = ScsiSense::NO_SENSE;
|
|
this->asc = ScsiError::NO_ERROR;
|
|
this->ascq = 0;
|
|
this->field_ptr_valid = false;
|
|
this->bit_ptr_valid = false;
|
|
}
|
|
|
|
void ScsiCommonCmds::invalid_lun() {
|
|
this->sense_key = ScsiSense::ILLEGAL_REQ;
|
|
this->asc = ScsiError::INVALID_LUN;
|
|
this->ascq = 0;
|
|
this->is_cdb_err = true;
|
|
this->field_ptr_valid = true;
|
|
this->field_ptr = 1; // error in the 2nd byte
|
|
this->bit_ptr_valid = true;
|
|
this->bit_ptr = 7; // MSB number of the invalid bitfield
|
|
}
|
|
|
|
void ScsiCommonCmds::invalid_cdb() {
|
|
this->sense_key = ScsiSense::ILLEGAL_REQ;
|
|
this->asc = ScsiError::INVALID_CDB;
|
|
this->ascq = 0;
|
|
this->is_cdb_err = true;
|
|
|
|
phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key);
|
|
}
|
|
|
|
void ScsiCommonCmds::invalid_command() {
|
|
LOG_F(ERROR, "unsupported command: 0x%02x", this->cdb_ptr[0]);
|
|
|
|
this->sense_key = ScsiSense::ILLEGAL_REQ;
|
|
this->asc = ScsiError::INVALID_CMD;
|
|
this->ascq = 0;
|
|
this->is_cdb_err = true;
|
|
this->field_ptr_valid = true;
|
|
this->field_ptr = 0; // error in the 1st byte
|
|
|
|
phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key);
|
|
}
|
|
|
|
void ScsiCommonCmds::illegal_request(uint8_t asc, uint8_t ascq, bool is_cdb) {
|
|
this->sense_key = ScsiSense::ILLEGAL_REQ;
|
|
this->asc = asc;
|
|
this->ascq = ascq;
|
|
this->is_cdb_err = is_cdb;
|
|
|
|
phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key);
|
|
}
|