Merge 'hard-disks' branch.

This commit is contained in:
Maxim Poliakovski 2023-04-17 01:19:17 +02:00
commit cf0d361918
31 changed files with 2746 additions and 179 deletions

View File

@ -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": []
}
]
}

View File

@ -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;

View File

@ -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);

View File

@ -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"

View File

@ -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;
}

View 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);
}
}

View 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

View 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

View 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;
}

View 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

View 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);

View 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

View File

@ -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 */

View File

@ -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);

View File

@ -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

View File

@ -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();
}
}
}
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View 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);

View 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

View 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;
}

View 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);

View 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

View File

@ -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"
};

View File

@ -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;

View File

@ -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, {}

View File

@ -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

View File

@ -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"},

View File

@ -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));

View File

@ -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 = {