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:
commit
38e85a340a
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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(...) {}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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() {}
|
||||
|
169
Machines/Apple/AppleII/SCSICard.cpp
Normal file
169
Machines/Apple/AppleII/SCSICard.cpp
Normal 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);
|
||||
}
|
55
Machines/Apple/AppleII/SCSICard.hpp
Normal file
55
Machines/Apple/AppleII/SCSICard.hpp
Normal 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 */
|
@ -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;
|
||||
|
@ -58,6 +58,7 @@ enum Name {
|
||||
AppleIIeCharacter,
|
||||
AppleIIEnhancedE,
|
||||
AppleIIEnhancedECharacter,
|
||||
AppleIISCSICard,
|
||||
|
||||
// Apple IIgs.
|
||||
AppleIIgsROM00,
|
||||
|
@ -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 */,
|
||||
|
@ -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.
|
@ -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!).
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
38
Storage/MassStorage/Encodings/AppleIIVolume.hpp
Normal file
38
Storage/MassStorage/Encodings/AppleIIVolume.hpp
Normal 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 */
|
257
Storage/MassStorage/Encodings/ApplePartitionMap.hpp
Normal file
257
Storage/MassStorage/Encodings/ApplePartitionMap.hpp
Normal 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 */
|
@ -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";
|
||||
}
|
||||
|
@ -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>;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user