1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Merge pull request #1085 from TomHarte/AppleIISCSI

Support SCSI drives on the Apple II
This commit is contained in:
Thomas Harte 2022-09-15 16:52:45 -04:00 committed by GitHub
commit 38e85a340a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1061 additions and 392 deletions

View File

@ -13,8 +13,17 @@ Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &
auto target = std::make_unique<Target>();
target->media = media;
if(!target->media.disks.empty())
// If any disks are present, attach a Disk II.
if(!target->media.disks.empty()) {
target->disk_controller = Target::DiskController::SixteenSector;
}
// The emulated SCSI card requires a IIe, so upgrade to that if
// any mass storage is present.
if(!target->media.mass_storage_devices.empty()) {
target->model = Target::Model::EnhancedIIe;
target->scsi_controller = Target::SCSIController::AppleSCSI;
}
TargetList targets;
targets.push_back(std::move(target));

View File

@ -29,16 +29,24 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
SixteenSector,
ThirteenSector
);
ReflectableEnum(SCSIController,
None,
AppleSCSI
);
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
SCSIController scsi_controller = SCSIController::None;
Target() : Analyser::Static::Target(Machine::AppleII) {
if(needs_declare()) {
DeclareField(model);
DeclareField(disk_controller);
DeclareField(scsi_controller);
AnnounceEnum(Model);
AnnounceEnum(DiskController);
AnnounceEnum(SCSIController);
}
}
};

View File

