mirror of
https://github.com/dingusdev/dingusppc.git
synced 2025-01-11 05:29:43 +00:00
Merge 'hard-disks' branch.
This commit is contained in:
commit
cf0d361918
@ -1,28 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "clang_cl_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "-v",
|
||||
"ctestCommandArgs": "",
|
||||
"variables": []
|
||||
},
|
||||
{
|
||||
"name": "x64-Release",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Release",
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "-v",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "clang_cl_x64" ],
|
||||
"variables": []
|
||||
}
|
||||
]
|
||||
}
|
@ -136,64 +136,40 @@ void dppc_interpreter::power_dozi() {
|
||||
|
||||
void dppc_interpreter::power_lscbx() {
|
||||
ppc_grab_regsdab();
|
||||
ppc_effective_address = (reg_a == 0) ? ppc_result_b : ppc_result_a + ppc_result_b;
|
||||
ppc_effective_address = (reg_a == 0) ? ppc_result_b : ppc_result_a + ppc_result_b;
|
||||
//ppc_result_d = 0xFFFFFFFF;
|
||||
|
||||
uint8_t return_value = 0;
|
||||
uint32_t bytes_to_load = (ppc_state.spr[SPR::XER] & 0x7f);
|
||||
uint32_t bytes_copied = 0;
|
||||
uint8_t matching_byte = (uint8_t)((ppc_state.spr[SPR::XER] & 0xFF00) >> 8);
|
||||
uint32_t byte_offset = 0;
|
||||
|
||||
//for storing each byte
|
||||
uint32_t bitmask = 0;
|
||||
uint32_t shift_amount = 0;
|
||||
// for storing each byte
|
||||
uint32_t bitmask = 0xFF000000;
|
||||
uint8_t shift_amount = 24;
|
||||
|
||||
while (bytes_to_load > 0) {
|
||||
if (byte_offset == 24) {
|
||||
reg_d = (reg_d + 1) % 32;
|
||||
ppc_result_d = 0xFFFFFFFF;
|
||||
ppc_store_result_regd();
|
||||
}
|
||||
|
||||
|
||||
switch (byte_offset) {
|
||||
case 0:
|
||||
bitmask = 0x00FFFFFF;
|
||||
shift_amount = 24;
|
||||
break;
|
||||
case 8:
|
||||
bitmask = 0xFF00FFFF;
|
||||
shift_amount = 16;
|
||||
break;
|
||||
case 16:
|
||||
bitmask = 0xFFFF00FF;
|
||||
shift_amount = 8;
|
||||
break;
|
||||
case 24:
|
||||
bitmask = 0xFFFFFF00;
|
||||
shift_amount = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return_value = mmu_read_vmem<uint8_t>(ppc_effective_address);
|
||||
// return_value = mem_grab_byte(ppc_effective_address);
|
||||
ppc_result_d = (ppc_result_d & bitmask) | (return_value << shift_amount);
|
||||
ppc_result_d = (ppc_result_d & ~(bitmask)) | (return_value << shift_amount);
|
||||
ppc_store_result_regd();
|
||||
bytes_copied++;
|
||||
|
||||
if (return_value == matching_byte) {
|
||||
//Match has been found - Time to break out
|
||||
break;
|
||||
}
|
||||
|
||||
byte_offset += 8;
|
||||
|
||||
if (byte_offset == 32) {
|
||||
byte_offset = 0;
|
||||
if (bitmask == 0x000000FF) {
|
||||
reg_d = (reg_d + 1) & 31;
|
||||
//ppc_result_d = 0xFFFFFFFF;
|
||||
bitmask = 0xFF000000;
|
||||
shift_amount = 24;
|
||||
}
|
||||
else {
|
||||
bitmask >>= 8;
|
||||
shift_amount -= 8;
|
||||
}
|
||||
|
||||
ppc_effective_address++;
|
||||
bytes_copied++;
|
||||
bytes_to_load--;
|
||||
|
||||
if (return_value == matching_byte)
|
||||
break;
|
||||
}
|
||||
|
||||
ppc_state.spr[SPR::XER] = (ppc_state.spr[SPR::XER] & 0xFFFFFF80) | bytes_copied;
|
||||
|
@ -2004,7 +2004,6 @@ void dppc_interpreter::ppc_lswi() {
|
||||
ppc_effective_address = reg_a ? ppc_result_a : 0;
|
||||
grab_inb = (ppc_cur_instruction >> 11) & 0x1F;
|
||||
grab_inb = grab_inb ? grab_inb : 32;
|
||||
uint32_t stringed_word = 0;
|
||||
|
||||
while (grab_inb > 0) {
|
||||
switch (grab_inb) {
|
||||
@ -2017,9 +2016,8 @@ void dppc_interpreter::ppc_lswi() {
|
||||
grab_inb = 0;
|
||||
break;
|
||||
case 3:
|
||||
stringed_word = mmu_read_vmem<uint16_t>(ppc_effective_address) << 16;
|
||||
stringed_word += mmu_read_vmem<uint8_t>(ppc_effective_address + 2) << 8;
|
||||
ppc_state.gpr[reg_d] = stringed_word;
|
||||
ppc_state.gpr[reg_d] = mmu_read_vmem<uint16_t>(ppc_effective_address) << 16;
|
||||
ppc_state.gpr[reg_d] += mmu_read_vmem<uint8_t>(ppc_effective_address + 2) << 8;
|
||||
grab_inb = 0;
|
||||
break;
|
||||
default:
|
||||
@ -2047,7 +2045,6 @@ void dppc_interpreter::ppc_lswx() {
|
||||
|
||||
ppc_effective_address = reg_a ? (ppc_result_a + ppc_result_b) : ppc_result_b;
|
||||
grab_inb = ppc_state.spr[SPR::XER] & 0x7F;
|
||||
uint32_t stringed_word = 0;
|
||||
|
||||
while (grab_inb > 0) {
|
||||
switch (grab_inb) {
|
||||
@ -2060,10 +2057,9 @@ void dppc_interpreter::ppc_lswx() {
|
||||
grab_inb = 0;
|
||||
break;
|
||||
case 3:
|
||||
stringed_word = mmu_read_vmem<uint16_t>(ppc_effective_address) << 16;
|
||||
stringed_word += mmu_read_vmem<uint8_t>(ppc_effective_address + 2) << 8;
|
||||
ppc_state.gpr[reg_d] = stringed_word;
|
||||
grab_inb = 0;
|
||||
ppc_state.gpr[reg_d] = mmu_read_vmem<uint16_t>(ppc_effective_address) << 16;
|
||||
ppc_state.gpr[reg_d] += mmu_read_vmem<uint8_t>(ppc_effective_address + 2) << 8;
|
||||
grab_inb = 0;
|
||||
break;
|
||||
default:
|
||||
ppc_state.gpr[reg_d] = mmu_read_vmem<uint32_t>(ppc_effective_address);
|
||||
|
@ -6,6 +6,7 @@ file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common/adb/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common/i2c/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common/ata/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common/pci/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/common/scsi/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/ethernet/*.cpp"
|
||||
|
@ -402,7 +402,7 @@ bool ADB_Bus::adb_keybd_listen(int reg) {
|
||||
}
|
||||
|
||||
bool ADB_Bus::adb_mouse_listen(int reg) {
|
||||
if ((reg != 0) | (reg != 3)) {
|
||||
if ((reg != 0) || (reg != 3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
129
devices/common/ata/atabasedevice.cpp
Normal file
129
devices/common/ata/atabasedevice.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 Heathrow hard drive controller */
|
||||
|
||||
#include <devices/common/ata/atabasedevice.h>
|
||||
#include <devices/common/ata/atadefs.h>
|
||||
#include <devices/deviceregistry.h>
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
using namespace ata_interface;
|
||||
|
||||
AtaBaseDevice::AtaBaseDevice(const std::string name)
|
||||
{
|
||||
this->set_name(name);
|
||||
supports_types(HWCompType::IDE_DEV);
|
||||
|
||||
device_reset();
|
||||
}
|
||||
|
||||
void AtaBaseDevice::device_reset(const uint8_t dev_head_val)
|
||||
{
|
||||
this->r_error = 1; // Device 0 passed, Device 1 passed or not present
|
||||
|
||||
this->r_sect_count = 1;
|
||||
this->r_sect_num = 1;
|
||||
this->r_dev_ctrl = 0;
|
||||
|
||||
// set protocol signature
|
||||
if (this->device_type == DEVICE_TYPE_ATAPI) {
|
||||
this->r_cylinder_lo = 0x14;
|
||||
this->r_cylinder_hi = 0xEB;
|
||||
this->r_dev_head = dev_head_val;
|
||||
} else { // assume ATA by default
|
||||
this->r_cylinder_lo = 0;
|
||||
this->r_cylinder_hi = 0;
|
||||
this->r_dev_head = 0;
|
||||
this->r_status = DRDY | DSC;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t AtaBaseDevice::read(const uint8_t reg_addr) {
|
||||
switch (reg_addr) {
|
||||
case ATA_Reg::DATA:
|
||||
LOG_F(WARNING, "Retrieving data from %s", this->name.c_str());
|
||||
return 0xFFFFU;
|
||||
case ATA_Reg::ERROR:
|
||||
return this->r_error;
|
||||
case ATA_Reg::SEC_COUNT:
|
||||
return this->r_sect_count;
|
||||
case ATA_Reg::SEC_NUM:
|
||||
return this->r_sect_num;
|
||||
case ATA_Reg::CYL_LOW:
|
||||
return this->r_cylinder_lo;
|
||||
case ATA_Reg::CYL_HIGH:
|
||||
return this->r_cylinder_hi;
|
||||
case ATA_Reg::DEVICE_HEAD:
|
||||
return this->r_dev_head;
|
||||
case ATA_Reg::STATUS:
|
||||
// TODO: clear pending interrupt
|
||||
return this->r_status;
|
||||
case ATA_Reg::ALT_STATUS:
|
||||
return this->r_status;
|
||||
default:
|
||||
LOG_F(WARNING, "Attempted to read unknown IDE register: %x", reg_addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AtaBaseDevice::write(const uint8_t reg_addr, const uint16_t value) {
|
||||
switch (reg_addr) {
|
||||
case ATA_Reg::DATA:
|
||||
LOG_F(WARNING, "Pushing data to %s", this->name.c_str());
|
||||
break;
|
||||
case ATA_Reg::FEATURES:
|
||||
this->r_features = value;
|
||||
break;
|
||||
case ATA_Reg::SEC_COUNT:
|
||||
this->r_sect_count = value;
|
||||
break;
|
||||
case ATA_Reg::SEC_NUM:
|
||||
this->r_sect_num = value;
|
||||
break;
|
||||
case ATA_Reg::CYL_LOW:
|
||||
this->r_cylinder_lo = value;
|
||||
break;
|
||||
case ATA_Reg::CYL_HIGH:
|
||||
this->r_cylinder_hi = value;
|
||||
break;
|
||||
case ATA_Reg::DEVICE_HEAD:
|
||||
this->r_dev_head = value;
|
||||
break;
|
||||
case ATA_Reg::COMMAND:
|
||||
this->r_command = value;
|
||||
if (is_selected() || this->r_command == DIAGNOSTICS) {
|
||||
perform_command();
|
||||
}
|
||||
break;
|
||||
case ATA_Reg::DEV_CTRL:
|
||||
if (!(this->r_dev_ctrl & SRST) && (value & SRST)) {
|
||||
LOG_F(INFO, "%s: soft reset triggered", this->name.c_str());
|
||||
this->r_status |= BSY;
|
||||
}
|
||||
this->r_dev_ctrl = value;
|
||||
break;
|
||||
default:
|
||||
LOG_F(WARNING, "Attempted to write unknown IDE register: %x", reg_addr);
|
||||
}
|
||||
}
|
67
devices/common/ata/atabasedevice.h
Normal file
67
devices/common/ata/atabasedevice.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 Base class for ATA devices. */
|
||||
|
||||
#ifndef ATA_BASE_DEVICE_H
|
||||
#define ATA_BASE_DEVICE_H
|
||||
|
||||
#include <devices/common/ata/atadefs.h>
|
||||
#include <devices/common/hwcomponent.h>
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
class AtaBaseDevice : public HWComponent, public AtaInterface
|
||||
{
|
||||
public:
|
||||
AtaBaseDevice(const std::string name);
|
||||
~AtaBaseDevice() = default;
|
||||
|
||||
uint16_t read(const uint8_t reg_addr);
|
||||
void write(const uint8_t reg_addr, const uint16_t value);
|
||||
|
||||
virtual int perform_command() = 0;
|
||||
|
||||
int get_device_id() { return this->my_dev_id; };
|
||||
|
||||
void device_reset(const uint8_t dev_head_val = 0);
|
||||
|
||||
private:
|
||||
bool is_selected() { return ((this->r_dev_head >> 4) & 1) == this->my_dev_id; };
|
||||
|
||||
protected:
|
||||
uint8_t my_dev_id = 0; // my IDE device ID configured by the host
|
||||
uint8_t device_type = ata_interface::DEVICE_TYPE_UNKNOWN;
|
||||
|
||||
// IDE aka task file registers
|
||||
uint8_t r_error;
|
||||
uint8_t r_features;
|
||||
uint8_t r_sect_count;
|
||||
uint8_t r_sect_num;
|
||||
uint8_t r_cylinder_lo;
|
||||
uint8_t r_cylinder_hi;
|
||||
uint8_t r_dev_head;
|
||||
uint8_t r_command;
|
||||
uint8_t r_status;
|
||||
uint8_t r_dev_ctrl;
|
||||
};
|
||||
|
||||
#endif // ATA_BASE_DEVICE_H
|
155
devices/common/ata/atadefs.h
Normal file
155
devices/common/ata/atadefs.h
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 ATA interface definitions. */
|
||||
|
||||
#ifndef ATA_INTERFACE_H
|
||||
#define ATA_INTERFACE_H
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace ata_interface {
|
||||
|
||||
/** Device IDs according with the Apple ATA zero/one specification. */
|
||||
enum {
|
||||
DEVICE_ID_INVALID = -1,
|
||||
DEVICE_ID_ZERO = 0,
|
||||
DEVICE_ID_ONE = 1
|
||||
};
|
||||
|
||||
/** Device types. */
|
||||
enum {
|
||||
DEVICE_TYPE_UNKNOWN = -1,
|
||||
DEVICE_TYPE_ATA = 0,
|
||||
DEVICE_TYPE_ATAPI = 1,
|
||||
};
|
||||
|
||||
/** ATA register offsets. */
|
||||
enum ATA_Reg : int {
|
||||
DATA = 0x0,
|
||||
ERROR = 0x1, // error (read)
|
||||
FEATURES = 0x1, // features (write)
|
||||
SEC_COUNT = 0x2, // sector count
|
||||
SEC_NUM = 0x3, // sector number
|
||||
CYL_LOW = 0x4, // cylinder low
|
||||
CYL_HIGH = 0x5, // cylinder high
|
||||
DEVICE_HEAD = 0x6, // device/head
|
||||
STATUS = 0x7, // status (read)
|
||||
COMMAND = 0x7, // command (write)
|
||||
ALT_STATUS = 0x16, // alt status (read)
|
||||
DEV_CTRL = 0x16, // device control (write)
|
||||
TIME_CONFIG = 0x20 // Apple ASIC specific timing configuration
|
||||
};
|
||||
|
||||
/** Status register bits. */
|
||||
enum ATA_Status : int {
|
||||
ERR = 0x1,
|
||||
IDX = 0x2,
|
||||
CORR = 0x4,
|
||||
DRQ = 0x8,
|
||||
DSC = 0x10,
|
||||
DWF = 0x20,
|
||||
DRDY = 0x40,
|
||||
BSY = 0x80
|
||||
};
|
||||
|
||||
/** Error register bits. */
|
||||
enum ATA_Error : int {
|
||||
ANMF = 0x1, //no address mark
|
||||
TK0NF = 0x2, //track 0 not found
|
||||
ABRT = 0x4, //abort command
|
||||
MCR = 0x8,
|
||||
IDNF = 0x10, //id mark not found
|
||||
MC = 0x20, //media change request
|
||||
UNC = 0x40,
|
||||
BBK = 0x80 //bad block
|
||||
};
|
||||
|
||||
/** Bit definition for the device control register. */
|
||||
enum ATA_CTRL : int{
|
||||
IEN = 0x2,
|
||||
SRST = 0x4,
|
||||
HOB = 0x80,
|
||||
};
|
||||
|
||||
/* ATA commands. */
|
||||
enum ATA_Cmd : int {
|
||||
NOP = 0x00,
|
||||
RESET_ATAPI = 0x08,
|
||||
RECALIBRATE = 0x10,
|
||||
READ_SECTOR = 0x20,
|
||||
READ_SECTOR_NR = 0x21,
|
||||
READ_LONG = 0x22,
|
||||
READ_SECTOR_EXT = 0x24,
|
||||
WRITE_SECTOR = 0x30,
|
||||
WRITE_SECTOR_NR = 0x31,
|
||||
WRITE_LONG = 0x32,
|
||||
READ_VERIFY = 0x40,
|
||||
FORMAT_TRACKS = 0x50,
|
||||
IDE_SEEK = 0x70,
|
||||
DIAGNOSTICS = 0x90,
|
||||
INIT_DEV_PARAM = 0x91,
|
||||
PACKET = 0xA0,
|
||||
IDFY_PKT_DEV = 0xA1,
|
||||
READ_MULTIPLE = 0xC4,
|
||||
WRITE_MULTIPLE = 0xC5,
|
||||
READ_DMA = 0xC8,
|
||||
WRITE_DMA = 0xCA,
|
||||
WRITE_BUFFER_DMA = 0xE9,
|
||||
READ_BUFFER_DMA = 0xEB,
|
||||
IDENTIFY_DEVICE = 0xEC,
|
||||
};
|
||||
|
||||
}; // namespace ata_interface
|
||||
|
||||
/** Interface for ATA devices. */
|
||||
class AtaInterface {
|
||||
public:
|
||||
AtaInterface() = default;
|
||||
virtual ~AtaInterface() = default;
|
||||
virtual uint16_t read(const uint8_t reg_addr) = 0;
|
||||
virtual void write(const uint8_t reg_addr, const uint16_t val) = 0;
|
||||
|
||||
virtual int get_device_id() = 0;
|
||||
};
|
||||
|
||||
/** Dummy ATA device. */
|
||||
class AtaNullDevice : public AtaInterface {
|
||||
public:
|
||||
AtaNullDevice() = default;
|
||||
~AtaNullDevice() = default;
|
||||
|
||||
uint16_t read(const uint8_t reg_addr) {
|
||||
// return all one's except DD7 if no device is present
|
||||
// DD7 corresponds to the BSY bit of the status register
|
||||
// The host should have a pull-down resistor on DD7
|
||||
// to prevent the software from waiting for a long time
|
||||
// for empty slots
|
||||
return 0xFF7FU;
|
||||
};
|
||||
|
||||
void write(const uint8_t reg_addr, const uint16_t val) {};
|
||||
|
||||
// invalid device ID means no real device is present
|
||||
int get_device_id() { return ata_interface::DEVICE_ID_INVALID; };
|
||||
};
|
||||
|
||||
#endif // ATA_INTERFACE_H
|
117
devices/common/ata/atahd.cpp
Normal file
117
devices/common/ata/atahd.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 ATA hard disk emulation. */
|
||||
|
||||
#include <devices/common/ata/atahd.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <sys/stat.h>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <loguru.hpp>
|
||||
|
||||
using namespace ata_interface;
|
||||
|
||||
AtaHardDisk::AtaHardDisk() : AtaBaseDevice("ATA-HD")
|
||||
{
|
||||
}
|
||||
|
||||
void AtaHardDisk::insert_image(std::string filename) {
|
||||
this->hdd_img.open(filename, std::fstream::out | std::fstream::in | std::fstream::binary);
|
||||
|
||||
struct stat stat_buf;
|
||||
int rc = stat(filename.c_str(), &stat_buf);
|
||||
if (!rc) {
|
||||
this->img_size = stat_buf.st_size;
|
||||
} else {
|
||||
ABORT_F("AtaHardDisk: could not determine file size using stat()");
|
||||
}
|
||||
this->hdd_img.seekg(0, std::ios_base::beg);
|
||||
}
|
||||
|
||||
int AtaHardDisk::perform_command()
|
||||
{
|
||||
LOG_F(INFO, "%s: command 0x%x requested", this->name.c_str(), this->r_command);
|
||||
this->r_status |= BSY;
|
||||
this->r_status &= ~(DRDY);
|
||||
switch (this->r_command) {
|
||||
case NOP:
|
||||
break;
|
||||
case RESET_ATAPI: {
|
||||
device_reset();
|
||||
break;
|
||||
}
|
||||
case RECALIBRATE:
|
||||
hdd_img.seekg(0, std::ios::beg);
|
||||
this->r_error = 0;
|
||||
this->r_cylinder_lo = 0;
|
||||
this->r_cylinder_hi = 0;
|
||||
break;
|
||||
case READ_SECTOR:
|
||||
case READ_SECTOR_NR: {
|
||||
this->r_status |= DRQ;
|
||||
uint16_t sec_count = (this->r_sect_count == 0) ? 256 : this->r_sect_count;
|
||||
uint32_t sector = (r_sect_num << 16);
|
||||
sector |= ((this->r_cylinder_lo) << 8) + (this->r_cylinder_hi);
|
||||
uint64_t offset = sector * 512;
|
||||
hdd_img.seekg(offset, std::ios::beg);
|
||||
hdd_img.read(buffer, sec_count * 512);
|
||||
this->r_status &= ~(DRQ);
|
||||
break;
|
||||
}
|
||||
case WRITE_SECTOR:
|
||||
case WRITE_SECTOR_NR: {
|
||||
this->r_status |= DRQ;
|
||||
uint16_t sec_count = (this->r_sect_count == 0) ? 256 : this->r_sect_count;
|
||||
uint32_t sector = (r_sect_num << 16);
|
||||
sector |= ((this->r_cylinder_lo) << 8) + (this->r_cylinder_hi);
|
||||
uint64_t offset = sector * 512;
|
||||
hdd_img.seekg(offset, std::ios::beg);
|
||||
hdd_img.write(buffer, sec_count * 512);
|
||||
this->r_status &= ~(DRQ);
|
||||
break;
|
||||
}
|
||||
case INIT_DEV_PARAM:
|
||||
break;
|
||||
case DIAGNOSTICS: {
|
||||
this->r_status |= DRQ;
|
||||
int ret_code = this->r_error;
|
||||
this->r_status &= ~(DRQ);
|
||||
return ret_code;
|
||||
break;
|
||||
}
|
||||
case IDENTIFY_DEVICE: {
|
||||
this->r_status |= DRQ;
|
||||
std::memcpy(buffer, ide_hd_id, 512);
|
||||
this->r_status &= ~(DRQ);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_F(INFO, "Unknown ATA command 0x%x", this->r_command);
|
||||
this->r_status &= ~(BSY);
|
||||
this->r_status |= ERR;
|
||||
return -1;
|
||||
}
|
||||
this->r_status &= ~(BSY);
|
||||
this->r_status |= DRDY;
|
||||
return 0;
|
||||
}
|
50
devices/common/ata/atahd.h
Normal file
50
devices/common/ata/atahd.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 TA hard disk definitions. */
|
||||
|
||||
#ifndef ATA_HARD_DISK_H
|
||||
#define ATA_HARD_DISK_H
|
||||
|
||||
#include <devices/common/ata/atabasedevice.h>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
class AtaHardDisk : public AtaBaseDevice
|
||||
{
|
||||
public:
|
||||
AtaHardDisk();
|
||||
~AtaHardDisk() = default;
|
||||
|
||||
void insert_image(std::string filename);
|
||||
int perform_command();
|
||||
|
||||
private:
|
||||
std::fstream hdd_img;
|
||||
uint64_t img_size;
|
||||
char * buffer = new char[1 <<17];
|
||||
|
||||
char* ide_hd_id = {
|
||||
0x0
|
||||
};
|
||||
};
|
||||
|
||||
#endif // ATA_HARD_DISK_H
|
90
devices/common/ata/idechannel.cpp
Normal file
90
devices/common/ata/idechannel.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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/>.
|
||||
*/
|
||||
|
||||
/** IDE Channel (aka IDE port) emulation.
|
||||
|
||||
One IDE channel is capable of controlling up to two IDE devices.
|
||||
This class handles device registration and passing of messages
|
||||
from and to the host.
|
||||
*/
|
||||
|
||||
#include <devices/common/ata/atadefs.h>
|
||||
#include <devices/common/ata/idechannel.h>
|
||||
#include <devices/common/hwcomponent.h>
|
||||
#include <devices/deviceregistry.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using namespace ata_interface;
|
||||
|
||||
IdeChannel::IdeChannel(const std::string name)
|
||||
{
|
||||
this->set_name(name);
|
||||
this->supports_types(HWCompType::IDE_BUS);
|
||||
|
||||
this->devices[0] = std::unique_ptr<AtaNullDevice>(new AtaNullDevice());
|
||||
this->devices[1] = std::unique_ptr<AtaNullDevice>(new AtaNullDevice());
|
||||
}
|
||||
|
||||
uint32_t IdeChannel::read(const uint8_t reg_addr, const int size)
|
||||
{
|
||||
if (reg_addr == TIME_CONFIG) {
|
||||
if (size != 4) {
|
||||
LOG_F(WARNING, "%s: non-DWORD read from the channel config", this->name.c_str());
|
||||
}
|
||||
return this->ch_config;
|
||||
} else {
|
||||
return this->devices[this->cur_dev]->read(reg_addr);
|
||||
}
|
||||
}
|
||||
|
||||
void IdeChannel::write(const uint8_t reg_addr, const uint32_t val, const int size)
|
||||
{
|
||||
if (reg_addr == TIME_CONFIG) {
|
||||
if (size != 4) {
|
||||
LOG_F(WARNING, "%s: non-DWORD write to the channel config", this->name.c_str());
|
||||
}
|
||||
this->ch_config = val;
|
||||
} else {
|
||||
// keep track of the currently selected device
|
||||
if (reg_addr == DEVICE_HEAD) {
|
||||
this->cur_dev = (val >> 4) & 1;
|
||||
}
|
||||
|
||||
// redirect register writes to both devices
|
||||
for (auto& dev : this->devices) {
|
||||
dev->write(reg_addr, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const DeviceDescription Ide0_Descriptor = {
|
||||
IdeChannel::create_first, {}, {}
|
||||
};
|
||||
|
||||
static const DeviceDescription Ide1_Descriptor = {
|
||||
IdeChannel::create_second, {}, {}
|
||||
};
|
||||
|
||||
REGISTER_DEVICE(Ide0, Ide0_Descriptor);
|
||||
REGISTER_DEVICE(Ide1, Ide1_Descriptor);
|
58
devices/common/ata/idechannel.h
Normal file
58
devices/common/ata/idechannel.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 IDE Channel (aka IDE port) definitions. */
|
||||
|
||||
#ifndef IDE_CHANNEL_H
|
||||
#define IDE_CHANNEL_H
|
||||
|
||||
#include <devices/common/ata/atadefs.h>
|
||||
#include <devices/common/hwcomponent.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class IdeChannel : public HWComponent
|
||||
{
|
||||
public:
|
||||
IdeChannel(const std::string name);
|
||||
~IdeChannel() = default;
|
||||
|
||||
static std::unique_ptr<HWComponent> create_first() {
|
||||
return std::unique_ptr<IdeChannel>(new IdeChannel("IDEO"));
|
||||
}
|
||||
|
||||
static std::unique_ptr<HWComponent> create_second() {
|
||||
return std::unique_ptr<IdeChannel>(new IdeChannel("IDE1"));
|
||||
}
|
||||
|
||||
uint32_t read(const uint8_t reg_addr, const int size);
|
||||
void write(const uint8_t reg_addr, const uint32_t val, const int size);
|
||||
|
||||
private:
|
||||
int cur_dev = 0;
|
||||
uint32_t ch_config = 0; // timing configuration for this channel
|
||||
|
||||
std::unique_ptr<AtaInterface> devices[2];
|
||||
};
|
||||
|
||||
#endif // IDE_CHANNEL_H
|
@ -43,6 +43,8 @@ enum HWCompType {
|
||||
SCSI_BUS = 1ULL << 20, /* SCSI bus */
|
||||
SCSI_HOST = 1ULL << 21, /* SCSI host adapter */
|
||||
SCSI_DEV = 1ULL << 22, /* SCSI device */
|
||||
IDE_BUS = 1ULL << 23, /* IDE bus */
|
||||
IDE_DEV = 1ULL << 25, /* IDE device */
|
||||
SND_SERVER = 1ULL << 31, /* host sound server */
|
||||
FLOPPY_CTRL = 1ULL << 32, /* floppy disk controller */
|
||||
FLOPPY_DRV = 1ULL << 33, /* floppy disk drive */
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-21 divingkatae and maximum
|
||||
Copyright (C) 2018-23 divingkatae and maximum
|
||||
(theweirdo) spatium
|
||||
|
||||
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
|
||||
@ -21,22 +21,57 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/** @file MESH (Macintosh Enhanced SCSI Hardware) controller emulation. */
|
||||
|
||||
#include <core/timermanager.h>
|
||||
#include <devices/common/hwinterrupt.h>
|
||||
#include <devices/common/scsi/mesh.h>
|
||||
#include <devices/deviceregistry.h>
|
||||
#include <loguru.hpp>
|
||||
#include <machines/machinebase.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <loguru.hpp>
|
||||
|
||||
using namespace MeshScsi;
|
||||
|
||||
uint8_t MESHController::read(uint8_t reg_offset)
|
||||
int MeshController::device_postinit()
|
||||
{
|
||||
this->bus_obj = dynamic_cast<ScsiBus*>(gMachineObj->get_comp_by_name("SCSI0"));
|
||||
|
||||
this->int_ctrl = dynamic_cast<InterruptCtrl*>(
|
||||
gMachineObj->get_comp_by_type(HWCompType::INT_CTRL));
|
||||
this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MeshController::reset(bool is_hard_reset)
|
||||
{
|
||||
this->cur_cmd = SeqCmd::NoOperation;
|
||||
this->int_mask = 0;
|
||||
|
||||
if (is_hard_reset) {
|
||||
this->bus_stat = 0;
|
||||
this->sync_params = 2; // guessed
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t MeshController::read(uint8_t reg_offset)
|
||||
{
|
||||
switch(reg_offset) {
|
||||
case MeshReg::Sequence:
|
||||
return this->cur_cmd;
|
||||
case MeshReg::BusStatus0:
|
||||
LOG_F(9, "MESH: read from BusStatus0 register");
|
||||
return this->bus_obj->test_ctrl_lines(0xFFU);
|
||||
case MeshReg::BusStatus1:
|
||||
return this->bus_obj->test_ctrl_lines(0xE000U) >> 8;
|
||||
case MeshReg::Exception:
|
||||
return 0;
|
||||
case MeshReg::Error:
|
||||
return 0;
|
||||
case MeshReg::IntMask:
|
||||
return this->int_mask;
|
||||
case MeshReg::Interrupt:
|
||||
return this->int_stat;
|
||||
case MeshReg::MeshID:
|
||||
LOG_F(INFO, "MESH: read from MeshID register");
|
||||
return this->chip_id; // tell them who we are
|
||||
default:
|
||||
LOG_F(WARNING, "MESH: read from unimplemented register at offset 0x%x", reg_offset);
|
||||
@ -45,34 +80,166 @@ uint8_t MESHController::read(uint8_t reg_offset)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MESHController::write(uint8_t reg_offset, uint8_t value)
|
||||
void MeshController::write(uint8_t reg_offset, uint8_t value)
|
||||
{
|
||||
uint16_t new_stat;
|
||||
|
||||
switch(reg_offset) {
|
||||
case MeshReg::Sequence:
|
||||
LOG_F(INFO, "MESH: write 0x%02X to Sequence register", value);
|
||||
perform_command(value);
|
||||
break;
|
||||
case MeshReg::BusStatus1:
|
||||
LOG_F(INFO, "MESH: write 0x%02X to BusStatus1 register", value);
|
||||
new_stat = value << 8;
|
||||
if (new_stat != this->bus_stat) {
|
||||
for (uint16_t mask = SCSI_CTRL_RST; mask >= SCSI_CTRL_SEL; mask >>= 1) {
|
||||
if ((new_stat ^ this->bus_stat) & mask) {
|
||||
if (new_stat & mask)
|
||||
this->bus_obj->assert_ctrl_line(new_stat, mask);
|
||||
else
|
||||
this->bus_obj->release_ctrl_line(new_stat, mask);
|
||||
}
|
||||
}
|
||||
this->bus_stat = new_stat;
|
||||
}
|
||||
break;
|
||||
case MeshReg::IntMask:
|
||||
LOG_F(INFO, "MESH: write 0x%02X to IntMask register", value);
|
||||
this->int_mask = value;
|
||||
break;
|
||||
case MeshReg::Interrupt:
|
||||
LOG_F(INFO, "MESH: write 0x%02X to Interrupt register", value);
|
||||
this->int_stat &= ~(value & INT_MASK); // clear requested interrupt bits
|
||||
update_irq();
|
||||
break;
|
||||
case MeshReg::SourceID:
|
||||
LOG_F(INFO, "MESH: write 0x%02X to SourceID register", value);
|
||||
this->src_id = value;
|
||||
break;
|
||||
case MeshReg::DestID:
|
||||
this->dst_id = value;
|
||||
break;
|
||||
case MeshReg::SyncParms:
|
||||
LOG_F(INFO, "MESH: write 0x%02X to SyncParms register", value);
|
||||
this->sync_params = value;
|
||||
break;
|
||||
case MeshReg::SelTimeOut:
|
||||
LOG_F(9, "MESH: selection timeout set to 0x%x", value);
|
||||
break;
|
||||
default:
|
||||
LOG_F(WARNING, "MESH: write to unimplemented register at offset 0x%x", reg_offset);
|
||||
LOG_F(WARNING, "MESH: write to unimplemented register at offset 0x%x",
|
||||
reg_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshController::perform_command(const uint8_t cmd)
|
||||
{
|
||||
this->cur_cmd = cmd;
|
||||
|
||||
this->int_stat &= ~INT_CMD_DONE;
|
||||
|
||||
switch (this->cur_cmd & 0xF) {
|
||||
case SeqCmd::Arbitrate:
|
||||
this->cur_state = SeqState::BUS_FREE;
|
||||
this->sequencer();
|
||||
break;
|
||||
case SeqCmd::Select:
|
||||
this->cur_state = SeqState::SEL_BEGIN;
|
||||
this->sequencer();
|
||||
break;
|
||||
case SeqCmd::DisReselect:
|
||||
LOG_F(INFO, "MESH: DisReselect command requested");
|
||||
break;
|
||||
case SeqCmd::ResetMesh:
|
||||
this->reset(false);
|
||||
this->int_stat |= INT_CMD_DONE;
|
||||
break;
|
||||
case SeqCmd::FlushFIFO:
|
||||
LOG_F(INFO, "MESH: FlushFIFO command requested");
|
||||
break;
|
||||
default:
|
||||
LOG_F(ERROR, "MESH: unsupported sequencer command 0x%X", this->cur_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshController::seq_defer_state(uint64_t delay_ns)
|
||||
{
|
||||
seq_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
||||
delay_ns,
|
||||
[this]() {
|
||||
// re-enter the sequencer with the state specified in next_state
|
||||
this->cur_state = this->next_state;
|
||||
this->sequencer();
|
||||
});
|
||||
}
|
||||
|
||||
void MeshController::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->src_id)) {
|
||||
LOG_F(ERROR, "MESH: arbitration error, bus not free!");
|
||||
this->bus_obj->release_ctrl_lines(this->src_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->src_id) &&
|
||||
!this->bus_obj->test_ctrl_lines(SCSI_CTRL_SEL)) { // arbitration won
|
||||
this->bus_obj->assert_ctrl_line(this->src_id, SCSI_CTRL_SEL);
|
||||
} else { // arbitration lost
|
||||
LOG_F(INFO, "MESH: arbitration lost!");
|
||||
this->bus_obj->release_ctrl_lines(this->src_id);
|
||||
this->exception |= EXC_ARB_LOST;
|
||||
this->int_stat |= INT_EXCEPTION;
|
||||
}
|
||||
this->int_stat |= INT_CMD_DONE;
|
||||
update_irq();
|
||||
break;
|
||||
case SeqState::SEL_BEGIN:
|
||||
this->bus_obj->begin_selection(this->src_id, this->dst_id, this->cur_cmd & 0x20);
|
||||
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->src_id, this->dst_id)) {
|
||||
this->bus_obj->release_ctrl_line(this->src_id, SCSI_CTRL_SEL);
|
||||
LOG_F(9, "MESH: selection completed");
|
||||
} else { // selection timeout
|
||||
this->bus_obj->disconnect(this->src_id);
|
||||
this->cur_state = SeqState::IDLE;
|
||||
this->exception |= EXC_SEL_TIMEOUT;
|
||||
this->int_stat |= INT_EXCEPTION;
|
||||
}
|
||||
this->int_stat |= INT_CMD_DONE;
|
||||
update_irq();
|
||||
break;
|
||||
default:
|
||||
ABORT_F("MESH: unimplemented sequencer state %d", this->cur_state);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshController::update_irq()
|
||||
{
|
||||
uint8_t new_irq = !!(this->int_stat & this->int_mask);
|
||||
if (new_irq != this->irq) {
|
||||
this->irq = new_irq;
|
||||
this->int_ctrl->ack_int(this->irq_id, new_irq);
|
||||
}
|
||||
}
|
||||
|
||||
static const DeviceDescription Mesh_Descriptor = {
|
||||
MESHController::create, {}, {}
|
||||
MeshController::create, {}, {}
|
||||
};
|
||||
|
||||
REGISTER_DEVICE(Mesh, Mesh_Descriptor);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-21 divingkatae and maximum
|
||||
Copyright (C) 2018-23 divingkatae and maximum
|
||||
(theweirdo) spatium
|
||||
|
||||
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
|
||||
@ -25,6 +25,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#define MESH_H
|
||||
|
||||
#include <devices/common/hwcomponent.h>
|
||||
#include <devices/common/hwinterrupt.h>
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
@ -34,7 +36,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MeshScsi {
|
||||
|
||||
// MESH registers offsets
|
||||
// MESH registers offsets.
|
||||
enum MeshReg : uint8_t {
|
||||
XferCount0 = 0,
|
||||
XferCount1 = 1,
|
||||
@ -54,26 +56,102 @@ enum MeshReg : uint8_t {
|
||||
SelTimeOut = 0xF
|
||||
};
|
||||
|
||||
// MESH Sequencer commands.
|
||||
enum SeqCmd : uint8_t {
|
||||
NoOperation = 0,
|
||||
Arbitrate = 1,
|
||||
Select = 2,
|
||||
DisReselect = 0xD,
|
||||
ResetMesh = 0xE,
|
||||
FlushFIFO = 0xF,
|
||||
};
|
||||
|
||||
// Exception register bits.
|
||||
enum {
|
||||
EXC_SEL_TIMEOUT = 1 << 0,
|
||||
EXC_PHASE_MM = 1 << 1,
|
||||
EXC_ARB_LOST = 1 << 2,
|
||||
};
|
||||
|
||||
// Interrupt register bits.
|
||||
enum {
|
||||
INT_CMD_DONE = 1 << 0,
|
||||
INT_EXCEPTION = 1 << 1,
|
||||
INT_ERROR = 1 << 2,
|
||||
INT_MASK = INT_CMD_DONE | INT_EXCEPTION | INT_ERROR
|
||||
};
|
||||
|
||||
|
||||
enum SeqState : uint32_t {
|
||||
IDLE = 0,
|
||||
BUS_FREE,
|
||||
ARB_BEGIN,
|
||||
ARB_END,
|
||||
SEL_BEGIN,
|
||||
SEL_END,
|
||||
SEND_MSG,
|
||||
SEND_CMD,
|
||||
CMD_COMPLETE,
|
||||
XFER_BEGIN,
|
||||
XFER_END,
|
||||
SEND_DATA,
|
||||
RCV_DATA,
|
||||
RCV_STATUS,
|
||||
RCV_MESSAGE,
|
||||
};
|
||||
|
||||
}; // namespace MeshScsi
|
||||
|
||||
class MESHController : public HWComponent {
|
||||
class MeshController : public HWComponent {
|
||||
public:
|
||||
MESHController(uint8_t mesh_id) {
|
||||
MeshController(uint8_t mesh_id) {
|
||||
supports_types(HWCompType::SCSI_HOST | HWCompType::SCSI_DEV);
|
||||
this->chip_id = mesh_id;
|
||||
this->reset(true);
|
||||
};
|
||||
~MESHController() = default;
|
||||
~MeshController() = default;
|
||||
|
||||
static std::unique_ptr<HWComponent> create() {
|
||||
return std::unique_ptr<MESHController>(new MESHController(HeathrowMESHID));
|
||||
return std::unique_ptr<MeshController>(new MeshController(HeathrowMESHID));
|
||||
}
|
||||
|
||||
// MESH registers access
|
||||
uint8_t read(uint8_t reg_offset);
|
||||
void write(uint8_t reg_offset, uint8_t value);
|
||||
|
||||
// HWComponent methods
|
||||
int device_postinit();
|
||||
|
||||
protected:
|
||||
void reset(bool is_hard_reset);
|
||||
void perform_command(const uint8_t cmd);
|
||||
void seq_defer_state(uint64_t delay_ns);
|
||||
void sequencer();
|
||||
void update_irq();
|
||||
|
||||
private:
|
||||
uint8_t chip_id; // Chip ID for the MESH controller
|
||||
uint8_t chip_id;
|
||||
uint8_t int_mask;
|
||||
uint8_t int_stat = 0;
|
||||
uint8_t sync_params;
|
||||
uint8_t src_id;
|
||||
uint8_t dst_id;
|
||||
uint8_t cur_cmd;
|
||||
uint8_t error;
|
||||
uint8_t exception;
|
||||
|
||||
ScsiBus* bus_obj;
|
||||
uint16_t bus_stat;
|
||||
|
||||
// Sequencer state
|
||||
uint32_t seq_timer_id;
|
||||
uint32_t cur_state;
|
||||
uint32_t next_state;
|
||||
|
||||
// interrupt related stuff
|
||||
InterruptCtrl* int_ctrl = nullptr;
|
||||
uint32_t irq_id = 0;
|
||||
uint8_t irq = 0;
|
||||
};
|
||||
|
||||
#endif // MESH_H
|
||||
|
@ -30,8 +30,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#include <machines/machinebase.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id)
|
||||
Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) : ScsiDevice(my_id)
|
||||
{
|
||||
this->chip_id = chip_id;
|
||||
this->my_bus_id = my_id;
|
||||
@ -74,13 +75,20 @@ void Sc53C94::reset_device()
|
||||
|
||||
uint8_t Sc53C94::read(uint8_t reg_offset)
|
||||
{
|
||||
uint8_t int_status;
|
||||
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:
|
||||
return this->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;
|
||||
@ -91,6 +99,8 @@ uint8_t Sc53C94::read(uint8_t reg_offset)
|
||||
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:
|
||||
@ -107,6 +117,12 @@ uint8_t Sc53C94::read(uint8_t reg_offset)
|
||||
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;
|
||||
@ -142,6 +158,37 @@ void Sc53C94::write(uint8_t reg_offset, uint8_t value)
|
||||
}
|
||||
}
|
||||
|
||||
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->cmd_fifo[0] & 0x80)) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// see if we need to refill FIFO
|
||||
if (!this->data_fifo_pos && !is_done) {
|
||||
this->sequencer();
|
||||
}
|
||||
|
||||
return data_word;
|
||||
}
|
||||
|
||||
void Sc53C94::update_command_reg(uint8_t cmd)
|
||||
{
|
||||
if (this->on_reset && (cmd & 0x7F) != CMD_NOP) {
|
||||
@ -166,13 +213,13 @@ void Sc53C94::update_command_reg(uint8_t cmd)
|
||||
}
|
||||
} else {
|
||||
LOG_F(ERROR, "SC53C94: the top of the command FIFO overwritten!");
|
||||
this->status |= 0x40; // signal IOE/Gross Error
|
||||
this->status |= STAT_GE; // signal IOE/Gross Error
|
||||
}
|
||||
}
|
||||
|
||||
void Sc53C94::exec_command()
|
||||
{
|
||||
uint8_t cmd = this->cmd_fifo[0] & 0x7F;
|
||||
uint8_t cmd = this->cur_cmd = this->cmd_fifo[0] & 0x7F;
|
||||
bool is_dma_cmd = !!(this->cmd_fifo[0] & 0x80);
|
||||
|
||||
if (is_dma_cmd) {
|
||||
@ -180,6 +227,9 @@ void Sc53C94::exec_command()
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +242,7 @@ void Sc53C94::exec_command()
|
||||
exec_next_command();
|
||||
break;
|
||||
case CMD_CLEAR_FIFO:
|
||||
this->data_fifo_pos = 0; // set the bottom of the FIFO to zero
|
||||
this->data_fifo_pos = 0; // set the bottom of the data FIFO to zero
|
||||
this->data_fifo[0] = 0;
|
||||
exec_next_command();
|
||||
break;
|
||||
@ -216,26 +266,77 @@ void Sc53C94::exec_command()
|
||||
}
|
||||
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]{
|
||||
{SeqState::RCV_STATUS, 0, 0},
|
||||
{SeqState::RCV_MESSAGE, 0, 0},
|
||||
{SeqState::CMD_COMPLETE, 0, INTSTAT_SR}
|
||||
};
|
||||
if (this->bus_obj->current_phase() != ScsiPhase::STATUS) {
|
||||
ABORT_F("Sc53C94: complete steps only works in the STATUS phase");
|
||||
}
|
||||
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]{
|
||||
{SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
|
||||
{SeqState::CMD_BEGIN, 3, INTSTAT_SR | INTSTAT_SO},
|
||||
{SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
|
||||
{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;
|
||||
sequencer();
|
||||
LOG_F(INFO, "SC53C94: SELECT W/O ATN command started");
|
||||
this->sequencer();
|
||||
LOG_F(9, "SC53C94: SELECT W/O ATN command started");
|
||||
break;
|
||||
case CMD_SELECT_WITH_ATN:
|
||||
static SeqDesc * sel_with_atn_desc = new SeqDesc[4]{
|
||||
{SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
|
||||
{SeqState::SEND_MSG, 2, INTSTAT_SR | INTSTAT_SO},
|
||||
{SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
|
||||
{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, "SC53C94: SELECT WITH ATN command started");
|
||||
break;
|
||||
case CMD_ENA_SEL_RESEL:
|
||||
LOG_F(INFO, "SC53C94: ENABLE SELECTION/RESELECTION command executed");
|
||||
exec_next_command();
|
||||
break;
|
||||
default:
|
||||
LOG_F(ERROR, "SC53C94: invalid/unimplemented command 0x%X", cmd);
|
||||
this->cmd_fifo_pos--; // remove invalid command from FIFO
|
||||
this->int_status |= INTSTAT_ICMD;
|
||||
this->update_irq();
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,14 +353,30 @@ void Sc53C94::exec_next_command()
|
||||
|
||||
void Sc53C94::fifo_push(const uint8_t data)
|
||||
{
|
||||
if (this->data_fifo_pos < 16) {
|
||||
if (this->data_fifo_pos < DATA_FIFO_MAX) {
|
||||
this->data_fifo[this->data_fifo_pos++] = data;
|
||||
} else {
|
||||
LOG_F(ERROR, "SC53C94: data FIFO overflow!");
|
||||
this->status |= 0x40; // signal IOE/Gross Error
|
||||
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, "SC53C94: data FIFO underflow!");
|
||||
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)
|
||||
{
|
||||
seq_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
||||
@ -316,8 +433,8 @@ void Sc53C94::sequencer()
|
||||
break;
|
||||
case SeqState::SEL_END:
|
||||
if (this->bus_obj->end_selection(this->my_bus_id, this->target_id)) {
|
||||
LOG_F(INFO, "SC53C94: selection completed");
|
||||
this->cmd_steps++;
|
||||
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_SEL);
|
||||
LOG_F(9, "SC53C94: selection completed");
|
||||
} else { // selection timeout
|
||||
this->seq_step = this->cmd_steps->step_num;
|
||||
this->int_status |= this->cmd_steps->status;
|
||||
@ -327,6 +444,84 @@ void Sc53C94::sequencer()
|
||||
exec_next_command();
|
||||
}
|
||||
break;
|
||||
case SeqState::SEND_MSG:
|
||||
this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out);
|
||||
this->bus_obj->push_data(this->target_id, this->data_fifo, this->bytes_out);
|
||||
this->data_fifo_pos -= this->bytes_out;
|
||||
if (this->data_fifo_pos > 0) {
|
||||
std::memmove(this->data_fifo, &this->data_fifo[this->bytes_out], this->data_fifo_pos);
|
||||
}
|
||||
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_ATN);
|
||||
break;
|
||||
case SeqState::SEND_CMD:
|
||||
this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out);
|
||||
this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos);
|
||||
this->data_fifo_pos = 0;
|
||||
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->cmd_fifo[0] & 0x80) {
|
||||
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->cmd_fifo[0] & 0x80)) {
|
||||
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("SC53C94: unimplemented sequencer state %d", this->cur_state);
|
||||
}
|
||||
@ -342,7 +537,7 @@ void Sc53C94::update_irq()
|
||||
}
|
||||
}
|
||||
|
||||
void Sc53C94::notify(ScsiMsg msg_type, int param)
|
||||
void Sc53C94::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
|
||||
{
|
||||
switch (msg_type) {
|
||||
case ScsiMsg::CONFIRM_SEL:
|
||||
@ -350,13 +545,111 @@ void Sc53C94::notify(ScsiMsg msg_type, int param)
|
||||
// cancel selection timeout timer
|
||||
TimerManager::get_instance()->cancel_timer(this->seq_timer_id);
|
||||
this->cur_state = SeqState::SEL_END;
|
||||
sequencer();
|
||||
this->sequencer();
|
||||
} else {
|
||||
LOG_F(WARNING, "SC53C94: ignore invalid selection confirmation message");
|
||||
}
|
||||
break;
|
||||
case ScsiMsg::BUS_PHASE_CHANGE:
|
||||
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(WARNING, "SC53C94: ignore notification message, type: %d", msg_type);
|
||||
LOG_F(9, "SC53C94: ignore notification message, type: %d", 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 {
|
||||
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->cmd_fifo[0] & 0x80) && 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(int direction)
|
||||
{
|
||||
bool is_done = false;
|
||||
|
||||
if (direction) {
|
||||
uint32_t got_bytes;
|
||||
uint8_t* src_ptr;
|
||||
|
||||
while (this->xfer_count) {
|
||||
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) {
|
||||
is_done = true;
|
||||
this->status |= STAT_TC; // signal zero transfer count
|
||||
this->cur_state = SeqState::XFER_END;
|
||||
this->sequencer();
|
||||
}
|
||||
}
|
||||
} else { // transfer data from target to host's memory
|
||||
while (this->xfer_count) {
|
||||
if (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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,12 +29,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#ifndef SC_53C94_H
|
||||
#define SC_53C94_H
|
||||
|
||||
#include <devices/common/dmacore.h>
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
#include <devices/common/hwinterrupt.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#define DATA_FIFO_MAX 16
|
||||
|
||||
/** 53C94 read registers */
|
||||
namespace Read {
|
||||
enum Reg53C94 : uint8_t {
|
||||
@ -78,13 +81,23 @@ namespace Write {
|
||||
|
||||
/** NCR53C94/Am53CF94 commands. */
|
||||
enum {
|
||||
CMD_NOP = 0,
|
||||
CMD_CLEAR_FIFO = 1,
|
||||
CMD_RESET_DEVICE = 2,
|
||||
CMD_RESET_BUS = 3,
|
||||
CMD_DMA_STOP = 4,
|
||||
CMD_SELECT_NO_ATN = 0x41,
|
||||
CMD_ENA_SEL_RESEL = 0x44,
|
||||
CMD_NOP = 0,
|
||||
CMD_CLEAR_FIFO = 1,
|
||||
CMD_RESET_DEVICE = 2,
|
||||
CMD_RESET_BUS = 3,
|
||||
CMD_DMA_STOP = 4,
|
||||
CMD_XFER = 0x10,
|
||||
CMD_COMPLETE_STEPS = 0x11,
|
||||
CMD_MSG_ACCEPTED = 0x12,
|
||||
CMD_SELECT_NO_ATN = 0x41,
|
||||
CMD_SELECT_WITH_ATN = 0x42,
|
||||
CMD_ENA_SEL_RESEL = 0x44,
|
||||
};
|
||||
|
||||
/** Status register bits. **/
|
||||
enum {
|
||||
STAT_TC = 1 << 4, // Terminal count (NCR) / count to zero (AMD)
|
||||
STAT_GE = 1 << 6, // Gross Error (NCR) / Illegal Operation Error (AMD)
|
||||
};
|
||||
|
||||
/** Interrupt status register bits. */
|
||||
@ -112,8 +125,15 @@ namespace SeqState {
|
||||
ARB_END,
|
||||
SEL_BEGIN,
|
||||
SEL_END,
|
||||
CMD_BEGIN,
|
||||
SEND_MSG,
|
||||
SEND_CMD,
|
||||
CMD_COMPLETE,
|
||||
XFER_BEGIN,
|
||||
XFER_END,
|
||||
SEND_DATA,
|
||||
RCV_DATA,
|
||||
RCV_STATUS,
|
||||
RCV_MESSAGE,
|
||||
};
|
||||
};
|
||||
|
||||
@ -137,11 +157,23 @@ public:
|
||||
int device_postinit();
|
||||
|
||||
// 53C94 registers access
|
||||
uint8_t read(uint8_t reg_offset);
|
||||
void write(uint8_t reg_offset, uint8_t value);
|
||||
uint8_t read(uint8_t reg_offset);
|
||||
void write(uint8_t reg_offset, uint8_t value);
|
||||
uint16_t pseudo_dma_read();
|
||||
|
||||
// real DMA control
|
||||
void real_dma_xfer(int direction);
|
||||
|
||||
void set_dma_channel(DmaBidirChannel *dma_ch) {
|
||||
this->dma_ch = dma_ch;
|
||||
};
|
||||
|
||||
// ScsiDevice methods
|
||||
void notify(ScsiMsg msg_type, int param);
|
||||
void notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param);
|
||||
bool prepare_data() { return false; };
|
||||
bool has_data() { return this->data_fifo_pos != 0; };
|
||||
int send_data(uint8_t* dst_ptr, int count);
|
||||
void process_command() {};
|
||||
|
||||
protected:
|
||||
void reset_device();
|
||||
@ -149,10 +181,13 @@ protected:
|
||||
void exec_command();
|
||||
void exec_next_command();
|
||||
void fifo_push(const uint8_t data);
|
||||
uint8_t fifo_pop();
|
||||
|
||||
void sequencer();
|
||||
void seq_defer_state(uint64_t delay_ns);
|
||||
|
||||
bool rcv_data();
|
||||
|
||||
void update_irq();
|
||||
|
||||
private:
|
||||
@ -165,6 +200,7 @@ private:
|
||||
uint8_t data_fifo[16];
|
||||
int cmd_fifo_pos;
|
||||
int data_fifo_pos;
|
||||
int bytes_out;
|
||||
bool on_reset = false;
|
||||
uint32_t xfer_count;
|
||||
uint32_t set_xfer_count;
|
||||
@ -186,11 +222,15 @@ private:
|
||||
SeqDesc* cmd_steps;
|
||||
bool is_initiator;
|
||||
uint8_t cur_cmd;
|
||||
int cur_bus_phase;
|
||||
|
||||
// interrupt related stuff
|
||||
InterruptCtrl* int_ctrl = nullptr;
|
||||
uint32_t irq_id = 0;
|
||||
uint8_t irq = 0;
|
||||
|
||||
// DMA related stuff
|
||||
DmaBidirChannel* dma_ch;
|
||||
};
|
||||
|
||||
#endif // SC_53C94_H
|
||||
|
@ -28,6 +28,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
/** SCSI control signals.
|
||||
@ -40,22 +41,102 @@ enum {
|
||||
SCSI_CTRL_ATN = 1 << 3,
|
||||
SCSI_CTRL_ACK = 1 << 4,
|
||||
SCSI_CTRL_REQ = 1 << 5,
|
||||
SCSI_CTRL_SEL = 1 << 6,
|
||||
SCSI_CTRL_BSY = 1 << 7,
|
||||
SCSI_CTRL_RST = 1 << 8,
|
||||
SCSI_CTRL_SEL = 1 << 13,
|
||||
SCSI_CTRL_BSY = 1 << 14,
|
||||
SCSI_CTRL_RST = 1 << 15,
|
||||
};
|
||||
|
||||
enum ScsiPhase : int {
|
||||
BUS_FREE = 0,
|
||||
ARBITRATION,
|
||||
SELECTION,
|
||||
RESELECTION,
|
||||
RESET,
|
||||
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 : int {
|
||||
TEST_UNIT_READY = 0x0,
|
||||
REWIND = 0x1,
|
||||
REQ_SENSE = 0x3,
|
||||
FORMAT = 0x4,
|
||||
READ_BLK_LIMITS = 0x5,
|
||||
READ_6 = 0x8,
|
||||
WRITE_6 = 0xA,
|
||||
SEEK_6 = 0xB,
|
||||
INQUIRY = 0x12,
|
||||
VERIFY_6 = 0x13,
|
||||
MODE_SELECT_6 = 0x15,
|
||||
RELEASE_UNIT = 0x17,
|
||||
ERASE_6 = 0x19,
|
||||
MODE_SENSE_6 = 0x1A,
|
||||
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,
|
||||
|
||||
// CD-ROM specific commands
|
||||
READ_TOC = 0x43,
|
||||
};
|
||||
|
||||
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 = 0x0,
|
||||
NO_SECTOR = 0x1,
|
||||
WRITE_FAULT = 0x3,
|
||||
DEV_NOT_READY = 0x4,
|
||||
INVALID_CMD = 0x20,
|
||||
INVALID_LBA = 0x21,
|
||||
INVALID_CDB = 0x24,
|
||||
INVALID_LUN = 0x25,
|
||||
WRITE_PROTECT = 0x27
|
||||
};
|
||||
|
||||
/** Standard SCSI bus timing values measured in ns. */
|
||||
@ -68,15 +149,46 @@ enum ScsiMsg : int {
|
||||
|
||||
#define SCSI_MAX_DEVS 8
|
||||
|
||||
class ScsiBus;
|
||||
|
||||
typedef std::function<void()> action_callback;
|
||||
|
||||
class ScsiDevice : public HWComponent {
|
||||
public:
|
||||
ScsiDevice() = default;
|
||||
ScsiDevice(int my_id) {
|
||||
this->scsi_id = my_id;
|
||||
this->cur_phase = ScsiPhase::BUS_FREE;
|
||||
};
|
||||
~ScsiDevice() = default;
|
||||
|
||||
virtual void notify(ScsiMsg msg_type, int param) = 0;
|
||||
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 void switch_phase(const int new_phase);
|
||||
|
||||
private:
|
||||
int scsi_id;
|
||||
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 bool prepare_data() = 0;
|
||||
virtual void process_command() = 0;
|
||||
|
||||
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. */
|
||||
@ -85,20 +197,33 @@ public:
|
||||
ScsiBus();
|
||||
~ScsiBus() = default;
|
||||
|
||||
// low-level control/status
|
||||
void register_device(int id, ScsiDevice* dev_obj);
|
||||
void assert_ctrl_line(int id, uint16_t mask);
|
||||
void release_ctrl_line(int id, uint16_t mask);
|
||||
void release_ctrl_lines(int id);
|
||||
int current_phase() { return this->cur_phase; };
|
||||
// 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);
|
||||
void target_next_step();
|
||||
bool negotiate_xfer(int& bytes_in, int& bytes_out);
|
||||
|
||||
protected:
|
||||
void change_bus_phase(int initiator_id);
|
||||
@ -108,7 +233,7 @@ private:
|
||||
std::array<ScsiDevice*, SCSI_MAX_DEVS> devices;
|
||||
|
||||
// per-device state of the control lines
|
||||
uint16_t dev_ctrl_lines[SCSI_MAX_DEVS];
|
||||
uint16_t dev_ctrl_lines[SCSI_MAX_DEVS] = {};
|
||||
|
||||
uint16_t ctrl_lines;
|
||||
int cur_phase;
|
||||
|
@ -36,11 +36,11 @@ ScsiBus::ScsiBus()
|
||||
this->dev_ctrl_lines[i] = 0;
|
||||
}
|
||||
|
||||
this->ctrl_lines = 0; // all control lines released
|
||||
this->data_lines = 0; // data lines released
|
||||
this->ctrl_lines = 0; // all control lines released
|
||||
this->data_lines = 0; // data lines released
|
||||
this->arb_winner_id = -1;
|
||||
this->initiator_id = -1;
|
||||
this->target_id = -1;
|
||||
this->initiator_id = -1;
|
||||
this->target_id = -1;
|
||||
}
|
||||
|
||||
void ScsiBus::register_device(int id, ScsiDevice* dev_obj)
|
||||
@ -58,7 +58,7 @@ void ScsiBus::change_bus_phase(int initiator_id)
|
||||
if (i == initiator_id)
|
||||
continue; // don't notify the initiator
|
||||
if (this->devices[i] != nullptr) {
|
||||
this->devices[i]->notify(ScsiMsg::BUS_PHASE_CHANGE, this->cur_phase);
|
||||
this->devices[i]->notify(this, ScsiMsg::BUS_PHASE_CHANGE, this->cur_phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,15 +67,14 @@ void ScsiBus::assert_ctrl_line(int initiator_id, uint16_t mask)
|
||||
{
|
||||
uint16_t new_state = 0xFFFFU & mask;
|
||||
|
||||
this->dev_ctrl_lines[initiator_id] = new_state;
|
||||
this->dev_ctrl_lines[initiator_id] |= new_state;
|
||||
|
||||
if (new_state == this->ctrl_lines) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->ctrl_lines = new_state;
|
||||
|
||||
if (new_state & SCSI_CTRL_RST) {
|
||||
this->ctrl_lines |= SCSI_CTRL_RST;
|
||||
this->cur_phase = ScsiPhase::RESET;
|
||||
change_bus_phase(initiator_id);
|
||||
}
|
||||
@ -98,6 +97,8 @@ void ScsiBus::release_ctrl_line(int id, uint16_t mask)
|
||||
this->cur_phase = ScsiPhase::BUS_FREE;
|
||||
change_bus_phase(id);
|
||||
}
|
||||
} else {
|
||||
this->ctrl_lines = new_state;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,6 +107,61 @@ void ScsiBus::release_ctrl_lines(int id)
|
||||
this->release_ctrl_line(id, 0xFFFFUL);
|
||||
}
|
||||
|
||||
uint16_t ScsiBus::test_ctrl_lines(uint16_t mask)
|
||||
{
|
||||
uint16_t new_state = 0;
|
||||
|
||||
// OR control lines of all devices together
|
||||
for (int i = 0; i < SCSI_MAX_DEVS; i++) {
|
||||
new_state |= this->dev_ctrl_lines[i];
|
||||
}
|
||||
|
||||
return new_state & mask;
|
||||
}
|
||||
|
||||
int ScsiBus::switch_phase(int id, int new_phase)
|
||||
{
|
||||
int old_phase = this->cur_phase;
|
||||
|
||||
// leave the current phase (low-level)
|
||||
switch (old_phase) {
|
||||
case ScsiPhase::COMMAND:
|
||||
this->release_ctrl_line(id, SCSI_CTRL_CD);
|
||||
break;
|
||||
case ScsiPhase::DATA_IN:
|
||||
this->release_ctrl_line(id, SCSI_CTRL_IO);
|
||||
break;
|
||||
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)
|
||||
switch (new_phase) {
|
||||
case ScsiPhase::COMMAND:
|
||||
this->assert_ctrl_line(id, SCSI_CTRL_CD);
|
||||
break;
|
||||
case ScsiPhase::DATA_IN:
|
||||
this->assert_ctrl_line(id, SCSI_CTRL_IO);
|
||||
break;
|
||||
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)
|
||||
this->cur_phase = new_phase;
|
||||
change_bus_phase(id);
|
||||
|
||||
return old_phase;
|
||||
}
|
||||
|
||||
bool ScsiBus::begin_arbitration(int initiator_id)
|
||||
{
|
||||
if (this->cur_phase == ScsiPhase::BUS_FREE) {
|
||||
@ -143,7 +199,9 @@ bool ScsiBus::begin_selection(int initiator_id, int target_id, bool atn)
|
||||
if (this->cur_phase != ScsiPhase::ARBITRATION || this->arb_winner_id != initiator_id)
|
||||
return false;
|
||||
|
||||
this->data_lines |= (1 << initiator_id) | (1 << target_id);
|
||||
this->assert_ctrl_line(initiator_id, SCSI_CTRL_SEL);
|
||||
|
||||
this->data_lines = (1 << initiator_id) | (1 << target_id);
|
||||
|
||||
if (atn) {
|
||||
assert_ctrl_line(initiator_id, SCSI_CTRL_ATN);
|
||||
@ -161,7 +219,7 @@ void ScsiBus::confirm_selection(int target_id)
|
||||
|
||||
// notify initiator about selection confirmation from target
|
||||
if (this->initiator_id >= 0) {
|
||||
this->devices[this->initiator_id]->notify(ScsiMsg::CONFIRM_SEL, target_id);
|
||||
this->devices[this->initiator_id]->notify(this, ScsiMsg::CONFIRM_SEL, target_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,6 +229,42 @@ bool ScsiBus::end_selection(int initiator_id, int target_id)
|
||||
return this->target_id == target_id;
|
||||
}
|
||||
|
||||
bool ScsiBus::pull_data(const int id, uint8_t* dst_ptr, const int size)
|
||||
{
|
||||
if (dst_ptr == nullptr || !size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
364
devices/common/scsi/scsicdrom.cpp
Normal file
364
devices/common/scsi/scsicdrom.cpp
Normal file
@ -0,0 +1,364 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 SCSI CD-ROM emulation. */
|
||||
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
#include <devices/common/scsi/scsicdrom.h>
|
||||
#include <devices/deviceregistry.h>
|
||||
#include <loguru.hpp>
|
||||
#include <machines/machineproperties.h>
|
||||
#include <memaccess.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static char cdrom_vendor_sony_id[] = "SONY ";
|
||||
static char cdu8003a_product_id[] = "CD-ROM CDU-8003A";
|
||||
static char cdu8003a_revision_id[] = "1.9a";
|
||||
|
||||
ScsiCdrom::ScsiCdrom(int my_id) : ScsiDevice(my_id)
|
||||
{
|
||||
supports_types(HWCompType::SCSI_DEV);
|
||||
|
||||
this->sector_size = 2048;
|
||||
|
||||
this->pre_xfer_action = nullptr;
|
||||
this->post_xfer_action = nullptr;
|
||||
}
|
||||
|
||||
void ScsiCdrom::insert_image(std::string filename)
|
||||
{
|
||||
this->cdr_img.open(filename, ios::out | ios::in | ios::binary);
|
||||
|
||||
struct stat stat_buf;
|
||||
int rc = stat(filename.c_str(), &stat_buf);
|
||||
if (!rc) {
|
||||
this->img_size = stat_buf.st_size;
|
||||
this->total_frames = (this->img_size + this->sector_size - 1) / this->sector_size;
|
||||
|
||||
// create single track descriptor
|
||||
this->tracks[0] = {.trk_num = 1, .adr_ctrl = 0x14, .start_lba = 0};
|
||||
this->num_tracks = 1;
|
||||
|
||||
// create Lead-out descriptor containing all data
|
||||
this->tracks[1] = {.trk_num = LEAD_OUT_TRK_NUM, .adr_ctrl = 0x14,
|
||||
.start_lba = static_cast<uint32_t>(this->total_frames)};
|
||||
} else {
|
||||
ABORT_F("SCSI-CDROM: could not determine file size using stat()");
|
||||
}
|
||||
this->cdr_img.seekg(0, std::ios_base::beg);
|
||||
}
|
||||
|
||||
void ScsiCdrom::process_command()
|
||||
{
|
||||
uint32_t lba, xfer_len;
|
||||
|
||||
this->pre_xfer_action = nullptr;
|
||||
this->post_xfer_action = nullptr;
|
||||
|
||||
// assume successful command execution
|
||||
this->status = ScsiStatus::GOOD;
|
||||
|
||||
switch (cmd_buf[0]) {
|
||||
case ScsiCommand::TEST_UNIT_READY:
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
break;
|
||||
case ScsiCommand::READ_6:
|
||||
lba = ((cmd_buf[1] & 0x1F) << 16) + (cmd_buf[2] << 8) + cmd_buf[3];
|
||||
this->read(lba, cmd_buf[4], 6);
|
||||
break;
|
||||
case ScsiCommand::INQUIRY:
|
||||
this->inquiry();
|
||||
break;
|
||||
case ScsiCommand::MODE_SELECT_6:
|
||||
this->incoming_size = this->cmd_buf[4];
|
||||
this->switch_phase(ScsiPhase::DATA_OUT);
|
||||
break;
|
||||
case ScsiCommand::MODE_SENSE_6:
|
||||
this->mode_sense();
|
||||
break;
|
||||
case ScsiCommand::PREVENT_ALLOW_MEDIUM_REMOVAL:
|
||||
this->eject_allowed = (this->cmd_buf[4] & 1) == 0;
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
break;
|
||||
case ScsiCommand::READ_CAPACITY_10:
|
||||
this->read_capacity();
|
||||
break;
|
||||
case ScsiCommand::READ_10:
|
||||
lba = READ_DWORD_BE_U(&this->cmd_buf[2]);
|
||||
xfer_len = READ_WORD_BE_U(&this->cmd_buf[7]);
|
||||
if (this->cmd_buf[1] & 1) {
|
||||
ABORT_F("SCSI-CDROM: RelAdr bit set in READ_10");
|
||||
}
|
||||
read(lba, xfer_len, 10);
|
||||
break;
|
||||
case ScsiCommand::READ_TOC:
|
||||
this->read_toc();
|
||||
break;
|
||||
default:
|
||||
ABORT_F("SCSI_CDROM: unsupported command %d", this->cmd_buf[0]);
|
||||
}
|
||||
}
|
||||
|
||||
bool ScsiCdrom::prepare_data()
|
||||
{
|
||||
switch (this->cur_phase) {
|
||||
case ScsiPhase::DATA_IN:
|
||||
this->data_ptr = (uint8_t*)this->data_buf;
|
||||
this->data_size = this->bytes_out;
|
||||
break;
|
||||
case ScsiPhase::DATA_OUT:
|
||||
this->data_ptr = (uint8_t*)this->data_buf;
|
||||
this->data_size = 0;
|
||||
break;
|
||||
case ScsiPhase::STATUS:
|
||||
break;
|
||||
default:
|
||||
LOG_F(WARNING, "SCSI_CDROM: unexpected phase in prepare_data");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScsiCdrom::read(const uint32_t lba, const uint16_t transfer_len, const uint8_t cmd_len)
|
||||
{
|
||||
uint32_t transfer_size = transfer_len;
|
||||
|
||||
std::memset(this->data_buf, 0, sizeof(this->data_buf));
|
||||
|
||||
if (cmd_len == 6 && transfer_len == 0) {
|
||||
transfer_size = 256;
|
||||
}
|
||||
|
||||
transfer_size *= this->sector_size;
|
||||
uint64_t device_offset = lba * this->sector_size;
|
||||
|
||||
this->cdr_img.seekg(device_offset, this->cdr_img.beg);
|
||||
this->cdr_img.read(this->data_buf, transfer_size);
|
||||
|
||||
this->bytes_out = transfer_size;
|
||||
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
|
||||
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
}
|
||||
|
||||
void ScsiCdrom::inquiry()
|
||||
{
|
||||
int page_num = cmd_buf[2];
|
||||
int alloc_len = cmd_buf[4];
|
||||
|
||||
if (page_num) {
|
||||
ABORT_F("SCSI_CDROM: invalid page number in INQUIRY");
|
||||
}
|
||||
|
||||
if (alloc_len > 36) {
|
||||
LOG_F(WARNING, "SCSI_CDROM: more than 36 bytes requested in INQUIRY");
|
||||
}
|
||||
|
||||
this->data_buf[0] = 5; // device type: CD-ROM
|
||||
this->data_buf[1] = 0x80; // removable media
|
||||
this->data_buf[2] = 2; // ANSI version: SCSI-2
|
||||
this->data_buf[3] = 1; // response data format
|
||||
this->data_buf[4] = 0x1F; // additional length
|
||||
this->data_buf[5] = 0;
|
||||
this->data_buf[6] = 0;
|
||||
this->data_buf[7] = 0x18; // supports synchronous xfers and linked commands
|
||||
std::memcpy(&this->data_buf[8], cdrom_vendor_sony_id, 8);
|
||||
std::memcpy(&this->data_buf[16], cdu8003a_product_id, 16);
|
||||
std::memcpy(&this->data_buf[32], cdu8003a_revision_id, 4);
|
||||
|
||||
this->bytes_out = 36;
|
||||
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
|
||||
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
}
|
||||
|
||||
static char Apple_Copyright_Page_Data[] = "APPLE COMPUTER, INC ";
|
||||
|
||||
void ScsiCdrom::mode_sense()
|
||||
{
|
||||
uint8_t page_code = this->cmd_buf[2] & 0x3F;
|
||||
uint8_t alloc_len = this->cmd_buf[4];
|
||||
|
||||
int num_blocks = (this->img_size + this->sector_size) / this->sector_size;
|
||||
|
||||
this->data_buf[ 0] = 13; // initial data size
|
||||
this->data_buf[ 1] = 0; // medium type
|
||||
this->data_buf[ 2] = 0x80; // medium is write protected
|
||||
this->data_buf[ 3] = 8; // block description length
|
||||
|
||||
this->data_buf[ 4] = 0; // density code
|
||||
this->data_buf[ 5] = (num_blocks >> 16) & 0xFFU;
|
||||
this->data_buf[ 6] = (num_blocks >> 8) & 0xFFU;
|
||||
this->data_buf[ 7] = (num_blocks ) & 0xFFU;
|
||||
this->data_buf[ 8] = 0;
|
||||
this->data_buf[ 9] = 0;
|
||||
this->data_buf[10] = (this->sector_size >> 8) & 0xFFU;
|
||||
this->data_buf[11] = (this->sector_size ) & 0xFFU;
|
||||
|
||||
this->data_buf[12] = page_code;
|
||||
|
||||
switch (page_code) {
|
||||
case 0x01:
|
||||
this->data_buf[13] = 6; // data size
|
||||
std::memset(&this->data_buf[14], 0, 6);
|
||||
this->data_buf[0] += 7;
|
||||
break;
|
||||
case 0x30:
|
||||
this->data_buf[13] = 22; // data size
|
||||
std::memcpy(&this->data_buf[14], Apple_Copyright_Page_Data, 22);
|
||||
this->data_buf[0] += 23;
|
||||
break;
|
||||
default:
|
||||
ABORT_F("SCSI-HD: unsupported page %d in MODE_SENSE_6", page_code);
|
||||
}
|
||||
|
||||
this->bytes_out = this->data_buf[0];
|
||||
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
|
||||
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
}
|
||||
|
||||
void ScsiCdrom::read_toc()
|
||||
{
|
||||
int tot_tracks;
|
||||
uint8_t start_track = this->cmd_buf[6];
|
||||
uint16_t alloc_len = (this->cmd_buf[7] << 8) + this->cmd_buf[8];
|
||||
bool is_msf = !!(this->cmd_buf[1] & 2);
|
||||
|
||||
if (this->cmd_buf[2] & 0xF) {
|
||||
ABORT_F("SCSI-CDROM: unsupported format in READ_TOC");
|
||||
}
|
||||
|
||||
if (!alloc_len) {
|
||||
LOG_F(WARNING, "SCSI-CDROM: zero allocation length passed to READ_TOC");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!start_track) { // special case: track zero (lead-in) as starting track
|
||||
// return all tracks starting with track 1 plus lead-out
|
||||
start_track = 1;
|
||||
tot_tracks = this->num_tracks + 1;
|
||||
} else if (start_track == LEAD_OUT_TRK_NUM) {
|
||||
start_track = this->num_tracks + 1;
|
||||
tot_tracks = 1;
|
||||
} else if (start_track <= this->num_tracks) {
|
||||
tot_tracks = (this->num_tracks - start_track) + 2;
|
||||
} else {
|
||||
LOG_F(ERROR, "SCSI-CDROM: invalid starting track %d in READ_TOC", start_track);
|
||||
this->status = ScsiStatus::CHECK_CONDITION;
|
||||
this->sense = ScsiSense::ILLEGAL_REQ;
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
return;
|
||||
}
|
||||
|
||||
char* buf_ptr = this->data_buf;
|
||||
|
||||
int data_len = (tot_tracks * 8) + 2;
|
||||
|
||||
// write TOC data header
|
||||
*buf_ptr++ = (data_len >> 8) & 0xFFU;
|
||||
*buf_ptr++ = (data_len >> 0) & 0xFFU;
|
||||
*buf_ptr++ = 1; // 1st track number
|
||||
*buf_ptr++ = this->num_tracks; // last track number
|
||||
|
||||
for (int tx = 0; tx < tot_tracks; tx++) {
|
||||
if ((buf_ptr - this->data_buf + 8) > alloc_len)
|
||||
break; // exit the loop if the output buffer length is exhausted
|
||||
|
||||
TrackDescriptor& td = this->tracks[start_track + tx - 1];
|
||||
|
||||
*buf_ptr++ = 0; // reserved
|
||||
*buf_ptr++ = td.adr_ctrl;
|
||||
*buf_ptr++ = td.trk_num;
|
||||
*buf_ptr++ = 0; // reserved
|
||||
|
||||
if (is_msf) {
|
||||
AddrMsf msf = lba_to_msf(td.start_lba + 150);
|
||||
*buf_ptr++ = 0; // reserved
|
||||
*buf_ptr++ = msf.min;
|
||||
*buf_ptr++ = msf.sec;
|
||||
*buf_ptr++ = msf.frm;
|
||||
} else {
|
||||
*buf_ptr++ = (td.start_lba >> 24) & 0xFFU;
|
||||
*buf_ptr++ = (td.start_lba >> 16) & 0xFFU;
|
||||
*buf_ptr++ = (td.start_lba >> 8) & 0xFFU;
|
||||
*buf_ptr++ = (td.start_lba >> 0) & 0xFFU;
|
||||
}
|
||||
}
|
||||
|
||||
this->bytes_out = alloc_len;
|
||||
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
|
||||
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
}
|
||||
|
||||
void ScsiCdrom::read_capacity()
|
||||
{
|
||||
uint32_t lba = READ_DWORD_BE_U(&this->cmd_buf[2]);
|
||||
|
||||
if (this->cmd_buf[1] & 1) {
|
||||
ABORT_F("SCSI-CDROM: RelAdr bit set in READ_CAPACITY_10");
|
||||
}
|
||||
|
||||
if (!(this->cmd_buf[8] & 1) && lba) {
|
||||
LOG_F(ERROR, "SCSI-CDROM: non-zero LBA for PMI=0");
|
||||
this->status = ScsiStatus::CHECK_CONDITION;
|
||||
this->sense = ScsiSense::ILLEGAL_REQ;
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
return;
|
||||
}
|
||||
|
||||
int last_lba = this->total_frames - 1;
|
||||
|
||||
this->data_buf[0] = (last_lba >> 24) & 0xFFU;
|
||||
this->data_buf[1] = (last_lba >> 16) & 0xFFU;
|
||||
this->data_buf[2] = (last_lba >> 8) & 0xFFU;
|
||||
this->data_buf[3] = (last_lba >> 0) & 0xFFU;
|
||||
this->data_buf[4] = 0;
|
||||
this->data_buf[5] = 0;
|
||||
this->data_buf[6] = (this->sector_size >> 8) & 0xFFU;
|
||||
this->data_buf[7] = this->sector_size & 0xFFU;
|
||||
|
||||
this->bytes_out = 8;
|
||||
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
|
||||
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
}
|
||||
|
||||
AddrMsf ScsiCdrom::lba_to_msf(const int lba)
|
||||
{
|
||||
return {.min = lba / 4500, .sec = (lba / 75) % 60, .frm = lba % 75};
|
||||
}
|
||||
|
||||
static const PropMap ScsiCdromProperties = {
|
||||
{"cdr_img", new StrProperty("")},
|
||||
};
|
||||
|
||||
static const DeviceDescription ScsiCdromDescriptor =
|
||||
{ScsiCdrom::create, {}, ScsiCdromProperties};
|
||||
|
||||
REGISTER_DEVICE(ScsiCdrom, ScsiCdromDescriptor);
|
89
devices/common/scsi/scsicdrom.h
Normal file
89
devices/common/scsi/scsicdrom.h
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 SCSI CD-ROM definitions. */
|
||||
|
||||
#ifndef SCSI_CDROM_H
|
||||
#define SCSI_CDROM_H
|
||||
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
/* Original CD-ROM addressing mode expressed
|
||||
in minutes, seconds and frames */
|
||||
typedef struct {
|
||||
int min;
|
||||
int sec;
|
||||
int frm;
|
||||
} AddrMsf;
|
||||
|
||||
/* Descriptor for CD-ROM tracks in TOC */
|
||||
typedef struct {
|
||||
uint8_t trk_num;
|
||||
uint8_t adr_ctrl;
|
||||
uint32_t start_lba;
|
||||
} TrackDescriptor;
|
||||
|
||||
#define CDROM_MAX_TRACKS 100
|
||||
#define LEAD_OUT_TRK_NUM 0xAA
|
||||
|
||||
class ScsiCdrom : public ScsiDevice {
|
||||
public:
|
||||
ScsiCdrom(int my_id);
|
||||
~ScsiCdrom() = default;
|
||||
|
||||
static std::unique_ptr<HWComponent> create() {
|
||||
return std::unique_ptr<ScsiCdrom>(new ScsiCdrom(3));
|
||||
}
|
||||
|
||||
void insert_image(std::string filename);
|
||||
|
||||
virtual void process_command();
|
||||
virtual bool prepare_data();
|
||||
|
||||
protected:
|
||||
void read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len);
|
||||
void inquiry();
|
||||
void mode_sense();
|
||||
void read_toc();
|
||||
void read_capacity();
|
||||
|
||||
private:
|
||||
AddrMsf lba_to_msf(const int lba);
|
||||
|
||||
private:
|
||||
std::fstream cdr_img;
|
||||
uint64_t img_size;
|
||||
int total_frames;
|
||||
int num_tracks;
|
||||
int sector_size;
|
||||
bool eject_allowed = true;
|
||||
int bytes_out = 0;
|
||||
TrackDescriptor tracks[CDROM_MAX_TRACKS];
|
||||
|
||||
char data_buf[1 << 18]; // TODO: proper buffer management
|
||||
};
|
||||
|
||||
#endif // SCSI_CDROM_H
|
165
devices/common/scsi/scsidevice.cpp
Normal file
165
devices/common/scsi/scsidevice.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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/>.
|
||||
*/
|
||||
|
||||
#include <core/timermanager.h>
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
void ScsiDevice::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
|
||||
{
|
||||
if (msg_type == ScsiMsg::BUS_PHASE_CHANGE) {
|
||||
switch (param) {
|
||||
case ScsiPhase::RESET:
|
||||
LOG_F(9, "ScsiDevice %d: bus reset aknowledged", this->scsi_id);
|
||||
break;
|
||||
case ScsiPhase::SELECTION:
|
||||
// check if something tries to select us
|
||||
if (bus_obj->get_data_lines() & (1 << scsi_id)) {
|
||||
LOG_F(9, "ScsiDevice %d selected", this->scsi_id);
|
||||
TimerManager::get_instance()->add_oneshot_timer(
|
||||
BUS_SETTLE_DELAY,
|
||||
[this, bus_obj]() {
|
||||
// don't confirm selection if BSY or I/O are asserted
|
||||
if (bus_obj->test_ctrl_lines(SCSI_CTRL_BSY | SCSI_CTRL_IO))
|
||||
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();
|
||||
this->bus_obj = bus_obj;
|
||||
if (bus_obj->test_ctrl_lines(SCSI_CTRL_ATN)) {
|
||||
this->switch_phase(ScsiPhase::MESSAGE_OUT);
|
||||
if (this->msg_buf[0] != 0x80) {
|
||||
LOG_F(INFO, "ScsiDevice: received message 0x%X", this->msg_buf[0]);
|
||||
}
|
||||
}
|
||||
this->switch_phase(ScsiPhase::COMMAND);
|
||||
this->process_command();
|
||||
if (this->prepare_data()) {
|
||||
bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_REQ);
|
||||
} else {
|
||||
ABORT_F("ScsiDevice: prepare_data() failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScsiDevice::switch_phase(const int new_phase)
|
||||
{
|
||||
this->cur_phase = new_phase;
|
||||
this->bus_obj->switch_phase(this->scsi_id, this->cur_phase);
|
||||
}
|
||||
|
||||
void ScsiDevice::next_step(ScsiBus* bus_obj)
|
||||
{
|
||||
switch (this->cur_phase) {
|
||||
case ScsiPhase::COMMAND:
|
||||
this->process_command();
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
break;
|
||||
case ScsiPhase::DATA_OUT:
|
||||
if (this->data_size >= this->incoming_size) {
|
||||
if (this->post_xfer_action != nullptr) {
|
||||
this->post_xfer_action();
|
||||
}
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
}
|
||||
break;
|
||||
case ScsiPhase::DATA_IN:
|
||||
if (!this->has_data()) {
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
}
|
||||
break;
|
||||
case ScsiPhase::STATUS:
|
||||
this->switch_phase(ScsiPhase::MESSAGE_IN);
|
||||
break;
|
||||
case ScsiPhase::MESSAGE_IN:
|
||||
case ScsiPhase::BUS_FREE:
|
||||
bus_obj->release_ctrl_lines(this->scsi_id);
|
||||
this->switch_phase(ScsiPhase::BUS_FREE);
|
||||
break;
|
||||
default:
|
||||
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::DATA_OUT:
|
||||
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)
|
||||
{
|
||||
// accumulating incoming data in the pre-configured buffer
|
||||
std::memcpy(this->data_ptr, src_ptr, count);
|
||||
this->data_ptr += count;
|
||||
this->data_size += count;
|
||||
|
||||
return count;
|
||||
}
|
307
devices/common/scsi/scsihd.cpp
Normal file
307
devices/common/scsi/scsihd.cpp
Normal file
@ -0,0 +1,307 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 Generic SCSI Hard Disk emulation. */
|
||||
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
#include <devices/common/scsi/scsihd.h>
|
||||
#include <devices/deviceregistry.h>
|
||||
#include <loguru.hpp>
|
||||
#include <machines/machinebase.h>
|
||||
#include <machines/machineproperties.h>
|
||||
#include <memaccess.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define HDD_SECTOR_SIZE 512
|
||||
|
||||
using namespace std;
|
||||
|
||||
ScsiHardDisk::ScsiHardDisk(int my_id) : ScsiDevice(my_id) {
|
||||
supports_types(HWCompType::SCSI_DEV);
|
||||
}
|
||||
|
||||
void ScsiHardDisk::insert_image(std::string filename) {
|
||||
//We don't want to store everything in memory, but
|
||||
//we want to keep the hard disk available.
|
||||
this->hdd_img.open(filename, ios::out | ios::in | ios::binary);
|
||||
|
||||
struct stat stat_buf;
|
||||
int rc = stat(filename.c_str(), &stat_buf);
|
||||
if (!rc) {
|
||||
this->img_size = stat_buf.st_size;
|
||||
this->total_blocks = (this->img_size + HDD_SECTOR_SIZE - 1) / HDD_SECTOR_SIZE;
|
||||
} else {
|
||||
ABORT_F("ScsiHardDisk: could not determine file size using stat()");
|
||||
}
|
||||
this->hdd_img.seekg(0, std::ios_base::beg);
|
||||
}
|
||||
|
||||
void ScsiHardDisk::process_command() {
|
||||
uint32_t lba = 0;
|
||||
uint16_t transfer_len = 0;
|
||||
uint16_t alloc_len = 0;
|
||||
uint8_t param_len = 0;
|
||||
|
||||
uint8_t page_code = 0;
|
||||
uint8_t subpage_code = 0;
|
||||
|
||||
this->pre_xfer_action = nullptr;
|
||||
this->post_xfer_action = nullptr;
|
||||
|
||||
// assume successful command execution
|
||||
this->status = ScsiStatus::GOOD;
|
||||
|
||||
uint8_t* cmd = this->cmd_buf;
|
||||
|
||||
if (cmd[0] != 0 && cmd[0] != 8 && cmd[0] != 0xA && cmd[0] != 0x28
|
||||
&& cmd[0] != 0x2A && cmd[0] != 0x25) {
|
||||
ABORT_F("SCSI-HD: untested command 0x%X", cmd[0]);
|
||||
}
|
||||
|
||||
switch (cmd[0]) {
|
||||
case ScsiCommand::TEST_UNIT_READY:
|
||||
test_unit_ready();
|
||||
break;
|
||||
case ScsiCommand::REWIND:
|
||||
rewind();
|
||||
break;
|
||||
case ScsiCommand::REQ_SENSE:
|
||||
alloc_len = cmd[4];
|
||||
req_sense(alloc_len);
|
||||
break;
|
||||
case ScsiCommand::INQUIRY:
|
||||
alloc_len = (cmd[3] << 8) + cmd[4];
|
||||
inquiry(alloc_len);
|
||||
break;
|
||||
case ScsiCommand::READ_6:
|
||||
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
|
||||
transfer_len = cmd[4];
|
||||
read(lba, transfer_len, 6);
|
||||
break;
|
||||
case ScsiCommand::READ_10:
|
||||
lba = (cmd[2] << 24) + (cmd[3] << 16) + (cmd[4] << 8) + cmd[5];
|
||||
transfer_len = (cmd[7] << 8) + cmd[8];
|
||||
read(lba, transfer_len, 10);
|
||||
break;
|
||||
case ScsiCommand::WRITE_6:
|
||||
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
|
||||
transfer_len = cmd[4];
|
||||
write(lba, transfer_len, 6);
|
||||
break;
|
||||
case ScsiCommand::WRITE_10:
|
||||
lba = (cmd[2] << 24) + (cmd[3] << 16) + (cmd[4] << 8) + cmd[5];
|
||||
transfer_len = (cmd[7] << 8) + cmd[8];
|
||||
write(lba, transfer_len, 10);
|
||||
this->switch_phase(ScsiPhase::DATA_OUT);
|
||||
break;
|
||||
case ScsiCommand::SEEK_6:
|
||||
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
|
||||
seek(lba);
|
||||
break;
|
||||
case ScsiCommand::MODE_SELECT_6:
|
||||
param_len = cmd[4];
|
||||
mode_select_6(param_len);
|
||||
break;
|
||||
case ScsiCommand::MODE_SENSE_6:
|
||||
page_code = cmd[2] & 0x1F;
|
||||
subpage_code = cmd[3];
|
||||
alloc_len = cmd[4];
|
||||
mode_sense_6(page_code, subpage_code, alloc_len);
|
||||
break;
|
||||
case ScsiCommand::READ_CAPACITY_10:
|
||||
read_capacity_10();
|
||||
break;
|
||||
default:
|
||||
LOG_F(WARNING, "SCSI_HD: unrecognized command: %x", cmd[0]);
|
||||
}
|
||||
}
|
||||
|
||||
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::DATA_OUT:
|
||||
this->data_ptr = (uint8_t*)this->img_buffer;
|
||||
this->data_size = 0;
|
||||
break;
|
||||
case ScsiPhase::STATUS:
|
||||
if (!error) {
|
||||
this->img_buffer[0] = ScsiStatus::GOOD;
|
||||
} else {
|
||||
this->img_buffer[0] = ScsiStatus::CHECK_CONDITION;
|
||||
}
|
||||
this->cur_buf_cnt = 1;
|
||||
break;
|
||||
case ScsiPhase::MESSAGE_IN:
|
||||
this->img_buffer[0] = this->msg_code;
|
||||
this->cur_buf_cnt = 1;
|
||||
break;
|
||||
default:
|
||||
LOG_F(WARNING, "SCSI_HD: unexpected phase in prepare_data");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ScsiHardDisk::test_unit_ready() {
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
return ScsiError::NO_ERROR;
|
||||
}
|
||||
|
||||
int ScsiHardDisk::req_sense(uint16_t alloc_len) {
|
||||
if (alloc_len != 252) {
|
||||
LOG_F(WARNING, "Inappropriate Allocation Length: %d", alloc_len);
|
||||
}
|
||||
return ScsiError::NO_ERROR; // placeholder - no sense
|
||||
}
|
||||
|
||||
void ScsiHardDisk::inquiry(uint16_t alloc_len) {
|
||||
if (alloc_len >= 48) {
|
||||
uint8_t empty_filler[1 << 17] = {0x0};
|
||||
std::memcpy(img_buffer, empty_filler, (1 << 17));
|
||||
img_buffer[2] = 0x1;
|
||||
img_buffer[3] = 0x2;
|
||||
img_buffer[4] = 0x31;
|
||||
img_buffer[7] = 0x1C;
|
||||
std::memcpy(img_buffer + 8, vendor_info, 8);
|
||||
std::memcpy(img_buffer + 16, prod_info, 16);
|
||||
std::memcpy(img_buffer + 32, rev_info, 8);
|
||||
std::memcpy(img_buffer + 40, serial_info, 8);
|
||||
}
|
||||
else {
|
||||
LOG_F(WARNING, "Inappropriate Allocation Length: %d", alloc_len);
|
||||
}
|
||||
}
|
||||
|
||||
int ScsiHardDisk::send_diagnostic() {
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
int ScsiHardDisk::mode_select_6(uint8_t param_len) {
|
||||
if (param_len == 0) {
|
||||
return 0x0;
|
||||
}
|
||||
else {
|
||||
LOG_F(WARNING, "Mode Select calling for param length of: %d", param_len);
|
||||
return param_len;
|
||||
}
|
||||
}
|
||||
|
||||
void ScsiHardDisk::mode_sense_6(uint8_t page_code, uint8_t subpage_code, uint8_t alloc_len) {
|
||||
LOG_F(WARNING, "Page Code %d; Subpage Code: %d", page_code, subpage_code);
|
||||
}
|
||||
|
||||
void ScsiHardDisk::read_capacity_10() {
|
||||
uint32_t lba = READ_DWORD_BE_U(&this->cmd_buf[2]);
|
||||
|
||||
if (this->cmd_buf[1] & 1) {
|
||||
ABORT_F("SCSI-HD: RelAdr bit set in READ_CAPACITY_10");
|
||||
}
|
||||
|
||||
if (!(this->cmd_buf[8] & 1) && lba) {
|
||||
LOG_F(ERROR, "SCSI-HD: non-zero LBA for PMI=0");
|
||||
this->status = ScsiStatus::CHECK_CONDITION;
|
||||
this->sense = ScsiSense::ILLEGAL_REQ;
|
||||
this->switch_phase(ScsiPhase::STATUS);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t last_lba = this->total_blocks - 1;
|
||||
uint32_t blk_len = HDD_SECTOR_SIZE;
|
||||
|
||||
WRITE_DWORD_BE_A(&img_buffer[0], last_lba);
|
||||
WRITE_DWORD_BE_A(&img_buffer[4], blk_len);
|
||||
|
||||
this->cur_buf_cnt = 8;
|
||||
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
|
||||
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
}
|
||||
|
||||
|
||||
void ScsiHardDisk::format() {
|
||||
}
|
||||
|
||||
void ScsiHardDisk::read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) {
|
||||
uint32_t transfer_size = transfer_len;
|
||||
|
||||
std::memset(img_buffer, 0, sizeof(img_buffer));
|
||||
|
||||
if (cmd_len == 6 && transfer_len == 0) {
|
||||
transfer_size = 256;
|
||||
}
|
||||
|
||||
transfer_size *= HDD_SECTOR_SIZE;
|
||||
uint64_t device_offset = lba * HDD_SECTOR_SIZE;
|
||||
|
||||
this->hdd_img.seekg(device_offset, this->hdd_img.beg);
|
||||
this->hdd_img.read(img_buffer, transfer_size);
|
||||
|
||||
this->cur_buf_cnt = transfer_size;
|
||||
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
|
||||
|
||||
this->switch_phase(ScsiPhase::DATA_IN);
|
||||
}
|
||||
|
||||
void ScsiHardDisk::write(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) {
|
||||
uint32_t transfer_size = transfer_len;
|
||||
|
||||
if (cmd_len == 6 && transfer_len == 0) {
|
||||
transfer_size = 256;
|
||||
}
|
||||
|
||||
transfer_size *= HDD_SECTOR_SIZE;
|
||||
uint64_t device_offset = lba * HDD_SECTOR_SIZE;
|
||||
|
||||
this->incoming_size = transfer_size;
|
||||
|
||||
this->hdd_img.seekg(device_offset, this->hdd_img.beg);
|
||||
|
||||
this->post_xfer_action = [this]() {
|
||||
this->hdd_img.write(this->img_buffer, this->incoming_size);
|
||||
};
|
||||
}
|
||||
|
||||
void ScsiHardDisk::seek(uint32_t lba) {
|
||||
uint64_t device_offset = lba * HDD_SECTOR_SIZE;
|
||||
this->hdd_img.seekg(device_offset, this->hdd_img.beg);
|
||||
}
|
||||
|
||||
void ScsiHardDisk::rewind() {
|
||||
this->hdd_img.seekg(0, this->hdd_img.beg);
|
||||
}
|
||||
|
||||
static const PropMap SCSI_HD_Properties = {
|
||||
{"hdd_img", new StrProperty("")},
|
||||
{"hdd_wr_prot", new BinProperty(0)},
|
||||
};
|
||||
|
||||
static const DeviceDescription SCSI_HD_Descriptor =
|
||||
{ScsiHardDisk::create, {}, SCSI_HD_Properties};
|
||||
|
||||
REGISTER_DEVICE(ScsiHD, SCSI_HD_Descriptor);
|
82
devices/common/scsi/scsihd.h
Normal file
82
devices/common/scsi/scsihd.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
DingusPPC - The Experimental PowerPC Macintosh emulator
|
||||
Copyright (C) 2018-22 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 SCSI hard drive definitions. */
|
||||
|
||||
#ifndef SCSI_HD_H
|
||||
#define SCSI_HD_H
|
||||
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
class ScsiHardDisk : public ScsiDevice {
|
||||
public:
|
||||
ScsiHardDisk(int my_id);
|
||||
~ScsiHardDisk() = default;
|
||||
|
||||
static std::unique_ptr<HWComponent> create() {
|
||||
return std::unique_ptr<ScsiHardDisk>(new ScsiHardDisk(0));
|
||||
}
|
||||
|
||||
void insert_image(std::string filename);
|
||||
void process_command();
|
||||
bool prepare_data();
|
||||
|
||||
protected:
|
||||
int test_unit_ready();
|
||||
int req_sense(uint16_t alloc_len);
|
||||
int send_diagnostic();
|
||||
int mode_select_6(uint8_t param_len);
|
||||
|
||||
void mode_sense_6(uint8_t page_code, uint8_t subpage_code, uint8_t alloc_len);
|
||||
void format();
|
||||
void inquiry(uint16_t alloc_len);
|
||||
void read_capacity_10();
|
||||
void read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len);
|
||||
void write(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len);
|
||||
void seek(uint32_t lba);
|
||||
void rewind();
|
||||
|
||||
private:
|
||||
std::fstream hdd_img;
|
||||
uint64_t img_size;
|
||||
int total_blocks;
|
||||
uint64_t file_offset = 0;
|
||||
|
||||
char img_buffer[1 << 21]; // TODO: add proper buffer management!
|
||||
|
||||
uint32_t cur_buf_cnt = 0;
|
||||
uint8_t error = ScsiError::NO_ERROR;
|
||||
uint8_t msg_code = 0;
|
||||
|
||||
//inquiry info
|
||||
char vendor_info[8] = {'D', 'i', 'n', 'g', 'u', 's', 'D', '\0'};
|
||||
char prod_info[16] = {'E', 'm', 'u', 'l', 'a', 't', 'e', 'd', ' ', 'D', 'i', 's', 'k', '\0'};
|
||||
char rev_info[8] = {'d', 'i', '0', '0', '0', '0', '0', '1'};
|
||||
char serial_info[8] = {'0', '0', '0', '0', '0', '0', '0', '0'};
|
||||
};
|
||||
|
||||
#endif // SCSI_HD_H
|
@ -53,6 +53,8 @@ AMIC::AMIC() : MMIODevice()
|
||||
|
||||
// connect internal SCSI controller
|
||||
this->scsi = dynamic_cast<Sc53C94*>(gMachineObj->get_comp_by_name("Sc53C94"));
|
||||
this->scsi_dma = std::unique_ptr<AmicScsiDma> (new AmicScsiDma());
|
||||
this->scsi->set_dma_channel(this->scsi_dma.get());
|
||||
|
||||
// connect serial HW
|
||||
this->escc = dynamic_cast<EsccController*>(gMachineObj->get_comp_by_name("Escc"));
|
||||
@ -115,7 +117,11 @@ uint32_t AMIC::read(uint32_t rgn_start, uint32_t offset, int size)
|
||||
case 0xA: // MACE registers
|
||||
return this->mace->read((offset >> 4) & 0x1F);
|
||||
case 0x10: // SCSI registers
|
||||
return this->scsi->read((offset >> 4) & 0xF);
|
||||
if (offset & 0x100) {
|
||||
return this->scsi->pseudo_dma_read();
|
||||
} else {
|
||||
return this->scsi->read((offset >> 4) & 0xF);
|
||||
}
|
||||
case 0x14: // Sound registers
|
||||
switch (offset) {
|
||||
case AMICReg::Snd_Stat_0:
|
||||
@ -166,7 +172,7 @@ uint32_t AMIC::read(uint32_t rgn_start, uint32_t offset, int size)
|
||||
case AMICReg::DMA_Base_Addr_3:
|
||||
return (this->dma_base >> (3 - (offset & 3)) * 8) & 0xFF;
|
||||
case AMICReg::SCSI_DMA_Ctrl:
|
||||
return this->scsi_dma_cs;
|
||||
return this->scsi_dma->read_stat();
|
||||
case AMICReg::Floppy_Addr_Ptr_0:
|
||||
case AMICReg::Floppy_Addr_Ptr_1:
|
||||
case AMICReg::Floppy_Addr_Ptr_2:
|
||||
@ -251,6 +257,12 @@ void AMIC::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size)
|
||||
}
|
||||
|
||||
switch(offset) {
|
||||
case AMICReg::VIA2_IFR:
|
||||
// for each "1" in value clear the corresponding IRQ bit
|
||||
// TODO: is bit 7 read only?
|
||||
// TODO: update interrupts?
|
||||
this->via2_ifr &= ~(value & 0x7F);
|
||||
break;
|
||||
case AMICReg::VIA2_Slot_IER:
|
||||
LOG_F(INFO, "AMIC VIA2 Slot Interrupt Enable Register updated, val=%x", value);
|
||||
break;
|
||||
@ -314,9 +326,24 @@ void AMIC::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size)
|
||||
case AMICReg::Enet_DMA_Xmt_Ctrl:
|
||||
LOG_F(INFO, "AMIC Ethernet Transmit DMA Ctrl updated, val=%x", value);
|
||||
break;
|
||||
case AMICReg::SCSI_DMA_Base_0:
|
||||
case AMICReg::SCSI_DMA_Base_1:
|
||||
case AMICReg::SCSI_DMA_Base_2:
|
||||
case AMICReg::SCSI_DMA_Base_3:
|
||||
SET_ADDR_BYTE(this->scsi_dma_base, offset, value);
|
||||
this->scsi_dma_base &= 0xFFFFFFF8UL;
|
||||
LOG_F(9, "AMIC: SCSI DMA base address set to 0x%X", this->scsi_dma_base);
|
||||
break;
|
||||
case AMICReg::SCSI_DMA_Ctrl:
|
||||
LOG_F(INFO, "AMIC SCSI DMA Ctrl updated, val=%x", value);
|
||||
this->scsi_dma_cs = value;
|
||||
if (value & 1) { // RST bit set?
|
||||
this->scsi_addr_ptr = this->scsi_dma_base;
|
||||
this->scsi_dma->reset(this->scsi_addr_ptr);
|
||||
}
|
||||
if (value & 2) { // RUN bit set?
|
||||
this->scsi_dma->reinit(this->scsi_dma_base);
|
||||
this->scsi->real_dma_xfer((value >> 6) & 1);
|
||||
}
|
||||
this->scsi_dma->write_ctrl(value);
|
||||
break;
|
||||
case AMICReg::Enet_DMA_Rcv_Ctrl:
|
||||
LOG_F(INFO, "AMIC Ethernet Receive DMA Ctrl updated, val=%x", value);
|
||||
@ -549,6 +576,50 @@ DmaPullResult AmicFloppyDma::pull_data(uint32_t req_len, uint32_t *avail_len,
|
||||
return DmaPullResult::NoMoreData;
|
||||
}
|
||||
|
||||
// ============================ SCSI DMA stuff ================================
|
||||
void AmicScsiDma::reset(const uint32_t addr_ptr)
|
||||
{
|
||||
this->stat &= 0x48; // clear interrupt flag, RUN and RST bits
|
||||
this->addr_ptr = addr_ptr;
|
||||
this->byte_count = 0;
|
||||
}
|
||||
|
||||
void AmicScsiDma::reinit(const uint32_t addr_ptr)
|
||||
{
|
||||
this->addr_ptr = addr_ptr;
|
||||
this->byte_count = 0;
|
||||
}
|
||||
|
||||
void AmicScsiDma::write_ctrl(uint8_t value)
|
||||
{
|
||||
// copy over DIR, IE and RUN bits
|
||||
this->stat = (this->stat & 0x81) | (value & 0x4A);
|
||||
|
||||
// clear interrupt flag if requested
|
||||
if (value & 0x80) {
|
||||
this->stat &= 0x7F;
|
||||
}
|
||||
}
|
||||
|
||||
int AmicScsiDma::push_data(const char* src_ptr, int len)
|
||||
{
|
||||
uint8_t *p_data = mmu_get_dma_mem(this->addr_ptr, len);
|
||||
std::memcpy(p_data, src_ptr, len);
|
||||
|
||||
this->addr_ptr += len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DmaPullResult AmicScsiDma::pull_data(uint32_t req_len, uint32_t *avail_len,
|
||||
uint8_t **p_data)
|
||||
{
|
||||
*p_data = mmu_get_dma_mem(this->addr_ptr, req_len);
|
||||
this->addr_ptr += req_len;
|
||||
*avail_len = req_len;
|
||||
return DmaPullResult::MoreData;
|
||||
}
|
||||
|
||||
static vector<string> Amic_Subdevices = {
|
||||
"Sc53C94", "Escc", "Mace", "ViaCuda", "Swim3"
|
||||
};
|
||||
|
@ -102,6 +102,27 @@ private:
|
||||
uint8_t stat;
|
||||
};
|
||||
|
||||
/** AMIC-specific SCSI DMA implementation. */
|
||||
class AmicScsiDma : public DmaBidirChannel {
|
||||
public:
|
||||
AmicScsiDma() = default;
|
||||
~AmicScsiDma() = default;
|
||||
|
||||
void reinit(const uint32_t addr_ptr);
|
||||
void reset(const uint32_t addr_ptr);
|
||||
void write_ctrl(const uint8_t value);
|
||||
uint8_t read_stat() { return this->stat; };
|
||||
|
||||
int push_data(const char* src_ptr, int len);
|
||||
DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len,
|
||||
uint8_t **p_data);
|
||||
|
||||
private:
|
||||
uint32_t addr_ptr;
|
||||
uint16_t byte_count;
|
||||
uint8_t stat;
|
||||
};
|
||||
|
||||
// macro for byte wise updating of AMIC DMA address registers
|
||||
#define SET_ADDR_BYTE(reg, offset, value) \
|
||||
mask = 0xFF000000UL >> (8 * ((offset) & 3)); \
|
||||
@ -150,6 +171,10 @@ enum AMICReg : uint32_t {
|
||||
|
||||
// Interrupt registers
|
||||
Int_Ctrl = 0x2A000,
|
||||
DMA_Int_0 = 0x2A008,
|
||||
Bus_Err_Int_0 = 0x2A009,
|
||||
DMA_Int_1 = 0x2A00A,
|
||||
Bus_Err_Int_1 = 0x2A00B,
|
||||
|
||||
// Undocumented diagnostics register
|
||||
Diag_Reg = 0x2C000,
|
||||
@ -160,9 +185,15 @@ enum AMICReg : uint32_t {
|
||||
DMA_Base_Addr_2 = 0x31002,
|
||||
DMA_Base_Addr_3 = 0x31003,
|
||||
Enet_DMA_Xmt_Ctrl = 0x31C20,
|
||||
SCSI_DMA_Ctrl = 0x32008,
|
||||
Enet_DMA_Rcv_Ctrl = 0x32028,
|
||||
|
||||
// SCSI DMA registers
|
||||
SCSI_DMA_Base_0 = 0x32000,
|
||||
SCSI_DMA_Base_1 = 0x32001,
|
||||
SCSI_DMA_Base_2 = 0x32002,
|
||||
SCSI_DMA_Base_3 = 0x32003,
|
||||
SCSI_DMA_Ctrl = 0x32008,
|
||||
|
||||
// Floppy (SWIM3) DMA registers
|
||||
Floppy_Addr_Ptr_0 = 0x32060,
|
||||
Floppy_Addr_Ptr_1 = 0x32061,
|
||||
@ -210,15 +241,19 @@ private:
|
||||
|
||||
uint8_t emmo_pin; // EMMO aka factory tester pin status, active low
|
||||
|
||||
uint32_t dma_base = 0; // DMA physical base address
|
||||
uint16_t snd_buf_size = 0; // sound buffer size in bytes
|
||||
uint8_t snd_out_ctrl = 0;
|
||||
uint32_t dma_base = 0; // DMA physical base address
|
||||
uint32_t scsi_dma_base = 0; // physical base address for SCSI DMA
|
||||
uint16_t snd_buf_size = 0; // sound buffer size in bytes
|
||||
uint8_t snd_out_ctrl = 0;
|
||||
|
||||
// floppy DMA state
|
||||
uint32_t floppy_addr_ptr;
|
||||
uint16_t floppy_byte_cnt;
|
||||
|
||||
uint8_t scsi_dma_cs = 0; // SCSI DMA control/status register value
|
||||
// SCSI DMA state
|
||||
uint32_t scsi_addr_ptr;
|
||||
|
||||
//uint8_t scsi_dma_cs = 0; // SCSI DMA control/status register value
|
||||
|
||||
// interrupt state
|
||||
uint8_t int_ctrl = 0;
|
||||
@ -241,6 +276,7 @@ private:
|
||||
std::unique_ptr<AwacDevicePdm> awacs;
|
||||
std::unique_ptr<AmicSndOutDma> snd_out_dma;
|
||||
std::unique_ptr<AmicFloppyDma> floppy_dma;
|
||||
std::unique_ptr<AmicScsiDma> scsi_dma;
|
||||
|
||||
// on-board video
|
||||
std::unique_ptr<DisplayID> disp_id;
|
||||
|
@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <cpu/ppc/ppcemu.h>
|
||||
#include <devices/deviceregistry.h>
|
||||
#include <devices/common/ata/idechannel.h>
|
||||
#include <devices/common/dbdma.h>
|
||||
#include <devices/common/hwcomponent.h>
|
||||
#include <devices/common/viacuda.h>
|
||||
@ -76,7 +77,11 @@ HeathrowIC::HeathrowIC() : PCIDevice("mac-io/heathrow"), InterruptCtrl()
|
||||
);
|
||||
|
||||
// connect SCSI HW
|
||||
this->mesh = dynamic_cast<MESHController*>(gMachineObj->get_comp_by_name("Mesh"));
|
||||
this->mesh = dynamic_cast<MeshController*>(gMachineObj->get_comp_by_name("Mesh"));
|
||||
|
||||
// connect IDE HW
|
||||
this->ide_0 = dynamic_cast<IdeChannel*>(gMachineObj->get_comp_by_name("Ide0"));
|
||||
this->ide_1 = dynamic_cast<IdeChannel*>(gMachineObj->get_comp_by_name("Ide1"));
|
||||
|
||||
// connect serial HW
|
||||
this->escc = dynamic_cast<EsccController*>(gMachineObj->get_comp_by_name("Escc"));
|
||||
@ -161,10 +166,16 @@ uint32_t HeathrowIC::read(uint32_t rgn_start, uint32_t offset, int size) {
|
||||
break;
|
||||
case 0x15: // SWIM3
|
||||
return this->swim3->read((offset >> 4 )& 0xF);
|
||||
case 0x16:
|
||||
case 0x16: // VIA-CUDA
|
||||
case 0x17:
|
||||
res = this->viacuda->read((offset - 0x16000) >> 9);
|
||||
break;
|
||||
case 0x20: // IDE 0
|
||||
res = this->ide_0->read((offset >> 4) & 0x1F, size);
|
||||
break;
|
||||
case 0x21: // IDE 1
|
||||
res = this->ide_1->read((offset >> 4) & 0x1F, size);
|
||||
break;
|
||||
default:
|
||||
if (sub_addr >= 0x60) {
|
||||
res = this->nvram->read_byte((offset - 0x60000) >> 4);
|
||||
@ -206,10 +217,16 @@ void HeathrowIC::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int
|
||||
case 0x15: // SWIM3
|
||||
this->swim3->write((offset >> 4) & 0xF, value);
|
||||
break;
|
||||
case 0x16:
|
||||
case 0x16: // VIA-CUDA
|
||||
case 0x17:
|
||||
this->viacuda->write((offset - 0x16000) >> 9, value);
|
||||
break;
|
||||
case 0x20: // IDE O
|
||||
this->ide_0->write((offset >> 4) & 0x1F, value, size);
|
||||
break;
|
||||
case 0x21: // IDE 1
|
||||
this->ide_1->write((offset >> 4) & 0x1F, value, size);
|
||||
break;
|
||||
default:
|
||||
if (sub_addr >= 0x60) {
|
||||
this->nvram->write_byte((offset - 0x60000) >> 4, value);
|
||||
@ -386,8 +403,7 @@ void HeathrowIC::clear_cpu_int()
|
||||
}
|
||||
|
||||
static const vector<string> Heathrow_Subdevices = {
|
||||
"NVRAM", "ViaCuda", "Mesh", "Escc", "Swim3"
|
||||
};
|
||||
"NVRAM", "ViaCuda", "Mesh", "Escc", "Swim3", "Ide0", "Ide1"};
|
||||
|
||||
static const DeviceDescription Heathrow_Descriptor = {
|
||||
HeathrowIC::create, Heathrow_Subdevices, {}
|
||||
|
@ -51,6 +51,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#ifndef MACIO_H
|
||||
#define MACIO_H
|
||||
|
||||
#include <devices/common/ata/idechannel.h>
|
||||
#include <devices/common/dbdma.h>
|
||||
#include <devices/common/mmiodevice.h>
|
||||
#include <devices/common/nvram.h>
|
||||
@ -271,8 +272,10 @@ private:
|
||||
|
||||
NVram* nvram; // NVRAM
|
||||
ViaCuda* viacuda; // VIA cell with Cuda MCU attached to it
|
||||
MESHController* mesh; // MESH SCSI cell instance
|
||||
MeshController* mesh; // MESH SCSI cell instance
|
||||
EsccController* escc; // ESCC serial controller
|
||||
IdeChannel* ide_0; // Internal ATA
|
||||
IdeChannel* ide_1; // Media Bay ATA
|
||||
Swim3::Swim3Ctrl* swim3; // floppy disk controller
|
||||
|
||||
// DMA channels
|
||||
|
@ -79,6 +79,9 @@ static const map<string, string> PropHelp = {
|
||||
{"fdd_img", "specifies path to floppy disk image"},
|
||||
{"fdd_fmt", "specifies floppy disk format"},
|
||||
{"fdd_wr_prot", "toggles floppy disk's write protection"},
|
||||
{"hdd_img", "specifies path to hard disk image"},
|
||||
{"hdd_wr_prot", "toggles hard disk's write protection"},
|
||||
{"cdr_img", "specifies path to CD-ROM image"},
|
||||
{"mon_id", "specifies which monitor to emulate"},
|
||||
{"pci_A1", "insert a PCI device into A1 slot"},
|
||||
{"pci_B1", "insert a PCI device into B1 slot"},
|
||||
|
@ -28,12 +28,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#include <devices/common/i2c/athens.h>
|
||||
#include <devices/common/i2c/i2cprom.h>
|
||||
#include <devices/common/machineid.h>
|
||||
#include <devices/floppy/floppyimg.h>
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
#include <devices/ioctrl/macio.h>
|
||||
#include <devices/memctrl/mpc106.h>
|
||||
#include <devices/memctrl/spdram.h>
|
||||
#include <devices/sound/soundserver.h>
|
||||
#include <devices/video/atirage.h>
|
||||
#include <loguru.hpp>
|
||||
#include <machines/machinebase.h>
|
||||
#include <machines/machinefactory.h>
|
||||
@ -128,6 +126,8 @@ int initialize_gossamer(std::string& id)
|
||||
grackle_obj->pci_register_device(
|
||||
DEV_FUN(0x12,0), dynamic_cast<PCIDevice*>(gMachineObj->get_comp_by_name(id == "pmg3twr" ? "AtiRagePro" : "AtiRageGT")));
|
||||
|
||||
gMachineObj->add_device("SCSI0", std::unique_ptr<ScsiBus>(new ScsiBus()));
|
||||
|
||||
// add Athens clock generator device and register it with the I2C host
|
||||
gMachineObj->add_device("Athens", std::unique_ptr<AthensClocks>(new AthensClocks(0x28)));
|
||||
I2CBus* i2c_bus = dynamic_cast<I2CBus*>(gMachineObj->get_comp_by_type(HWCompType::I2C_HOST));
|
||||
|
@ -27,6 +27,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#include <cpu/ppc/ppcemu.h>
|
||||
#include <devices/common/machineid.h>
|
||||
#include <devices/common/scsi/scsi.h>
|
||||
#include <devices/common/scsi/scsicdrom.h>
|
||||
#include <devices/common/scsi/scsihd.h>
|
||||
#include <devices/memctrl/hmc.h>
|
||||
#include <loguru.hpp>
|
||||
#include <machines/machinebase.h>
|
||||
@ -82,6 +84,25 @@ int initialize_pdm(std::string& id)
|
||||
|
||||
// add internal SCSI bus
|
||||
gMachineObj->add_device("SCSI0", std::unique_ptr<ScsiBus>(new ScsiBus()));
|
||||
auto scsi_bus = dynamic_cast<ScsiBus*>(gMachineObj->get_comp_by_name("SCSI0"));
|
||||
|
||||
std::string hd_image_path = GET_STR_PROP("hdd_img");
|
||||
if (!hd_image_path.empty()) {
|
||||
// attach SCSI HD to the main bus, ID #0
|
||||
auto my_hd = dynamic_cast<ScsiHardDisk*>(gMachineObj->get_comp_by_name("ScsiHD"));
|
||||
scsi_bus->register_device(0, my_hd);
|
||||
// insert specified disk image
|
||||
my_hd->insert_image(hd_image_path);
|
||||
}
|
||||
|
||||
std::string cdr_image_path = GET_STR_PROP("cdr_img");
|
||||
if (!cdr_image_path.empty()) {
|
||||
// attach SCSI CD-ROM to the main bus, ID #3
|
||||
auto my_cdr = dynamic_cast<ScsiCdrom*>(gMachineObj->get_comp_by_name("ScsiCdrom"));
|
||||
scsi_bus->register_device(3, my_cdr);
|
||||
// insert specified disk image
|
||||
my_cdr->insert_image(cdr_image_path);
|
||||
}
|
||||
|
||||
// Init virtual CPU and request MPC601
|
||||
ppc_cpu_init(hmc_obj, PPC_VER::MPC601, 7812500ULL);
|
||||
@ -109,7 +130,7 @@ static const PropMap pm6100_settings = {
|
||||
};
|
||||
|
||||
static vector<string> pm6100_devices = {
|
||||
"HMC", "Amic"
|
||||
"HMC", "Amic", "ScsiHD", "ScsiCdrom"
|
||||
};
|
||||
|
||||
static const MachineDescription pm6100_descriptor = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user