/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 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 .
*/
/** @file SCSI bus definitions. */
#ifndef SCSI_H
#define SCSI_H
#include
#include
#include
#include
#include
#include
/** 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,
};
namespace ScsiPhase {
enum : int {
BUS_FREE = 0,
ARBITRATION,
SELECTION,
RESELECTION,
COMMAND,
DATA_IN,
DATA_OUT,
STATUS,
MESSAGE_IN,
MESSAGE_OUT,
RESET,
};
};
namespace ScsiStatus {
enum : uint8_t {
GOOD = 0,
CHECK_CONDITION = 2,
};
};
namespace ScsiMessage {
enum : uint8_t {
COMMAND_COMPLETE = 0,
};
};
enum ScsiMsg : int {
CONFIRM_SEL = 1,
BUS_PHASE_CHANGE,
SEND_CMD_BEGIN,
SEND_CMD_END,
MESSAGE_BEGIN,
MESSAGE_END,
};
enum ScsiCommand : uint8_t {
TEST_UNIT_READY = 0x00,
REWIND = 0x01,
REQ_SENSE = 0x03,
FORMAT_UNIT = 0x04,
READ_BLK_LIMITS = 0x05,
READ_6 = 0x08,
WRITE_6 = 0x0A,
SEEK_6 = 0x0B,
INQUIRY = 0x12,
VERIFY_6 = 0x13,
MODE_SELECT_6 = 0x15,
RELEASE_UNIT = 0x17,
ERASE_6 = 0x19,
MODE_SENSE_6 = 0x1A,
START_STOP_UNIT = 0x1B,
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,
MODE_SENSE_10 = 0x5A,
READ_12 = 0xA8,
// CD-ROM specific commands
READ_TOC = 0x43,
SET_CD_SPEED = 0xBB,
READ_CD = 0xBE,
};
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
};
enum ScsiError : int {
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,
};
/** 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
class ScsiBus;
typedef std::function action_callback;
class ScsiDevice : public HWComponent {
public:
ScsiDevice(std::string name, int my_id) {
this->set_name(name);
supports_types(HWCompType::SCSI_DEV);
this->scsi_id = my_id;
this->cur_phase = ScsiPhase::BUS_FREE;
};
~ScsiDevice() = default;
virtual void notify(ScsiMsg msg_type, int param);
virtual void next_step();
virtual void prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out);
virtual void switch_phase(const int new_phase);
virtual bool has_data() { return this->data_size != 0; };
virtual int xfer_data();
virtual int send_data(uint8_t* dst_ptr, int count);
virtual int rcv_data(const uint8_t* src_ptr, const int count);
virtual bool prepare_data() = 0;
virtual void process_command() = 0;
void set_bus_object_ptr(ScsiBus *bus_obj_ptr) {
this->bus_obj = bus_obj_ptr;
}
protected:
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;
int incoming_size;
uint8_t status;
int sense;
ScsiBus* bus_obj;
action_callback pre_xfer_action = nullptr;
action_callback post_xfer_action = nullptr;
};
/** This class provides a higher level abstraction for the SCSI bus. */
class ScsiBus : public HWComponent {
public:
ScsiBus(const std::string name);
~ScsiBus() = default;
static std::unique_ptr create_first() {
return std::unique_ptr(new ScsiBus("SCSIO"));
}
static std::unique_ptr create_second() {
return std::unique_ptr(new ScsiBus("SCSI1"));
}
// 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);
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; };
// high-level control/status
int switch_phase(int id, int new_phase);
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);
void disconnect(int dev_id);
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);
int target_xfer_data();
void target_next_step();
bool negotiate_xfer(int& bytes_in, int& bytes_out);
protected:
void change_bus_phase(int initiator_id);
private:
// SCSI devices registered with this bus
std::array devices;
// per-device state of the control lines
uint16_t dev_ctrl_lines[SCSI_MAX_DEVS] = {};
uint16_t ctrl_lines;
int cur_phase;
int arb_winner_id;
int initiator_id;
int target_id;
uint8_t data_lines;
};
#endif // SCSI_H