@ -80,6 +80,8 @@
// Target Platform Types
#include "../../Storage/TargetPlatforms.hpp"
template<class> inline constexpr bool always_false_v = false;
using namespace Analyser::Static;
namespace {
@ -123,7 +125,21 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
if(extension == "2mg") {
// 2MG uses a factory method; defer to it.
try {
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
const auto media = Storage::Disk::Disk2MG::open(file_name);
std::visit([&result, &potential_platforms](auto &&arg) {
using Type = typename std::decay<decltype(arg)>::type;
if constexpr (std::is_same<Type, nullptr_t>::value) {
// It's valid for no media to be returned.
} else if constexpr (std::is_same<Type, Storage::Disk::DiskImageHolderBase *>::value) {
InsertInstance(result.disks, arg, TargetPlatform::DiskII);
} else if constexpr (std::is_same<Type, Storage::MassStorage::MassStorageDevice *>::value) {
// TODO: or is it Apple IIgs?
InsertInstance(result.mass_storage_devices, arg, TargetPlatform::AppleII);
} else {
static_assert(always_false_v<Type>, "Unexpected type encountered.");
}
}, media);
} catch(...) {}
}

View File

@ -8,8 +8,18 @@
#include "ncr5380.hpp"
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[5380] "
#include "../../Outputs/Log.hpp"
// TODO:
//
// end_of_dma_ should be set if: /EOP && /DACK && (/RD || /WR); for at least 100ns.
using namespace NCR::NCR5380;
using SCSI::Line;
@ -28,20 +38,16 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
void NCR5380::write(int address, uint8_t value, bool) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
data_bus_ = value;
LOG("[0] Set current SCSI bus state to " << PADHEX(2) << int(value));
data_bus_ = value;
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
// printf("w %02x\n", value);
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
dma_acknowledge(value);
}
break;
case 1: {
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
LOG("[1] Initiator command register set: " << PADHEX(2) << int(value));
initiator_command_ = value;
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
@ -57,7 +63,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
} break;
case 2:
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
LOG("[2] Set mode: " << PADHEX(2) << int(value));
mode_ = value;
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
@ -69,6 +75,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
// bit 1: 1 = use DMA mode
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
arbitration_in_progress_ = false;
phase_mismatch_ = false;
switch(mode_ & 0x3) {
case 0x0:
bus_output_ &= ~SCSI::Line::Busy;
@ -88,31 +95,36 @@ void NCR5380::write(int address, uint8_t value, bool) {
bus_.update_observers();
break;
}
// "[The End of DMA Transfer] bit is reset when the DMA MODE bit
// is reset (0) in the Mode Register".
end_of_dma_ &= bool(value & 0x2);
update_control_output();
break;
case 3: {
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
LOG("[3] Set target command: " << PADHEX(2) << int(value));
target_command_ = value;
update_control_output();
} break;
case 4:
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
LOG("[4] Set select enabled: " << PADHEX(2) << int(value));
break;
case 5:
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
LOG("[5] Start DMA send: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::Send;
break;
case 6:
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
LOG("[6] Start DMA target receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::TargetReceive;
break;
case 7:
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
LOG("[7] Start DMA initiator receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::InitiatorReceive;
break;
}
@ -136,18 +148,15 @@ void NCR5380::write(int address, uint8_t value, bool) {
uint8_t NCR5380::read(int address, bool) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
LOG("[0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
return dma_acknowledge();
}
return uint8_t(bus_.get_state());
case 1:
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
LOG("[1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
return
// Bits repeated as they were set.
(initiator_command_ & ~0x60) |
@ -159,11 +168,11 @@ uint8_t NCR5380::read(int address, bool) {
(lost_arbitration_ ? 0x20 : 0x00);
case 2:
// LOG("[SCSI 2] Get mode");
LOG("[2] Get mode");
return mode_;
case 3:
// LOG("[SCSI 3] Get target command");
LOG("[3] Get target command");
return target_command_;
case 4: {
@ -177,41 +186,38 @@ uint8_t NCR5380::read(int address, bool) {
((bus_state & Line::Input) ? 0x04 : 0x00) |
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
((bus_state & Line::Parity) ? 0x01 : 0x00);
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
LOG("[4] Get current bus state: " << PADHEX(2) << int(result));
return result;
}
case 5: {
const auto bus_state = bus_.get_state();
const bool phase_matches =
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
const uint8_t result =
/* b7 = end of DMA */
(end_of_dma_ ? 0x80 : 0x00) |
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
/* b5 = parity error */
/* b4 = IRQ active */
(phase_matches ? 0x08 : 0x00) |
(irq_ ? 0x10 : 0x00) |
(phase_matches() ? 0x08 : 0x00) |
/* b2 = busy error */
((bus_state & Line::Attention) ? 0x02 : 0x00) |
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
LOG("[5] Get bus and status: " << PADHEX(2) << int(result));
return result;
}
case 6:
// LOG("[SCSI 6] Get input data");
LOG("[6] Get input data");
return 0xff;
case 7:
// LOG("[SCSI 7] Reset parity/interrupt");
LOG("[7] Reset parity/interrupt");
irq_ = false;
return 0xff;
}
return 0;
}
SCSI::BusState NCR5380::target_output() {
SCSI::BusState NCR5380::target_output() const {
SCSI::BusState output = SCSI::DefaultBusState;
if(target_command_ & 0x08) output |= Line::Request;
if(target_command_ & 0x04) output |= Line::Message;
@ -236,6 +242,17 @@ void NCR5380::update_control_output() {
}
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
/*
When connected as an Initiator with DMA Mode True,
if the phase lines I//O, C//D, and /MSG do not match the
phase bits in the Target Command Register, a phase mismatch
interrupt is generated when /REQ goes active.
*/
if((mode_ & 0x42) == 0x02 && new_state & SCSI::Line::Request && !phase_matches()) {
irq_ = true;
phase_mismatch_ = true;
}
switch(state_) {
default: break;
@ -296,7 +313,13 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
dma_request_ = false;
break;
case SCSI::Line::Request:
dma_request_ = true;
// Don't issue a new DMA request if a phase mismatch has
// been detected and this is an intiator receiving.
// This is a bit of reading between the lines.
// (i.e. guesswork, partly)
dma_request_ =
!phase_mismatch_ ||
(dma_operation_ != DMAOperation::InitiatorReceive);
break;
case SCSI::Line::Request | SCSI::Line::Acknowledge:
dma_request_ = false;
@ -316,3 +339,38 @@ void NCR5380::set_execution_state(ExecutionState state) {
state_ = state;
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
}
size_t NCR5380::scsi_id() {
return device_id_;
}
bool NCR5380::dma_request() {
return dma_request_;
}
uint8_t NCR5380::dma_acknowledge() {
const uint8_t bus_state = uint8_t(bus_.get_state());
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
return bus_state;
}
void NCR5380::dma_acknowledge(uint8_t value) {
data_bus_ = value;
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
bool NCR5380::phase_matches() const {
const auto bus_state = bus_.get_state();
return
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
}

View File

@ -30,6 +30,18 @@ class NCR5380 final: public SCSI::Bus::Observer {
/*! Reads from @c address. */
uint8_t read(int address, bool dma_acknowledge = false);
/*! @returns The SCSI ID assigned to this device. */
size_t scsi_id();
/*! @return @c true if DMA request is active; @c false otherwise. */
bool dma_request();
/*! Signals DMA acknowledge with a simultaneous read. */
uint8_t dma_acknowledge();
/*! Signals DMA acknowledge with a simultaneous write. */
void dma_acknowledge(uint8_t);
private:
SCSI::Bus &bus_;
@ -46,6 +58,10 @@ class NCR5380 final: public SCSI::Bus::Observer {
bool assert_data_bus_ = false;
bool dma_request_ = false;
bool dma_acknowledge_ = false;
bool end_of_dma_ = false;
bool irq_ = false;
bool phase_mismatch_ = false;
enum class ExecutionState {
None,
@ -63,10 +79,11 @@ class NCR5380 final: public SCSI::Bus::Observer {
void set_execution_state(ExecutionState state);
SCSI::BusState target_output();
SCSI::BusState target_output() const;
void update_control_output();
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
bool phase_matches() const;
};
}

View File

@ -24,16 +24,28 @@
#include "DiskIICard.hpp"
#include "Joystick.hpp"
#include "LanguageCardSwitches.hpp"
#include "SCSICard.hpp"
#include "Video.hpp"
#include "../../../Analyser/Static/AppleII/Target.hpp"
#include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Configurable/StandardOptions.hpp"
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
#include <algorithm>
#include <array>
#include <memory>
namespace {
constexpr int DiskIISlot = 6; // Apple recommended slot 6 for the (first) Disk II.
constexpr int SCSISlot = 7; // Install the SCSI card in slot 7, to one-up any connected Disk II.
}
namespace Apple {
namespace II {
@ -101,7 +113,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
Cycles cycles_since_audio_update_;
// MARK: - Cards
std::array<std::unique_ptr<Apple::II::Card>, 7> cards_;
static constexpr size_t NoActiveCard = 7; // There is no 'card 0' in internal numbering.
size_t active_card_ = NoActiveCard;
std::array<std::unique_ptr<Apple::II::Card>, 8> cards_; // The final slot is a sentinel for 'no active card'.
Cycles cycles_since_card_update_;
std::vector<Apple::II::Card *> every_cycle_cards_;
std::vector<Apple::II::Card *> just_in_time_cards_;
@ -141,7 +156,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
Apple::II::DiskIICard *diskii_card() {
return dynamic_cast<Apple::II::DiskIICard *>(cards_[5].get());
return dynamic_cast<Apple::II::DiskIICard *>(cards_[DiskIISlot - 1].get());
}
Apple::II::SCSICard *scsi_card() {
return dynamic_cast<Apple::II::SCSICard *>(cards_[SCSISlot - 1].get());
}
// MARK: - Memory Map.
@ -460,10 +479,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
const bool has_disk_controller = target.disk_controller != Target::DiskController::None;
const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector;
if(has_disk_controller) {
// Apple recommended slot 6 for the (first) Disk II.
request = request && DiskIICard::rom_request(is_sixteen_sector);
}
// Add a SCSI card if requested.
const bool has_scsi_card = target.scsi_controller == Target::SCSIController::AppleSCSI;
if(has_scsi_card) {
request = request && SCSICard::rom_request();
}
// Request, validate and install ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
@ -471,7 +495,13 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
if(has_disk_controller) {
install_card(6, new Apple::II::DiskIICard(roms, is_sixteen_sector));
install_card(DiskIISlot, new Apple::II::DiskIICard(roms, is_sixteen_sector));
}
if(has_scsi_card) {
// Rounding the clock rate slightly shouldn't matter, but:
// TODO: be [slightly] more honest about clock rate.
install_card(SCSISlot, new Apple::II::SCSICard(roms, int(master_clock / 14.0f)));
}
rom_ = std::move(roms.find(system)->second);
@ -727,18 +757,32 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
Communication with cards follows.
*/
if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) {
if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xd000) {
// If this is a card access, figure out which card is at play before determining
// the totality of who needs messaging.
size_t card_number = 0;
Apple::II::Card::Select select = Apple::II::Card::None;
if(address >= 0xc100) {
if(address >= 0xc800) {
/*
Decode the 2kb area used for additional ROMs.
This is shared by all cards.
*/
card_number = active_card_;
select = Apple::II::Card::C8Region;
// An access to $cfff will disable the active card.
if(address == 0xcfff) {
active_card_ = NoActiveCard;
}
} else if(address >= 0xc100) {
/*
Decode the area conventionally used by cards for ROMs:
0xCn00 to 0xCnff: card n.
This also sets the active card for the C8 region.
*/
card_number = (address - 0xc100) >> 8;
active_card_ = card_number = (address - 0xc100) >> 8;
select = Apple::II::Card::Device;
} else {
/*
@ -863,6 +907,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
auto diskii = diskii_card();
if(diskii) diskii->set_disk(media.disks[0], 0);
}
if(!media.mass_storage_devices.empty()) {
auto scsi = scsi_card();
if(scsi) scsi->set_storage_device(media.mass_storage_devices[0]);
}
return true;
}

View File

@ -42,9 +42,13 @@ class Card {
public:
virtual ~Card() {}
enum Select: int {
None = 0, // No select line is active
IO = 1 << 0, // IO select is active
Device = 1 << 1, // Device select is active
None = 0, // No select line is active.
IO = 1 << 0, // IO select is active; i.e. access is in range $C0x0 to $C0xf.
Device = 1 << 1, // Device select is active; i.e. access is in range $Cx00 to $Cxff.
C8Region = 1 << 2, // Access is to the region $c800 to $cfff, was preceded by at least
// one Device access to this card, and has not yet been followed up
// by an access to $cfff.
};
/*!
@ -54,7 +58,7 @@ class Card {
no constraints, that want to be informed of every machine cycle, will receive
a call to perform_bus_operation every cycle and should use that for time keeping.
*/
virtual void run_for([[maybe_unused]] Cycles half_cycles, [[maybe_unused]] int stretches) {}
virtual void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) {}
/// Requests a flush of any pending audio or video output.
virtual void flush() {}

View File

@ -0,0 +1,169 @@
//
// SCSICard.cpp
// Clock Signal
//
// Created by Thomas Harte on 22/08/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
// Per the documentation around the GGLabs Apple II SCSI card clone:
//
// A 5380 is mapped to the first eight bytes of slot IO:
//
// $c0x0 R current SCSI data register
// $c0x0 W output data register
// $c0x1 R/W initiator command register
// $c0x2 R/W mode select register
// $c0x3 R/W target command register
// $c0x4 R SCSI bus status
// $c0x4 W select enable register
// $c0x5 R bus and status register
// $c0x6 R input data register
// $c0x7 R reset parity and interrupts
// (i.e. the 5380's standard registers in their usual order)
//
// The remaining eight are used for control functions:
//
// $c0x8 R/W PDMA/DACK
// $c0x9 R SCSI device ID
// $c0xa W memory bank select register
// $c0xb W reset 5380 SCSI chip
// $c0xc - [unused]
// $c0xd W PDMA mode enable
// $c0xe R read DRQ status through bit 7
// $c0xf - [unused]
//
// Further, per that card's schematic:
//
// BANK REGISTER: bit 0..3 ROM Addr, 4..6 RAM Addr, 7 RSVD
//
// Which relates to the description:
//
// The card is also equipped with 16K of ROM and 8K of RAM.
// These are mapped in the $C800-$CFFF card memory using a banking
// scheme. The $C0xA bank register selects the which bank of RAM
// and ROM are mapped. RAM is always at $C800-$CBFF and ROM is
// at $CC00-$CFFF. The boot code in the first 256 bytes of ROM
// bank 0 is also mapped in the IOSEL space ($Cn00-$CnFF).
//
#include "SCSICard.hpp"
#include <cstring>
using namespace Apple::II;
ROM::Request SCSICard::rom_request() {
return ROM::Request(ROM::Name::AppleIISCSICard);
}
// TODO: accept and supply real clock rate.
SCSICard::SCSICard(ROM::Map &map, int clock_rate) :
scsi_bus_(clock_rate),
ncr5380_(scsi_bus_, clock_rate),
storage_(scsi_bus_, 6)
{
// Grab a copy of the SCSI ROM.
const auto rom = map.find(ROM::Name::AppleIISCSICard);
if(rom == map.end()) {
throw ROMMachine::Error::MissingROMs;
}
memcpy(rom_.data(), rom->second.data(), rom_.size());
// Set up initial banking.
rom_pointer_ = rom_.data();
ram_pointer_ = ram_.data();
}
void SCSICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) {
switch(select) {
default: break;
case Select::Device:
if(is_read) {
*value = rom_[address & 255];
}
break;
case Select::IO:
address &= 0xf;
switch(address) {
case 0x0: case 0x1: case 0x2: case 0x3:
case 0x4: case 0x5: case 0x6: case 0x7:
if(is_read) {
*value = ncr5380_.read(address);
} else {
ncr5380_.write(address, *value);
}
break;
case 0x9:
if(is_read) {
*value = uint8_t(ncr5380_.scsi_id());
}
break;
case 0x8:
// DMA acknowledge.
if(is_read) {
*value = ncr5380_.dma_acknowledge();
} else {
ncr5380_.dma_acknowledge(*value);
}
break;
case 0xa:
// RAM and ROM select.
if(!is_read) {
const auto rom_base = size_t((*value & 0x0f) << 10);
const auto ram_base = size_t((*value & 0x70) << 6);
rom_pointer_ = &rom_[rom_base];
ram_pointer_ = &ram_[ram_base];
}
break;
case 0xb:
if(!is_read) {
printf("TODO: NCR reset\n");
}
break;
case 0xd:
if(!is_read) {
printf("TODO: Enable PDMA\n");
}
break;
case 0xe:
// DRQ in b7.
if(is_read) {
*value = ncr5380_.dma_request() ? 0x80 : 0x00;
}
break;
default:
printf("Unhandled: %04x %c %02x\n", address, is_read ? 'r' : 'w', *value);
break;
}
break;
case Select::C8Region:
if(address & 0x400) {
if(is_read) {
*value = rom_pointer_[address & 0x3ff];
}
} else {
if(is_read) {
*value = ram_pointer_[address & 0x3ff];
} else {
ram_pointer_[address & 0x3ff] = *value;
}
}
break;
}
}
void SCSICard::set_storage_device(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device) {
storage_->set_storage(device);
}

View File

@ -0,0 +1,55 @@
//
// SCSICard.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/08/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef SCSICard_hpp
#define SCSICard_hpp
#include "Card.hpp"
#include "../../ROMMachine.hpp"
#include "../../../Components/5380/ncr5380.hpp"
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
#include "../../../Storage/MassStorage/MassStorageDevice.hpp"
#include <array>
#include <memory>
namespace Apple {
namespace II {
class SCSICard: public Card {
public:
static ROM::Request rom_request();
SCSICard(ROM::Map &, int clock_rate);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void set_storage_device(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device);
void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) final {
scsi_bus_.run_for(cycles);
}
private:
uint8_t *ram_pointer_ = nullptr;
uint8_t *rom_pointer_ = nullptr;
std::array<uint8_t, 8*1024> ram_;
std::array<uint8_t, 16*1024> rom_;
SCSI::Bus scsi_bus_;
NCR::NCR5380::NCR5380 ncr5380_;
SCSI::Target::Target<SCSI::DirectAccessDevice> storage_;
};
}
}
#endif /* SCSICard_hpp */

View File

@ -420,6 +420,9 @@ Description::Description(Name name) {
case Name::AppleIIEnhancedECharacter:
*this = Description(name, "AppleII", "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014du);
break;
case Name::AppleIISCSICard:
*this = Description(name, "AppleII", "the Apple II SCSI card ROM", "scsi.rom", 16*1024, 0x5aff85d3u);
break;
case Name::AppleIIgsROM00: /* TODO */
case Name::AppleIIgsROM01: *this = Description(name, "AppleIIgs", "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0u); break;

View File

@ -58,6 +58,7 @@ enum Name {
AppleIIeCharacter,
AppleIIEnhancedE,
AppleIIEnhancedECharacter,
AppleIISCSICard,
// Apple IIgs.
AppleIIgsROM00,

View File

@ -254,6 +254,10 @@
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; };
4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; };
4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; };
4B4C81C528B3C5CD00F84AE9 /* SCSICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4C81C328B3C5CD00F84AE9 /* SCSICard.cpp */; };
4B4C81C628B3C5CD00F84AE9 /* SCSICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4C81C328B3C5CD00F84AE9 /* SCSICard.cpp */; };
4B4C81CA28B56CF800F84AE9 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4C81C928B56CF800F84AE9 /* MacintoshVolume.cpp */; };
4B4C81CB28B56CF800F84AE9 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4C81C928B56CF800F84AE9 /* MacintoshVolume.cpp */; };
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; };
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; };
4B4DEC06252BFA56004583AC /* 65816Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DEC05252BFA56004583AC /* 65816Base.cpp */; };
@ -312,8 +316,6 @@
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF802312FA9C00500CE8 /* HFV.cpp */; };
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF802312FA9C00500CE8 /* HFV.cpp */; };
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; };
4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; };
4B75F979280D7C5100121055 /* 68000DecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B75F978280D7C5100121055 /* 68000DecoderTests.mm */; };
4B75F97B280D7C7700121055 /* 68000 Decoding in Resources */ = {isa = PBXBuildFile; fileRef = 4B75F97A280D7C7700121055 /* 68000 Decoding */; };
4B7752A628217DF80073E2C5 /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; };
@ -408,7 +410,6 @@
4B778F2F23A5F0B10000D260 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; };
4B778F3023A5F0C50000D260 /* Macintosh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE0058227CFFCA000CA200 /* Macintosh.cpp */; };
4B778F3123A5F0CB0000D260 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B65085F22F4CF8D009C1100 /* Keyboard.cpp */; };
4B778F3223A5F0EE0000D260 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; };
4B778F3323A5F0FB0000D260 /* MassStorageDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */; };
4B778F3423A5F1040000D260 /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
4B778F3523A5F1040000D260 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; };
@ -1360,6 +1361,12 @@
4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KonamiSCC.cpp; sourceTree = "<group>"; };
4B4B1A3B200198C900A0F866 /* KonamiSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = KonamiSCC.hpp; sourceTree = "<group>"; };
4B4C81C228B0288B00F84AE9 /* BlitterSequencer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BlitterSequencer.hpp; sourceTree = "<group>"; };
4B4C81C328B3C5CD00F84AE9 /* SCSICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SCSICard.cpp; sourceTree = "<group>"; };
4B4C81C428B3C5CD00F84AE9 /* SCSICard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SCSICard.hpp; sourceTree = "<group>"; };
4B4C81C828B56CF800F84AE9 /* MacintoshVolume.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MacintoshVolume.hpp; sourceTree = "<group>"; };
4B4C81C928B56CF800F84AE9 /* MacintoshVolume.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MacintoshVolume.cpp; sourceTree = "<group>"; };
4B4C81CC28B56DD400F84AE9 /* ApplePartitionMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ApplePartitionMap.hpp; sourceTree = "<group>"; };
4B4C81CD28B67FCD00F84AE9 /* AppleIIVolume.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleIIVolume.hpp; sourceTree = "<group>"; };
4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = "<group>"; };
4B4DC8201D2C2425003C5BF8 /* Vic20.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Vic20.hpp; sourceTree = "<group>"; };
4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = "<group>"; };
@ -1453,8 +1460,6 @@
4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; };
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = HFV.hpp; sourceTree = "<group>"; };
4B74CF802312FA9C00500CE8 /* HFV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HFV.cpp; sourceTree = "<group>"; };
4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MacintoshVolume.cpp; path = Encodings/MacintoshVolume.cpp; sourceTree = "<group>"; };
4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MacintoshVolume.hpp; path = Encodings/MacintoshVolume.hpp; sourceTree = "<group>"; };
4B75F978280D7C5100121055 /* 68000DecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000DecoderTests.mm; sourceTree = "<group>"; };
4B75F97A280D7C7700121055 /* 68000 Decoding */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Decoding"; sourceTree = "<group>"; };
4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Z80.hpp; sourceTree = "<group>"; };
@ -2917,6 +2922,17 @@
path = KonamiSCC;
sourceTree = "<group>";
};
4B4C81C728B56CF800F84AE9 /* Encodings */ = {
isa = PBXGroup;
children = (
4B4C81C928B56CF800F84AE9 /* MacintoshVolume.cpp */,
4B4C81CC28B56DD400F84AE9 /* ApplePartitionMap.hpp */,
4B4C81C828B56CF800F84AE9 /* MacintoshVolume.hpp */,
4B4C81CD28B67FCD00F84AE9 /* AppleIIVolume.hpp */,
);
path = Encodings;
sourceTree = "<group>";
};
4B4DC81D1D2C2425003C5BF8 /* Commodore */ = {
isa = PBXGroup;
children = (
@ -3145,10 +3161,9 @@
4B6AAEA1230E3E1D0078E864 /* MassStorage */ = {
isa = PBXGroup;
children = (
4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */,
4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */,
4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */,
4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */,
4B4C81C728B56CF800F84AE9 /* Encodings */,
4B74CF7E2312FA9C00500CE8 /* Formats */,
4B6AAEA5230E40250078E864 /* SCSI */,
);
@ -4617,6 +4632,7 @@
4BCE0050227CE8CA000CA200 /* AppleII.cpp */,
4BCE004E227CE8CA000CA200 /* DiskIICard.cpp */,
4B2E86E025DC95150024F1E9 /* Joystick.cpp */,
4B4C81C328B3C5CD00F84AE9 /* SCSICard.cpp */,
4BCE004D227CE8CA000CA200 /* Video.cpp */,
4BCE004A227CE8CA000CA200 /* AppleII.hpp */,
4BF40A5A254263140033EA39 /* AuxiliaryMemorySwitches.hpp */,
@ -4625,6 +4641,7 @@
4B2E86E125DC95150024F1E9 /* Joystick.hpp */,
4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */,
4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */,
4B4C81C428B3C5CD00F84AE9 /* SCSICard.hpp */,
4BCE004F227CE8CA000CA200 /* Video.hpp */,
4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */,
);
@ -5581,6 +5598,7 @@
4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
4B89451D201967B4007DE474 /* Disk.cpp in Sources */,
4BFEA2F02682A7B900EBF94C /* Dave.cpp in Sources */,
4B4C81C628B3C5CD00F84AE9 /* SCSICard.cpp in Sources */,
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */,
4B7962A22819681F008130F9 /* Decoder.cpp in Sources */,
@ -5629,6 +5647,7 @@
4B2E86B825D7490E0024F1E9 /* ReactiveDevice.cpp in Sources */,
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */,
4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */,
4B4C81CB28B56CF800F84AE9 /* MacintoshVolume.cpp in Sources */,
4B1B58F7246CC4E8009C171E /* State.cpp in Sources */,
4B0ACC03237756F6008902D0 /* Line.cpp in Sources */,
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
@ -5680,7 +5699,6 @@
4BC6236F26F426B400F83DFE /* FAT.cpp in Sources */,
4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */,
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
4B0ACC3323775819008902D0 /* Atari2600.cpp in Sources */,
4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */,
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
@ -5757,6 +5775,7 @@
4B8DF505254E3C9D00F3433C /* ADB.cpp in Sources */,
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */,
4B0ACC2623775819008902D0 /* AtariST.cpp in Sources */,
4B4C81C528B3C5CD00F84AE9 /* SCSICard.cpp in Sources */,
4B894530201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */,
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
@ -5829,7 +5848,6 @@
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
4B0ACC2E23775819008902D0 /* TIA.cpp in Sources */,
4B2E86B725D7490E0024F1E9 /* ReactiveDevice.cpp in Sources */,
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
4B0F1C232605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */,
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
4B7C681A275196E8001671EC /* MouseJoystick.cpp in Sources */,
@ -5906,6 +5924,7 @@
4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */,
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
4B2E86CF25D8D8C70024F1E9 /* Keyboard.cpp in Sources */,
4B4C81CA28B56CF800F84AE9 /* MacintoshVolume.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
4B7C681627517A59001671EC /* Sprites.cpp in Sources */,
4B0ACC02237756ED008902D0 /* Line.cpp in Sources */,
@ -6171,7 +6190,6 @@
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */,
4BEE1EC022B5E236000A26A6 /* MacGCRTests.mm in Sources */,
4B778F0623A5EC150000D260 /* CAS.cpp in Sources */,
4B778F3223A5F0EE0000D260 /* MacintoshVolume.cpp in Sources */,
4B778F2B23A5EF0F0000D260 /* Commodore.cpp in Sources */,
4B778F3F23A5F1890000D260 /* MacintoshDoubleDensityDrive.cpp in Sources */,
4B778F1623A5ECA00000D260 /* Z80AllRAM.cpp in Sources */,

View File

@ -11,4 +11,6 @@ apple2-character.rom — a 2kb image of the Apple IIe's character ROM.
apple2eu-character.rom — a 4kb image of the Unenhanced IIe's character ROM.
apple2e-character.rom — a 4kb image of the Enhanced IIe's character ROM.
scsi.rom — a 16kb image of the Apple II SCSI card ROM.
Apologies for the wackiness around "at least xkb big", it's to allow for use of files such as those on ftp.apple.asimov.net, which tend to be a bunch of other things, then the system ROM.

View File

@ -9,12 +9,83 @@
#include "2MG.hpp"
#include "MacintoshIMG.hpp"
#include "../../../MassStorage/Encodings/AppleIIVolume.hpp"
#include <cstring>
using namespace Storage::Disk;
DiskImageHolderBase *Disk2MG::open(const std::string &file_name) {
namespace {
// TODO: I've boxed myself into a corner on this stuff by not using factories more generally;
// volume to device mappers are not themselves mass storage devices because then their use
// can't currently be private to file types and relevant knowledge would need to be pushed up into
// the static analyser.
//
// So, I guess: go factory, pervasively. And probably stop the strict disk/mass storage/tape
// distinction, given that clearly some platforms just capture volumes abstractly from media.
class MassStorage2MG: public Storage::MassStorage::MassStorageDevice {
public:
MassStorage2MG(const std::string &file_name, long start, long size):
file_(file_name),
file_start_(start),
image_size_(size)
{
mapper_.set_drive_type(
Storage::MassStorage::Encodings::Apple::DriveType::SCSI,
size_t(size / 512)
);
}
private:
Storage::FileHolder file_;
long file_start_, image_size_;
Storage::MassStorage::Encodings::AppleII::Mapper mapper_;
/* MassStorageDevices overrides. */
size_t get_block_size() final {
return 512;
}
size_t get_number_of_blocks() final {
return mapper_.get_number_of_blocks();
}
std::vector<uint8_t> get_block(size_t address) final {
const auto source_address = mapper_.to_source_address(address);
const auto file_offset = offset_for_block(source_address);
if(source_address >= 0) {
file_.seek(file_offset, SEEK_SET);
return mapper_.convert_source_block(source_address, file_.read(get_block_size()));
} else {
return mapper_.convert_source_block(source_address);
}
}
void set_block(size_t address, const std::vector<uint8_t> &data) final {
const auto source_address = mapper_.to_source_address(address);
const auto file_offset = offset_for_block(source_address);
if(source_address >= 0 && file_offset >= 0) {
file_.seek(file_offset, SEEK_SET);
file_.write(data);
}
}
/// @returns -1 if @c address is out of range; the offset into the file at which
/// the block for @c address resides otherwise.
long offset_for_block(ssize_t address) {
if(address < 0) return -1;
const long offset = 512 * address;
if(offset > image_size_ - 512) return -1;
return file_start_ + offset;
}
};
}
Disk2MG::DiskOrMassStorageDevice Disk2MG::open(const std::string &file_name) {
FileHolder file(file_name);
// Check the signature.
@ -65,12 +136,16 @@ DiskImageHolderBase *Disk2MG::open(const std::string &file_name) {
// TODO: DOS 3.3 sector order.
break;
case 1:
// ProDOS order, which could still mean Macintosh-style or Apple II-style. Try them both.
// 'ProDOS order', which could still mean Macintosh-style (ie. not ProDOS, but whatever)
// or Apple II-style. Try them both.
try {
return new DiskImageHolder<Storage::Disk::MacintoshIMG>(file_name, MacintoshIMG::FixedType::GCR, data_start, data_size);
} catch(...) {}
// TODO: Apple II-style.
// Try a hard-disk image. For now this assumes: for an Apple IIe or GS.
return new MassStorage2MG(file_name, data_start, data_size);
break;
case 2:
// TODO: NIB data (yuck!).

View File

@ -10,8 +10,12 @@
#define _MG_hpp
#include "../DiskImage.hpp"
#include "../../../MassStorage/MassStorageDevice.hpp"
#include "../../../FileHolder.hpp"
#include <variant>
namespace Storage {
namespace Disk {
@ -26,7 +30,8 @@ namespace Disk {
class Disk2MG {
public:
static DiskImageHolderBase *open(const std::string &file_name);
using DiskOrMassStorageDevice = std::variant<nullptr_t, DiskImageHolderBase *, Storage::MassStorage::MassStorageDevice *>;
static DiskOrMassStorageDevice open(const std::string &file_name);
};
}

View File

@ -0,0 +1,38 @@
//
// AppleIIVolume.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/08/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef AppleIIVolume_h
#define AppleIIVolume_h
#include "ApplePartitionMap.hpp"
namespace Storage {
namespace MassStorage {
namespace Encodings {
namespace AppleII {
struct VolumeProvider {
static constexpr bool HasDriver = false;
const char *name() const {
return "ProDOS";
}
const char *type() const {
return "Apple_PRODOS";
}
};
using Mapper = Storage::MassStorage::Encodings::Apple::PartitionMap<VolumeProvider>;
}
}
}
}
#endif /* AppleIIVolume_h */

View File

@ -0,0 +1,257 @@
//
// ApplePartitionMap.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef ApplePartitionMap_hpp
#define ApplePartitionMap_hpp
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <sys/types.h>
#include <vector>
namespace Storage {
namespace MassStorage {
namespace Encodings {
namespace Apple {
enum class DriveType {
SCSI
};
/*!
Implements a device to volume mapping with an Apple Partition Map.
The @c VolumeProvider provides both the volume to be embedded into a device,
and device driver information if applicable.
*/
template <typename VolumeProvider> class PartitionMap {
public:
/*!
Sets the drive type to map to and the number of blocks in the underlying partition.
*/
void set_drive_type(DriveType drive_type, size_t number_of_blocks) {
drive_type_ = drive_type;
number_of_blocks_ = number_of_blocks;
}
/*!
@returns The total number of blocks on the entire volume.
*/
size_t get_number_of_blocks() const {
return
number_of_blocks_ + // Size of the volume.
size_t(non_volume_blocks()); // Size of everything else.
}
/*!
Maps from a mass-storage device address to an address
in the underlying volume.
*/
ssize_t to_source_address(size_t address) const {
// The embedded volume is always the last thing on the device.
return ssize_t(address) - ssize_t(non_volume_blocks());
}
/*!
Converts from a source data block to one properly encoded for the drive type.
Expected usage:
const size_t source_address = mapper.to_source_address(unit_address);
if(is_in_range_for_partition(source_address)) {
return mapper.convert_source_block(source_address, get_block_contents(source_address));
} else {
return mapper.convert_source_block(source_address);
}
*/
std::vector<uint8_t> convert_source_block(ssize_t source_address, std::vector<uint8_t> source_data = {}) {
// Addresses greater than or equal to zero map to the actual disk image.
if(source_address >= 0) return source_data;
// Switch to mapping relative to 0, for personal sanity.
source_address += non_volume_blocks();
// Block 0 is the device descriptor, which lists the total number of blocks,
// and provides an offset to the driver, if any.
if(!source_address) {
const uint32_t total_device_blocks = uint32_t(get_number_of_blocks());
const auto driver_size = uint16_t(driver_block_size());
const auto driver_offset = uint16_t(predriver_blocks());
const uint8_t driver_count = driver_size > 0 ? 1 : 0;
/* The driver descriptor. */
std::vector<uint8_t> driver_description = {
0x45, 0x52, /* device signature */
0x02, 0x00, /* block size, in bytes */
uint8_t(total_device_blocks >> 24),
uint8_t(total_device_blocks >> 16),
uint8_t(total_device_blocks >> 8),
uint8_t(total_device_blocks),
/* number of blocks on device */
0x00, 0x01, /* reserved (formerly: device type) */
0x00, 0x01, /* reserved (formerly: device ID) */
0x00, 0x00,
0x00, 0x00, /* reserved ('sbData', no further explanation given) */
0x00, driver_count, /* number of device descriptor entries */
0x00, 0x00,
/* first device descriptor's starting block */
uint8_t(driver_offset >> 8), uint8_t(driver_offset),
/* size of device driver */
uint8_t(driver_size >> 8), uint8_t(driver_size),
0x00, 0x01, /*
More modern documentation: operating system (MacOS = 1)
Inside Macintosh IV: system type (Mac Plus = 1)
*/
};
driver_description.resize(512);
return driver_description;
}
// Blocks 1 and 2 contain entries of the partition map; there's also possibly an entry
// for the driver.
if(source_address < 3 + volume_provider_.HasDriver) {
struct Partition {
const char *name, *type;
uint32_t start_block, size;
uint8_t status;
} partitions[3] = {
{
volume_provider_.name(),
volume_provider_.type(),
uint32_t(non_volume_blocks()),
uint32_t(number_of_blocks_),
0xb7
},
{
"Apple",
"Apple_partition_map",
0x01,
uint32_t(predriver_blocks()) - 1,
0x37
},
{
"Macintosh",
"Apple_Driver",
uint32_t(predriver_blocks()),
uint32_t(driver_block_size()),
0x7f
},
};
std::vector<uint8_t> partition(512);
// Fill in the fixed fields.
partition[0] = 'P'; partition[1] = 'M'; /* Signature. */
partition[7] = 3; /* Number of partitions. */
const Partition &details = partitions[source_address-1];
partition[8] = uint8_t(details.start_block >> 24);
partition[9] = uint8_t(details.start_block >> 16);
partition[10] = uint8_t(details.start_block >> 8);
partition[11] = uint8_t(details.start_block);
partition[84] = partition[12] = uint8_t(details.size >> 24);
partition[85] = partition[13] = uint8_t(details.size >> 16);
partition[86] = partition[14] = uint8_t(details.size >> 8);
partition[87] = partition[15] = uint8_t(details.size);
// 32 bytes are allocated for each of the following strings.
memcpy(&partition[16], details.name, strlen(details.name));
memcpy(&partition[48], details.type, strlen(details.type));
partition[91] = details.status;
// The third entry in this constructed partition map is the driver;
// add some additional details.
if constexpr (VolumeProvider::HasDriver) {
if(source_address == 3) {
const auto driver_size = uint16_t(volume_provider_.driver_size());
const auto driver_checksum = uint16_t(volume_provider_.driver_checksum());
/* Driver size in bytes. */
partition[98] = uint8_t(driver_size >> 8);
partition[99] = uint8_t(driver_size);
/* Driver checksum. */
partition[118] = uint8_t(driver_checksum >> 8);
partition[119] = uint8_t(driver_checksum);
/* Driver target processor. */
const char *driver_target = volume_provider_.driver_target();
memcpy(&partition[120], driver_target, strlen(driver_target));
// Various non-zero values that Apple HD SC Tool wrote are below; they are
// documented as reserved officially, so I don't know their meaning.
partition[137] = 0x01;
partition[138] = 0x06;
partition[143] = 0x01;
partition[147] = 0x02;
partition[149] = 0x07;
}
}
return partition;
}
if constexpr (VolumeProvider::HasDriver) {
// The remainder of the non-volume area is the driver.
if(source_address >= predriver_blocks() && source_address < non_volume_blocks()) {
const uint8_t *driver = volume_provider_.driver();
const auto offset = (source_address - predriver_blocks()) * 512;
return std::vector<uint8_t>(&driver[offset], &driver[offset + 512]);
}
}
// Default: return an empty block.
return std::vector<uint8_t>(512);
}
private:
DriveType drive_type_;
size_t number_of_blocks_;
VolumeProvider volume_provider_;
ssize_t predriver_blocks() const {
return
0x40; // Holding:
// (i) the driver descriptor;
// (ii) the partition table; and
// (iii) the partition entries.
}
ssize_t non_volume_blocks() const {
return
predriver_blocks() +
driver_block_size(); // Size of device driver (if any).
}
ssize_t driver_block_size() const {
if constexpr (VolumeProvider::HasDriver) {
return (volume_provider_.driver_size() + 511) >> 9;
} else {
return 0;
}
}
};
}
}
}
}
#endif /* ApplePartitionMap_hpp */

View File

@ -12,124 +12,16 @@
using namespace Storage::MassStorage::Encodings::Macintosh;
void Mapper::set_drive_type(DriveType drive_type, size_t number_of_blocks) {
drive_type_ = drive_type;
number_of_blocks_ = number_of_blocks;
size_t VolumeProvider::driver_size() const {
return 0x139e;
}
size_t Mapper::get_number_of_blocks() {
return number_of_blocks_ + 0x60;
uint16_t VolumeProvider::driver_checksum() const {
return 0x84b9;
}
ssize_t Mapper::to_source_address(size_t address) {
return ssize_t(address) - 0x60;
}
std::vector<uint8_t> Mapper::convert_source_block(ssize_t source_address, std::vector<uint8_t> source_data) {
// Addresses greater than or equal to zero map to the actual disk image.
if(source_address >= 0) return source_data;
// Switch to mapping relative to 0, for personal sanity.
source_address += 0x60;
// Block 0 is the device descriptor, which lists the total number of blocks,
// and provides an offset to the driver.
if(!source_address) {
uint32_t total_device_blocks = uint32_t(number_of_blocks_ + 0x60);
/* The driver descriptor. */
std::vector<uint8_t> driver_description = {
0x45, 0x52, /* device signature */
0x02, 0x00, /* block size, in bytes */
uint8_t(total_device_blocks >> 24),
uint8_t(total_device_blocks >> 16),
uint8_t(total_device_blocks >> 8),
uint8_t(total_device_blocks),
/* number of blocks on device */
0x00, 0x01, /* reserved (formerly: device type) */
0x00, 0x01, /* reserved (formerly: device ID) */
0x00, 0x00,
0x00, 0x00, /* reserved ('sbData', no further explanation given) */
0x00, 0x01, /* number of device descriptor entries */
0x00, 0x00,
0x00, 0x40, /* first device descriptor's starting block */
0x00, 0x0a, /* size of device driver */
0x00, 0x01, /*
More modern documentation: operating system (MacOS = 1)
Inside Macintosh IV: system type (Mac Plus = 1)
*/
};
driver_description.resize(512);
return driver_description;
}
// Blocks 1, 2 and 3 contain parts of the partition map.
if(source_address < 4) {
struct Partition {
const char *name, *type;
uint32_t start_block, size;
uint8_t status;
} partitions[3] = {
{ "MacOS", "Apple_HFS", 0x60, uint32_t(number_of_blocks_), 0xb7 },
{ "Apple", "Apple_partition_map", 0x01, 0x3f, 0x37 },
{ "Macintosh", "Apple_Driver", 0x40, 0x20, 0x7f },
};
std::vector<uint8_t> partition(512);
// Fill in the fixed fields.
partition[0] = 'P'; partition[1] = 'M'; /* Signature. */
partition[7] = 3; /* Number of partitions. */
const Partition &details = partitions[source_address-1];
partition[8] = uint8_t(details.start_block >> 24);
partition[9] = uint8_t(details.start_block >> 16);
partition[10] = uint8_t(details.start_block >> 8);
partition[11] = uint8_t(details.start_block);
partition[84] = partition[12] = uint8_t(details.size >> 24);
partition[85] = partition[13] = uint8_t(details.size >> 16);
partition[86] = partition[14] = uint8_t(details.size >> 8);
partition[87] = partition[15] = uint8_t(details.size);
// 32 bytes are allocated for each of the following strings.
memcpy(&partition[16], details.name, strlen(details.name));
memcpy(&partition[48], details.type, strlen(details.type));
partition[91] = details.status;
// The third entry in this constructed partition map is the driver;
// add some additional details.
if(source_address == 3) {
partition[98] = 0x13;
partition[99] = 0x9e; /* This version of the driver code is 0x139e bytes long. */
partition[118] = 0x84;
partition[119] = 0xb9; /* Driver checksum. */
memcpy(&partition[120], "68000", strlen("68000")); /* Driver is for the 68000. */
// Various non-zero values that Apple HD SC Tool wrote are below; they are
// documented as reserved officially, so I don't know their meaning.
partition[137] = 0x01;
partition[138] = 0x06;
partition[143] = 0x01;
partition[147] = 0x02;
partition[149] = 0x07;
}
return partition;
}
// The ten blocks starting at 0x40 contain the SCSI driver.
if(source_address >= 0x40 && source_address < 0x40 + 10) {
// This is the body of the SCSI driver.
const uint8_t driver[] = {
const uint8_t *VolumeProvider::driver() const {
static constexpr uint8_t driver[] = {
0x60, 0x00, 0x01, 0xba, 0x23, 0x24, 0x00, 0x01, 0x00, 0x27, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x13, 0x9e, 0x00, 0x00, 0xda, 0xe7, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56,
0x00, 0x7c, 0x00, 0x68, 0x00, 0x72, 0x00, 0x5e, 0x07, 0x2e, 0x53, 0x43, 0x53, 0x49, 0x30, 0x30, 0x41, 0xbd, 0x30, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x1f, 0x60, 0x00,
0x02, 0x12, 0x41, 0xfa, 0xff, 0xf4, 0x20, 0xaf, 0x00, 0x04, 0x4e, 0x75, 0x20, 0x3a, 0xff, 0xea, 0x4e, 0x75, 0x20, 0x0d, 0x2a, 0x7a, 0xff, 0xe2, 0x4e, 0x75, 0x41, 0xfa, 0xff, 0xd8, 0x30, 0xaf,
@ -292,10 +184,17 @@ std::vector<uint8_t> Mapper::convert_source_block(ssize_t source_address, std::v
0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72,
};
const auto offset = (source_address - 0x40) * 512;
return std::vector<uint8_t>(&driver[offset], &driver[offset + 512]);
return driver;
}
// Default: return an empty block.
return std::vector<uint8_t>(512);
const char *VolumeProvider::driver_target() const {
return "68000";
}
const char *VolumeProvider::type() const {
return "Apple_HFS";
}
const char *VolumeProvider::name() const {
return "MacOS";
}

View File

@ -12,16 +12,15 @@
#include <cstddef>
#include <cstdint>
#include <sys/types.h>
#include <vector>
#include "ApplePartitionMap.hpp"
namespace Storage {
namespace MassStorage {
namespace Encodings {
namespace Macintosh {
enum class DriveType {
SCSI
};
using DriveType = Storage::MassStorage::Encodings::Apple::DriveType;
/*!
On the Macintosh life is slightly complicated by Apple's
@ -39,47 +38,23 @@ class Volume {
virtual void set_drive_type(DriveType type) = 0;
};
struct VolumeProvider {
static constexpr bool HasDriver = true;
size_t driver_size() const;
uint16_t driver_checksum() const;
const uint8_t *driver() const;
const char *driver_target() const;
const char *name() const;
const char *type() const;
};
/*!
A Mapper can used by a mass-storage device that knows the
contents of an HFS or MFS partition to provide the conversion
contents of an HFS partition to provide the conversion
necessary to a particular type of drive.
*/
class Mapper {
public:
/*!
Sets the drive type to map to and the number of blocks in the underlying partition.
*/
void set_drive_type(DriveType, size_t number_of_blocks);
/*!
Maps from a mass-storage device address to an address
in the underlying [H/M]FS partition.
*/
ssize_t to_source_address(size_t address);
/*!
Converts from a source data block to one properly encoded for the drive type.
Expected usage:
const size_t source_address = mapper.to_source_address(unit_address);
if(is_in_range_for_partition(source_address)) {
return mapper.convert_source_block(source_address, get_block_contents(source_address));
} else {
return mapper.convert_source_block(source_address);
}
*/
std::vector<uint8_t> convert_source_block(ssize_t source_address, std::vector<uint8_t> source_data = {});
/*!
@returns The total number of blocks on the entire volume.
*/
size_t get_number_of_blocks();
private:
DriveType drive_type_;
size_t number_of_blocks_;
};
using Mapper = Storage::MassStorage::Encodings::Apple::PartitionMap<VolumeProvider>;
}
}

View File

@ -17,12 +17,15 @@
namespace Storage {
namespace MassStorage {
template <size_t sector_size> class RawSectorDump: public MassStorageDevice {
template <long sector_size> class RawSectorDump: public MassStorageDevice {
public:
RawSectorDump(const std::string &file_name) : file_(file_name) {
RawSectorDump(const std::string &file_name, long offset = 0, long length = -1) :
file_(file_name),
file_size_((length == -1) ? long(file_.stats().st_size) : length),
file_start_(offset)
{
// Is the file a multiple of sector_size bytes in size?
const auto file_size = size_t(file_.stats().st_size);
if(file_size % sector_size) throw std::exception();
if(file_size_ % sector_size) throw std::exception();
}
/* MassStorageDevices overrides. */
@ -31,22 +34,23 @@ template <size_t sector_size> class RawSectorDump: public MassStorageDevice {
}
size_t get_number_of_blocks() final {
return size_t(file_.stats().st_size) / sector_size;
return size_t(file_size_ / sector_size);
}
std::vector<uint8_t> get_block(size_t address) final {
file_.seek(long(address * sector_size), SEEK_SET);
file_.seek(file_start_ + long(address * sector_size), SEEK_SET);
return file_.read(sector_size);
}
void set_block(size_t address, const std::vector<uint8_t> &contents) final {
assert(contents.size() == sector_size);
file_.seek(long(address * sector_size), SEEK_SET);
file_.seek(file_start_ + long(address * sector_size), SEEK_SET);
file_.write(contents);
}
private:
FileHolder file_;
const long file_size_, file_start_;
};
}

View File

@ -55,6 +55,8 @@ bool DirectAccessDevice::write(const Target::CommandState &state, Target::Respon
}
bool DirectAccessDevice::read_capacity(const Target::CommandState &, Target::Responder &responder) {
if(!device_) return false;
const auto final_block = device_->get_number_of_blocks() - 1;
const auto block_size = device_->get_block_size();
std::vector<uint8_t> data = {
@ -81,6 +83,8 @@ Target::Executor::Inquiry DirectAccessDevice::inquiry_values() {
}
bool DirectAccessDevice::format_unit(const Target::CommandState &, Target::Responder &responder) {
if(!device_) return false;
// Formatting: immediate.
responder.terminate_command(Target::Responder::Status::Good);
return true;

View File

@ -74,7 +74,7 @@ void Bus::set_activity_observer(Activity::Observer *observer) {
activity_observer_->register_led("SCSI");
}
BusState Bus::get_state() {
BusState Bus::get_state() const {
return state_;
}

View File

@ -19,6 +19,8 @@
namespace SCSI {
/// Provides the current state of the SCSI bus, being comprised of a bitwise combination
/// of zero or more of the @c BusState flags defined below.
typedef int BusState;
constexpr BusState DefaultBusState = 0;
@ -118,7 +120,7 @@ class Bus: public ClockingHint::Source, public Activity::Source {
/*!
@returns the current state of the bus.
*/
BusState get_state();
BusState get_state() const;
struct Observer {
/// Reports to an observer that the bus changed from a previous state to @c new_state,