dingusppc/devices/common/scsi/scsi.h

273 lines
7.7 KiB
C
Raw Normal View History

2022-02-05 17:12:10 +00:00
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
2022-02-05 17:12:10 +00:00
(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 SCSI bus definitions. */
#ifndef SCSI_H
#define SCSI_H
#include <devices/common/hwcomponent.h>
#include <array>
#include <cinttypes>
2022-11-07 20:56:01 +00:00
#include <functional>
2023-05-30 17:55:40 +00:00
#include <memory>
2022-02-05 17:12:10 +00:00
#include <string>
/** SCSI control signals.
Bit positions follow the MESH controller convention for easier mapping.
*/
enum {
SCSI_CTRL_IO = 1 << 0,
SCSI_CTRL_CD = 1 << 1,
SCSI_CTRL_MSG = 1 << 2,
SCSI_CTRL_ATN = 1 << 3,
SCSI_CTRL_ACK = 1 << 4,
SCSI_CTRL_REQ = 1 << 5,
SCSI_CTRL_SEL = 1 << 13,
SCSI_CTRL_BSY = 1 << 14,
SCSI_CTRL_RST = 1 << 15,
2022-02-05 17:12:10 +00:00
};
namespace ScsiPhase {
2022-10-31 22:17:08 +00:00
enum : int {
BUS_FREE = 0,
ARBITRATION,
SELECTION,
RESELECTION,
COMMAND,
DATA_IN,
DATA_OUT,
STATUS,
MESSAGE_IN,
MESSAGE_OUT,
RESET,
};
};
2022-02-05 17:12:10 +00:00
namespace ScsiStatus {
enum : uint8_t {
GOOD = 0,
CHECK_CONDITION = 2,
};
};
namespace ScsiMessage {
enum : uint8_t {
COMMAND_COMPLETE = 0,
};
2022-02-05 17:12:10 +00:00
};
enum ScsiMsg : int {
CONFIRM_SEL = 1,
BUS_PHASE_CHANGE,
SEND_CMD_BEGIN,
SEND_CMD_END,
2022-11-07 11:24:02 +00:00
MESSAGE_BEGIN,
MESSAGE_END,
2022-02-05 17:12:10 +00:00
};
enum ScsiCommand : uint8_t {
TEST_UNIT_READY = 0x00,
REWIND = 0x01,
REQ_SENSE = 0x03,
2023-11-09 11:30:07 +00:00
FORMAT_UNIT = 0x04,
READ_BLK_LIMITS = 0x05,
READ_6 = 0x08,
WRITE_6 = 0x0A,
SEEK_6 = 0x0B,
2022-11-13 23:52:53 +00:00
INQUIRY = 0x12,
VERIFY_6 = 0x13,
MODE_SELECT_6 = 0x15,
RELEASE_UNIT = 0x17,
ERASE_6 = 0x19,
MODE_SENSE_6 = 0x1A,
START_STOP_UNIT = 0x1B,
2022-11-13 23:52:53 +00:00
DIAG_RESULTS = 0x1C,
SEND_DIAGS = 0x1D,
PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E,
READ_CAPACITY_10 = 0x25,
READ_10 = 0x28,
WRITE_10 = 0x2A,
VERIFY_10 = 0x2F,
READ_LONG_10 = 0x35,
WRITE_BUFFER = 0x3B,
READ_BUFFER = 0x3C,
2023-06-18 21:04:02 +00:00
MODE_SENSE_10 = 0x5A,
READ_12 = 0xA8,
2022-11-13 23:52:53 +00:00
// CD-ROM specific commands
READ_TOC = 0x43,
2023-06-18 21:04:02 +00:00
SET_CD_SPEED = 0xBB,
READ_CD = 0xBE,
2022-09-02 05:10:52 +00:00
};
enum ScsiSense : int {
NO_SENSE = 0x0,
RECOVERED = 0x1,
NOT_READY = 0x2,
MEDIUM_ERR = 0x3,
HW_ERROR = 0x4,
ILLEGAL_REQ = 0x5,
UNIT_ATTENTION = 0x6,
DATA_PROTECT = 0x7,
BLANK_CHECK = 0x8,
VOL_OVERFLOW = 0xD,
MISCOMPARE = 0xE,
COMPLETED = 0xF
};
2022-09-02 05:10:52 +00:00
enum ScsiError : int {
2023-06-18 21:04:02 +00:00
NO_ERROR = 0x00,
NO_SECTOR = 0x01,
WRITE_FAULT = 0x03,
DEV_NOT_READY = 0x04,
INVALID_CMD = 0x20,
INVALID_LBA = 0x21,
INVALID_CDB = 0x24,
INVALID_LUN = 0x25,
WRITE_PROTECT = 0x27,
MEDIUM_NOT_PRESENT = 0x3A,
2022-02-05 17:12:10 +00:00
};
/** Standard SCSI bus timing values measured in ns. */
#define BUS_SETTLE_DELAY 400
#define BUS_FREE_DELAY 800
#define BUS_CLEAR_DELAY 800
#define ARB_DELAY 2400
#define SEL_ABORT_TIME 200000
#define SEL_TIME_OUT 250000000
#define SCSI_MAX_DEVS 8
2022-10-25 00:10:54 +00:00
class ScsiBus;
2022-11-07 20:56:01 +00:00
typedef std::function<void()> action_callback;
2022-02-05 17:12:10 +00:00
class ScsiDevice : public HWComponent {
public:
2023-12-04 21:41:01 +00:00
ScsiDevice(std::string name, int my_id) {
this->set_name(name);
supports_types(HWCompType::SCSI_DEV);
2022-10-25 00:10:54 +00:00
this->scsi_id = my_id;
this->cur_phase = ScsiPhase::BUS_FREE;
2022-10-25 00:10:54 +00:00
};
2022-02-05 17:12:10 +00:00
~ScsiDevice() = default;
virtual void notify(ScsiMsg msg_type, int param);
virtual void next_step();
2022-11-07 11:24:02 +00:00
virtual void prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out);
2022-11-07 20:56:01 +00:00
virtual void switch_phase(const int new_phase);
2022-02-05 17:12:10 +00:00
2022-11-07 11:24:02 +00:00
virtual bool has_data() { return this->data_size != 0; };
2023-11-03 09:50:50 +00:00
virtual int xfer_data();
2022-11-07 11:24:02 +00:00
virtual int send_data(uint8_t* dst_ptr, int count);
virtual int rcv_data(const uint8_t* src_ptr, const int count);
2022-02-05 17:12:10 +00:00
2022-11-07 20:56:01 +00:00
virtual bool prepare_data() = 0;
virtual bool get_more_data() = 0;
virtual void process_command() = 0;
void set_bus_object_ptr(ScsiBus *bus_obj_ptr) {
this->bus_obj = bus_obj_ptr;
}
protected:
2022-11-07 11:24:02 +00:00
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;
2022-11-07 20:56:01 +00:00
int incoming_size;
2022-11-07 11:24:02 +00:00
uint8_t status;
2022-11-13 23:52:53 +00:00
int sense;
2022-11-07 20:56:01 +00:00
ScsiBus* bus_obj;
action_callback pre_xfer_action = nullptr;
action_callback post_xfer_action = nullptr;
2022-02-05 17:12:10 +00:00
};
/** This class provides a higher level abstraction for the SCSI bus. */
class ScsiBus : public HWComponent {
public:
2023-05-30 17:46:27 +00:00
ScsiBus(const std::string name);
2022-02-05 17:12:10 +00:00
~ScsiBus() = default;
2023-05-30 17:46:27 +00:00
static std::unique_ptr<HWComponent> create_first() {
return std::unique_ptr<ScsiBus>(new ScsiBus("SCSIO"));
}
static std::unique_ptr<HWComponent> create_second() {
return std::unique_ptr<ScsiBus>(new ScsiBus("SCSI1"));
}
// low-level state management
void register_device(int id, ScsiDevice* dev_obj);
int current_phase() { return this->cur_phase; };
2022-11-07 11:24:02 +00:00
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);
void release_ctrl_line(int id, uint16_t mask);
void release_ctrl_lines(int id);
uint16_t test_ctrl_lines(uint16_t mask);
// reading/writing data lines
uint8_t get_data_lines() { return this->data_lines; };
2022-02-05 17:12:10 +00:00
// high-level control/status
int switch_phase(int id, int new_phase);
2022-02-05 17:12:10 +00:00
bool begin_arbitration(int id);
bool end_arbitration(int id);
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);
2022-02-06 00:50:19 +00:00
void disconnect(int dev_id);
2022-11-07 11:24:02 +00:00
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);
2023-11-03 09:50:50 +00:00
int target_xfer_data();
void target_next_step();
2022-11-07 11:24:02 +00:00
bool negotiate_xfer(int& bytes_in, int& bytes_out);
2022-02-05 17:12:10 +00:00
protected:
void change_bus_phase(int initiator_id);
private:
// SCSI devices registered with this bus
std::array<ScsiDevice*, SCSI_MAX_DEVS> devices;
// per-device state of the control lines
2022-10-30 22:38:09 +00:00
uint16_t dev_ctrl_lines[SCSI_MAX_DEVS] = {};
2022-02-05 17:12:10 +00:00
uint16_t ctrl_lines;
int cur_phase;
int arb_winner_id;
int initiator_id;
int target_id;
uint8_t data_lines;
};
#endif // SCSI_H