diff --git a/Analyser/Static/Macintosh/StaticAnalyser.cpp b/Analyser/Static/Macintosh/StaticAnalyser.cpp index 3c20dc7be..1dc76db84 100644 --- a/Analyser/Static/Macintosh/StaticAnalyser.cpp +++ b/Analyser/Static/Macintosh/StaticAnalyser.cpp @@ -10,10 +10,10 @@ #include "Target.hpp" Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { - // This analyser can comprehend disks only. - if(media.disks.empty()) return {}; + // This analyser can comprehend disks and mass-storage devices only. + if(media.disks.empty() && media.mass_storage_devices.empty()) return {}; - // If there is at least one disk, wave it through. + // As there is at least one usable media image, wave it through. Analyser::Static::TargetList targets; using Target = Analyser::Static::Macintosh::Target; diff --git a/Analyser/Static/Macintosh/Target.hpp b/Analyser/Static/Macintosh/Target.hpp index fd2434ca9..5d70748f0 100644 --- a/Analyser/Static/Macintosh/Target.hpp +++ b/Analyser/Static/Macintosh/Target.hpp @@ -21,7 +21,7 @@ struct Target: public ::Analyser::Static::Target { MacPlus }; - Model model = Model::Mac512ke; + Model model = Model::MacPlus; }; } diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 2aa77322d..a9df90cf0 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -46,6 +46,9 @@ #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" +// Mass Storage Devices (i.e. usually, hard disks) +#include "../../Storage/MassStorage/Formats/HFV.hpp" + // Tapes #include "../../Storage/Tape/Formats/CAS.hpp" #include "../../Storage/Tape/Formats/CommodoreTAP.hpp" @@ -102,7 +105,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format("dsd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // DSD Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DSK (Apple II) - Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // DSK (Macintosh) + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk) + Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk) Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DSK (MSX) Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Oric) // DSK (Oric) Format("g64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // G64 @@ -160,7 +164,7 @@ Media Analyser::Static::GetMedia(const std::string &file_name) { TargetList Analyser::Static::GetTargets(const std::string &file_name) { TargetList targets; - // Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the + // Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the // union of all platforms this file might be a target for. TargetPlatform::IntType potential_platforms = 0; Media media = GetMediaAndPlatforms(file_name, potential_platforms); diff --git a/Analyser/Static/StaticAnalyser.hpp b/Analyser/Static/StaticAnalyser.hpp index 7a7a00000..5981fc435 100644 --- a/Analyser/Static/StaticAnalyser.hpp +++ b/Analyser/Static/StaticAnalyser.hpp @@ -11,9 +11,10 @@ #include "../Machines.hpp" -#include "../../Storage/Tape/Tape.hpp" -#include "../../Storage/Disk/Disk.hpp" #include "../../Storage/Cartridge/Cartridge.hpp" +#include "../../Storage/Disk/Disk.hpp" +#include "../../Storage/MassStorage/MassStorageDevice.hpp" +#include "../../Storage/Tape/Tape.hpp" #include #include @@ -29,9 +30,10 @@ struct Media { std::vector> disks; std::vector> tapes; std::vector> cartridges; + std::vector> mass_storage_devices; bool empty() const { - return disks.empty() && tapes.empty() && cartridges.empty(); + return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty(); } }; diff --git a/Components/5380/ncr5380.cpp b/Components/5380/ncr5380.cpp new file mode 100644 index 000000000..6084b1e79 --- /dev/null +++ b/Components/5380/ncr5380.cpp @@ -0,0 +1,312 @@ +// +// ncr5380.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "ncr5380.hpp" + +#include "../../Outputs/Log.hpp" + +using namespace NCR::NCR5380; +using SCSI::Line; + +NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) : + bus_(bus), + clock_rate_(clock_rate) { + device_id_ = bus_.add_device(); + bus_.add_observer(this); +} + +void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) { + switch(address & 7) { + case 0: +// LOG("[SCSI 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_); + } + break; + + case 1: { +// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value)); + initiator_command_ = value; + + bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention); + if(value & 0x80) bus_output_ |= Line::Reset; + if(value & 0x08) bus_output_ |= Line::Busy; + if(value & 0x04) bus_output_ |= Line::SelectTarget; + + /* bit 5 = differential enable if this were a 5381 */ + + test_mode_ = value & 0x40; + assert_data_bus_ = value & 0x01; + update_control_output(); + } break; + + case 2: +// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value)); + mode_ = value; + + // bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled) + // bit 6: 1 = be a SCSI target; 0 = be an initiator + // bit 5: 1 = check parity + // bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found + // bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller + // bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs + // bit 1: 1 = use DMA mode + // bit 0: 1 = begin arbitration mode (device ID should be in register 0) + arbitration_in_progress_ = false; + switch(mode_ & 0x3) { + case 0x0: + bus_output_ &= ~SCSI::Line::Busy; + dma_request_ = false; + set_execution_state(ExecutionState::None); + break; + + case 0x1: + arbitration_in_progress_ = true; + set_execution_state(ExecutionState::WaitingForBusy); + lost_arbitration_ = false; + break; + + default: + assert_data_bus_ = false; + set_execution_state(ExecutionState::PerformingDMA); + bus_.update_observers(); + break; + } + update_control_output(); + break; + + case 3: { +// LOG("[SCSI 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)); + break; + + case 5: +// LOG("[SCSI 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)); + dma_operation_ = DMAOperation::TargetReceive; + break; + + case 7: +// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value)); + dma_operation_ = DMAOperation::InitiatorReceive; + break; + } + + // Data is output only if the data bus is asserted. + if(assert_data_bus_) { + bus_output_ = (bus_output_ & ~SCSI::Line::Data) | data_bus_; + } else { + bus_output_ &= ~SCSI::Line::Data; + } + + // In test mode, still nothing is output. Otherwise throw out + // the current value of bus_output_. + if(test_mode_) { + bus_.set_device_output(device_id_, SCSI::DefaultBusState); + } else { + bus_.set_device_output(device_id_, bus_output_); + } +} + +uint8_t NCR5380::read(int address, bool dma_acknowledge) { + switch(address & 7) { + case 0: +// LOG("[SCSI 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 uint8_t(bus_.get_state()); + + case 1: +// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-')); + return + // Bits repeated as they were set. + (initiator_command_ & ~0x60) | + + // Arbitration in progress. + (arbitration_in_progress_ ? 0x40 : 0x00) | + + // Lost arbitration. + (lost_arbitration_ ? 0x20 : 0x00); + + case 2: +// LOG("[SCSI 2] Get mode"); + return mode_; + + case 3: +// LOG("[SCSI 3] Get target command"); + return target_command_; + + case 4: { + const auto bus_state = bus_.get_state(); + const uint8_t result = + ((bus_state & Line::Reset) ? 0x80 : 0x00) | + ((bus_state & Line::Busy) ? 0x40 : 0x00) | + ((bus_state & Line::Request) ? 0x20 : 0x00) | + ((bus_state & Line::Message) ? 0x10 : 0x00) | + ((bus_state & Line::Control) ? 0x08 : 0x00) | + ((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)); + 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 */ + ((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) | + /* b5 = parity error */ + /* b4 = IRQ active */ + (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)); + return result; + } + + case 6: +// LOG("[SCSI 6] Get input data"); + return 0xff; + + case 7: +// LOG("[SCSI 7] Reset parity/interrupt"); + return 0xff; + } + return 0; +} + +SCSI::BusState NCR5380::target_output() { + SCSI::BusState output = SCSI::DefaultBusState; + if(target_command_ & 0x08) output |= Line::Request; + if(target_command_ & 0x04) output |= Line::Message; + if(target_command_ & 0x02) output |= Line::Control; + if(target_command_ & 0x01) output |= Line::Input; + return output; +} + +void NCR5380::update_control_output() { + bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input | Line::Acknowledge | Line::Attention); + if(mode_ & 0x40) { + // This is a target; C/D, I/O, /MSG and /REQ are signalled on the bus. + bus_output_ |= target_output(); + } else { + // This is an initiator; /ATN and /ACK are signalled on the bus. + if( + (initiator_command_ & 0x10) || + (state_ == ExecutionState::PerformingDMA && dma_acknowledge_) + ) bus_output_ |= Line::Acknowledge; + if(initiator_command_ & 0x02) bus_output_ |= Line::Attention; + } +} + +void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) { + switch(state_) { + default: break; + + /* + Official documentation: + + Arbitration is accomplished using a bus-free filter to continuously monitor BSY. + If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free + and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive + and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun + (BSY asserted), an arbitration delay of 2.2 /Lsec must elapse before the data bus + can be examined to deter- mine if arbitration has been won. This delay must be + implemented in the controlling software driver. + + Personal notes: + + I'm discounting that "arbitratation is accomplished" opening, and assuming that what needs + to happen is: + + (i) wait for BSY to be inactive; + (ii) count 400 nsec; + (iii) check that BSY and SEL are inactive. + */ + + case ExecutionState::WaitingForBusy: + if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return; + state_ = ExecutionState::WatchingBusy; + + case ExecutionState::WatchingBusy: + if(!(new_state & SCSI::Line::Busy)) { + lost_arbitration_ = true; + set_execution_state(ExecutionState::None); + } + + // Check for having hit 400ns (more or less) since BSY was inactive. + if(time_since_change >= SCSI::BusSettleDelay) { +// arbitration_in_progress_ = false; + if(new_state & SCSI::Line::SelectTarget) { + lost_arbitration_ = true; + set_execution_state(ExecutionState::None); + } else { + bus_output_ &= ~SCSI::Line::Busy; + set_execution_state(ExecutionState::None); + } + } + + /* TODO: there's a bug here, given that the dropping of Busy isn't communicated onward. */ + break; + + case ExecutionState::PerformingDMA: + if(time_since_change < SCSI::DeskewDelay) return; + + // Signal a DMA request if the request line is active, i.e. meaningful data is + // on the bus, and this device hasn't yet acknowledged it. + switch(new_state & (SCSI::Line::Request | SCSI::Line::Acknowledge)) { + case 0: + dma_request_ = false; + break; + case SCSI::Line::Request: + dma_request_ = true; + break; + case SCSI::Line::Request | SCSI::Line::Acknowledge: + dma_request_ = false; + break; + case SCSI::Line::Acknowledge: + dma_acknowledge_ = false; + dma_request_ = false; + update_control_output(); + bus_.set_device_output(device_id_, bus_output_); + break; + } + break; + } +} + +void NCR5380::set_execution_state(ExecutionState state) { + state_ = state; + if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready; +} diff --git a/Components/5380/ncr5380.hpp b/Components/5380/ncr5380.hpp new file mode 100644 index 000000000..8e7874d38 --- /dev/null +++ b/Components/5380/ncr5380.hpp @@ -0,0 +1,75 @@ +// +// ncr5380.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef ncr5380_hpp +#define ncr5380_hpp + +#include + +#include "../../Storage/MassStorage/SCSI/SCSI.hpp" + + +namespace NCR { +namespace NCR5380 { + +/*! + Models the NCR 5380, a SCSI interface chip. +*/ +class NCR5380 final: public SCSI::Bus::Observer { + public: + NCR5380(SCSI::Bus &bus, int clock_rate); + + /*! Writes @c value to @c address. */ + void write(int address, uint8_t value, bool dma_acknowledge = false); + + /*! Reads from @c address. */ + uint8_t read(int address, bool dma_acknowledge = false); + + private: + SCSI::Bus &bus_; + + const int clock_rate_; + size_t device_id_; + + SCSI::BusState bus_output_ = SCSI::DefaultBusState; + SCSI::BusState expected_phase_ = SCSI::DefaultBusState; + uint8_t mode_ = 0xff; + uint8_t initiator_command_ = 0xff; + uint8_t data_bus_ = 0xff; + uint8_t target_command_ = 0xff; + bool test_mode_ = false; + bool assert_data_bus_ = false; + bool dma_request_ = false; + bool dma_acknowledge_ = false; + + enum class ExecutionState { + None, + WaitingForBusy, + WatchingBusy, + PerformingDMA, + } state_ = ExecutionState::None; + enum class DMAOperation { + Ready, + Send, + TargetReceive, + InitiatorReceive + } dma_operation_ = DMAOperation::Ready; + bool lost_arbitration_ = false, arbitration_in_progress_ = false; + + void set_execution_state(ExecutionState state); + + SCSI::BusState target_output(); + void update_control_output(); + + void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final; +}; + +} +} + +#endif /* ncr5380_hpp */ diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 566da8ca5..3e6f83c09 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -24,7 +24,7 @@ class BusHandler { virtual void set_interrupt(bool irq) {} }; -class i8272: public Storage::Disk::MFMController { +class i8272 : public Storage::Disk::MFMController { public: i8272(BusHandler &bus_handler, Cycles clock_rate); @@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController { void set_dma_acknowledge(bool dack); void set_terminal_count(bool tc); - ClockingHint::Preference preferred_clocking() override; + ClockingHint::Preference preferred_clocking() final; protected: virtual void select_drive(int number) = 0; diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index 8f5cd92f8..252f1bbae 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -26,7 +26,7 @@ namespace Apple { /*! Provides an emulation of the Apple Disk II. */ -class DiskII: +class DiskII final: public Storage::Disk::Drive::EventDelegate, public ClockingHint::Source, public ClockingHint::Observer { @@ -76,7 +76,7 @@ class DiskII: void set_disk(const std::shared_ptr &disk, int drive); // As per Sleeper. - ClockingHint::Preference preferred_clocking() override; + ClockingHint::Preference preferred_clocking() final; // The Disk II functions as a potential target for @c Activity::Sources. void set_activity_observer(Activity::Observer *observer); diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 16d8c6bef..e0053ccb1 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -26,15 +26,21 @@ #include "../../../Outputs/Log.hpp" #include "../../../ClockReceiver/JustInTime.hpp" +#include "../../../ClockReceiver/ClockingHintSource.hpp" //#define LOG_TRACE +#include "../../../Components/5380/ncr5380.hpp" #include "../../../Components/6522/6522.hpp" #include "../../../Components/8530/z8530.hpp" #include "../../../Components/DiskII/IWM.hpp" #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" #include "../../../Processors/68000/68000.hpp" +#include "../../../Storage/MassStorage/SCSI/SCSI.hpp" +#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp" +#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp" + #include "../../../Analyser/Static/Macintosh/Target.hpp" #include "../../Utility/MemoryPacker.hpp" @@ -58,16 +64,20 @@ template class ConcreteMachin public KeyboardMachine::MappedMachine, public Zilog::SCC::z8530::Delegate, public Activity::Source, - public DriveSpeedAccumulator::Delegate { + public DriveSpeedAccumulator::Delegate, + public ClockingHint::Observer { public: using Target = Analyser::Static::Macintosh::Target; ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : mc68000_(*this), iwm_(CLOCK_RATE), - video_(ram_, audio_, drive_speed_accumulator_), + video_(audio_, drive_speed_accumulator_), via_(via_port_handler_), via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_), + scsi_bus_(CLOCK_RATE * 2), + scsi_(scsi_bus_, CLOCK_RATE * 2), + hard_drive_(scsi_bus_, 6 /* SCSI ID */), drives_{ {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} @@ -94,7 +104,7 @@ template class ConcreteMachin break; case Model::Mac512ke: case Model::MacPlus: { - ram_size = 512*1024; + ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024; rom_size = 128*1024; const std::initializer_list crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e }; rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s); @@ -102,7 +112,8 @@ template class ConcreteMachin } ram_mask_ = (ram_size >> 1) - 1; rom_mask_ = (rom_size >> 1) - 1; - video_.set_ram_mask(ram_mask_); + ram_.resize(ram_size >> 1); + video_.set_ram(ram_.data(), ram_mask_); // Grab a copy of the ROM and convert it into big-endian data. const auto roms = rom_fetcher(rom_descriptions); @@ -113,7 +124,7 @@ template class ConcreteMachin Memory::PackBigEndian16(*roms[0], rom_); // Randomise memory contents. - Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_)); + Memory::Fuzz(ram_); // Attach the drives to the IWM. iwm_->set_drive(0, &drives_[0]); @@ -127,6 +138,11 @@ template class ConcreteMachin // Make sure interrupt changes from the SCC are observed. scc_.set_delegate(this); + // Also watch for changes in clocking requirement from the SCSI chip. + if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + scsi_bus_.set_clocking_hint_observer(this); + } + // The Mac runs at 7.8336mHz. set_clock_rate(double(CLOCK_RATE)); audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); @@ -182,7 +198,7 @@ template class ConcreteMachin // Grab the word-precision address being accessed. uint16_t *memory_base = nullptr; HalfCycles delay; - switch(memory_map_[word_address >> 18]) { + switch(memory_map_[word_address >> 16]) { default: assert(false); case BusDevice::Unassigned: @@ -232,6 +248,36 @@ template class ConcreteMachin } } return delay; + case BusDevice::SCSI: { + const int register_address = word_address >> 3; + const bool dma_acknowledge = word_address & 0x100; + + // Even accesses = read; odd = write. + if(*cycle.address & 1) { + // Odd access => this is a write. Data will be in the upper byte. + if(cycle.operation & Microcycle::Read) { + scsi_.write(register_address, 0xff, dma_acknowledge); + } else { + if(cycle.operation & Microcycle::SelectWord) { + scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge); + } else { + scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge); + } + } + } else { + // Even access => this is a read. + if(cycle.operation & Microcycle::Read) { + const auto result = scsi_.read(register_address, dma_acknowledge); + if(cycle.operation & Microcycle::SelectWord) { + // Data is loaded on the top part of the bus only. + cycle.value->full = uint16_t((result << 8) | 0xff); + } else { + cycle.value->halves.low = result; + } + } + } + } return delay; + case BusDevice::SCCReadResetPhase: { // Any word access here adjusts phase. if(cycle.operation & Microcycle::SelectWord) { @@ -280,7 +326,7 @@ template class ConcreteMachin if(word_address > ram_mask_ - 0x6c80) update_video(); - memory_base = ram_; + memory_base = ram_.data(); word_address &= ram_mask_; // Apply a delay due to video contention if applicable; technically this is @@ -348,12 +394,12 @@ template class ConcreteMachin case Model::Mac128k: case Model::Mac512k: case Model::Mac512ke: - populate_memory_map([rom_is_overlay] (std::function map_to) { + populate_memory_map(0, [rom_is_overlay] (std::function map_to) { // Addresses up to $80 0000 aren't affected by this bit. if(rom_is_overlay) { // Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. - for(int c = 0; c <= 0x600000; c += 0x100000) { - map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned); + for(int c = 0; c < 0x600000; c += 0x100000) { + map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM); } map_to(0x800000, BusDevice::RAM); } else { @@ -365,19 +411,19 @@ template class ConcreteMachin break; case Model::MacPlus: - populate_memory_map([rom_is_overlay] (std::function map_to) { + populate_memory_map(0, [rom_is_overlay] (std::function map_to) { // Addresses up to $80 0000 aren't affected by this bit. if(rom_is_overlay) { - map_to(0x100000, BusDevice::ROM); - map_to(0x400000, BusDevice::Unassigned); - map_to(0x500000, BusDevice::ROM); - map_to(0x580000, BusDevice::Unassigned); + for(int c = 0; c < 0x580000; c += 0x20000) { + map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); + } map_to(0x600000, BusDevice::SCSI); map_to(0x800000, BusDevice::RAM); } else { map_to(0x400000, BusDevice::RAM); - map_to(0x500000, BusDevice::ROM); - map_to(0x580000, BusDevice::Unassigned); + for(int c = 0x400000; c < 0x580000; c += 0x20000) { + map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); + } map_to(0x600000, BusDevice::SCSI); map_to(0x800000, BusDevice::Unassigned); } @@ -396,16 +442,27 @@ template class ConcreteMachin } bool insert_media(const Analyser::Static::Media &media) override { - if(media.disks.empty()) + if(media.disks.empty() && media.mass_storage_devices.empty()) return false; // TODO: shouldn't allow disks to be replaced like this, as the Mac // uses software eject. Will need to expand messaging ability of // insert_media. - if(drives_[0].has_disk()) - drives_[1].set_disk(media.disks[0]); - else - drives_[0].set_disk(media.disks[0]); + if(!media.disks.empty()) { + if(drives_[0].has_disk()) + drives_[1].set_disk(media.disks[0]); + else + drives_[0].set_disk(media.disks[0]); + } + + // TODO: allow this only at machine startup. + if(!media.mass_storage_devices.empty()) { + const auto volume = dynamic_cast(media.mass_storage_devices.front().get()); + if(volume) { + volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI); + } + hard_drive_->set_storage(media.mass_storage_devices.front()); + } return true; } @@ -446,6 +503,10 @@ template class ConcreteMachin } private: + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override { + scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; + } + void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override { iwm_.flush(); drives_[0].set_rotation_speed(speed); @@ -533,6 +594,11 @@ template class ConcreteMachin via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); } + + // Update the SCSI if currently active. + if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration); + } } forceinline void update_video() { @@ -672,6 +738,10 @@ template class ConcreteMachin VIAPortHandler via_port_handler_; Zilog::SCC::z8530 scc_; + SCSI::Bus scsi_bus_; + NCR::NCR5380::NCR5380 scsi_; + SCSI::Target::Target hard_drive_; + bool scsi_bus_is_clocked_ = false; HalfCycles via_clock_; HalfCycles real_time_clock_; @@ -693,72 +763,39 @@ template class ConcreteMachin RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned }; - /// Divides the 24-bit address space up into $80000 (i.e. 512kb) segments, recording + /// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording /// which device is current mapped in each area. Keeping it in a table is a bit faster /// than the multi-level address inspection that is otherwise required, as well as /// simplifying slightly the handling of different models. /// - /// So: index with the top 5 bits of the 24-bit address. - BusDevice memory_map_[32]; + /// So: index with the top 7 bits of the 24-bit address. + BusDevice memory_map_[128]; void setup_memory_map() { - // Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true. + // Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true; + // start by calling into set_rom_is_overlay to seed everything up to $800000. + set_rom_is_overlay(true); - using Model = Analyser::Static::Macintosh::Target::Model; - switch(model) { - default: assert(false); - - case Model::Mac128k: - case Model::Mac512k: - case Model::Mac512ke: - populate_memory_map([] (std::function map_to) { - // Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. - for(int c = 0; c <= 0x600000; c += 0x100000) { - map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned); - } - map_to(0x800000, BusDevice::RAM); - map_to(0x900000, BusDevice::Unassigned); - map_to(0xa00000, BusDevice::SCCReadResetPhase); - map_to(0xb00000, BusDevice::Unassigned); - map_to(0xc00000, BusDevice::SCCWrite); - map_to(0xd00000, BusDevice::Unassigned); - map_to(0xe00000, BusDevice::IWM); - map_to(0xe80000, BusDevice::Unassigned); - map_to(0xf00000, BusDevice::VIA); - map_to(0xf80000, BusDevice::PhaseRead); - map_to(0x1000000, BusDevice::Unassigned); - }); - break; - - case Model::MacPlus: - populate_memory_map([] (std::function map_to) { - map_to(0x100000, BusDevice::ROM); - map_to(0x400000, BusDevice::Unassigned); - map_to(0x500000, BusDevice::ROM); - map_to(0x580000, BusDevice::Unassigned); - map_to(0x600000, BusDevice::SCSI); - map_to(0x800000, BusDevice::RAM); - map_to(0x900000, BusDevice::Unassigned); - map_to(0xa00000, BusDevice::SCCReadResetPhase); - map_to(0xb00000, BusDevice::Unassigned); - map_to(0xc00000, BusDevice::SCCWrite); - map_to(0xd00000, BusDevice::Unassigned); - map_to(0xe00000, BusDevice::IWM); - map_to(0xe80000, BusDevice::Unassigned); - map_to(0xf00000, BusDevice::VIA); - map_to(0xf80000, BusDevice::PhaseRead); - map_to(0x1000000, BusDevice::Unassigned); - }); - break; - } + populate_memory_map(0x800000, [] (std::function map_to) { + map_to(0x900000, BusDevice::Unassigned); + map_to(0xa00000, BusDevice::SCCReadResetPhase); + map_to(0xb00000, BusDevice::Unassigned); + map_to(0xc00000, BusDevice::SCCWrite); + map_to(0xd00000, BusDevice::Unassigned); + map_to(0xe00000, BusDevice::IWM); + map_to(0xe80000, BusDevice::Unassigned); + map_to(0xf00000, BusDevice::VIA); + map_to(0xf80000, BusDevice::PhaseRead); + map_to(0x1000000, BusDevice::Unassigned); + }); } - void populate_memory_map(std::function)> populator) { + void populate_memory_map(int start_address, std::function)> populator) { // Define semantics for below; map_to will write from the current cursor position // to the supplied 24-bit address, setting a particular mapped device. - int segment = 0; + int segment = start_address >> 17; auto map_to = [&segment, this](int address, BusDevice device) { - for(; segment < address >> 19; ++segment) { + for(; segment < address >> 17; ++segment) { this->memory_map_[segment] = device; } }; @@ -768,8 +805,8 @@ template class ConcreteMachin uint32_t ram_mask_ = 0; uint32_t rom_mask_ = 0; - uint16_t rom_[64*1024]; - uint16_t ram_[256*1024]; + uint16_t rom_[64*1024]; // i.e. up to 128kb in size. + std::vector ram_; }; } diff --git a/Machines/Apple/Macintosh/RealTimeClock.hpp b/Machines/Apple/Macintosh/RealTimeClock.hpp index 5a1f01496..9be04bb30 100644 --- a/Machines/Apple/Macintosh/RealTimeClock.hpp +++ b/Machines/Apple/Macintosh/RealTimeClock.hpp @@ -25,8 +25,15 @@ class RealTimeClock { public: RealTimeClock() { // TODO: this should persist, if possible, rather than - // being randomly initialised. - Memory::Fuzz(data_, sizeof(data_)); + // being default initialised. + const uint8_t default_data[] = { + 0xa8, 0x00, 0x00, 0x00, + 0xcc, 0x0a, 0xcc, 0x0a, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x63, 0x00, + 0x03, 0x88, 0x00, 0x4c + }; + memcpy(data_, default_data, sizeof(data_)); } /*! diff --git a/Machines/Apple/Macintosh/Video.cpp b/Machines/Apple/Macintosh/Video.cpp index 0ab4ae4c5..e1b3254c8 100644 --- a/Machines/Apple/Macintosh/Video.cpp +++ b/Machines/Apple/Macintosh/Video.cpp @@ -33,11 +33,10 @@ const int sync_end = 38; // "The visible portion of a full-screen display consists of 342 horizontal scan lines... // During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines," // -Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : +Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : audio_(audio), drive_speed_accumulator_(drive_speed_accumulator), - crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1), - ram_(ram) { + crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) { crt_.set_display_type(Outputs::Display::DisplayType::RGB); crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f)); @@ -196,6 +195,7 @@ void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use use_alternate_audio_buffer_ = use_alternate_audio_buffer; } -void Video::set_ram_mask(uint32_t mask) { +void Video::set_ram(uint16_t *ram, uint32_t mask) { + ram_ = ram; ram_mask_ = mask; } diff --git a/Machines/Apple/Macintosh/Video.hpp b/Machines/Apple/Macintosh/Video.hpp index 9b8bfd683..ab4d0bac8 100644 --- a/Machines/Apple/Macintosh/Video.hpp +++ b/Machines/Apple/Macintosh/Video.hpp @@ -29,7 +29,7 @@ class Video { Constructs an instance of @c Video sourcing its pixel data from @c ram and providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. */ - Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); + Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); /*! Sets the target device for video data. @@ -47,10 +47,10 @@ class Video { void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); /*! - Provides a mask indicating which parts of the generated video and audio/drive addresses are + Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. */ - void set_ram_mask(uint32_t); + void set_ram(uint16_t *ram, uint32_t mask); /*! @returns @c true if the video is currently outputting a vertical sync, @c false otherwise. diff --git a/Machines/Utility/MemoryFuzzer.cpp b/Machines/Utility/MemoryFuzzer.cpp index c423cc5d1..4bab8a062 100644 --- a/Machines/Utility/MemoryFuzzer.cpp +++ b/Machines/Utility/MemoryFuzzer.cpp @@ -26,7 +26,3 @@ void Memory::Fuzz(uint8_t *buffer, std::size_t size) { void Memory::Fuzz(uint16_t *buffer, std::size_t size) { Fuzz(reinterpret_cast(buffer), size * sizeof(uint16_t)); } - -void Memory::Fuzz(std::vector &buffer) { - Fuzz(buffer.data(), buffer.size()); -} diff --git a/Machines/Utility/MemoryFuzzer.hpp b/Machines/Utility/MemoryFuzzer.hpp index 9beecd2f6..a40406acd 100644 --- a/Machines/Utility/MemoryFuzzer.hpp +++ b/Machines/Utility/MemoryFuzzer.hpp @@ -22,7 +22,9 @@ void Fuzz(uint8_t *buffer, std::size_t size); void Fuzz(uint16_t *buffer, std::size_t size); /// Replaces all existing vector contents with random bytes. -void Fuzz(std::vector &buffer); +template void Fuzz(std::vector &buffer) { + Fuzz(reinterpret_cast(buffer.data()), buffer.size() * sizeof(buffer[0])); +} } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index b6f9f5f5b..2e10c1604 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -216,12 +216,21 @@ 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; 4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; }; + 4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */; }; + 4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; }; + 4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; }; + 4B6AAEAD230E40250078E864 /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA8230E40250078E864 /* Target.cpp */; }; + 4B6AAEAE230E40250078E864 /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA8230E40250078E864 /* Target.cpp */; }; 4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; }; 4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; }; 4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; }; 4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; }; 4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; }; 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 */; }; 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; 4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; }; 4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; }; @@ -631,6 +640,8 @@ 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; 4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; + 4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; }; + 4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; }; 4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; }; 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; @@ -669,6 +680,8 @@ 4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; }; 4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; }; 4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; + 4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; }; + 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; }; 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; 4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; }; 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; @@ -953,6 +966,13 @@ 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = ""; }; 4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = ""; }; 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = ""; }; + 4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MassStorageDevice.cpp; sourceTree = ""; }; + 4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MassStorageDevice.hpp; sourceTree = ""; }; + 4B6AAEA6230E40250078E864 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4B6AAEA7230E40250078E864 /* SCSI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SCSI.cpp; sourceTree = ""; }; + 4B6AAEA8230E40250078E864 /* Target.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Target.cpp; sourceTree = ""; }; + 4B6AAEA9230E40250078E864 /* SCSI.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SCSI.hpp; sourceTree = ""; }; + 4B6AAEAA230E40250078E864 /* TargetImplementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TargetImplementation.hpp; sourceTree = ""; }; 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WOZ.cpp; sourceTree = ""; }; 4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WOZ.hpp; sourceTree = ""; }; 4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = ""; }; @@ -968,6 +988,10 @@ 4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = ""; }; 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = ""; }; 4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = ""; }; + 4B74CF7F2312FA9C00500CE8 /* HFV.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = HFV.hpp; sourceTree = ""; }; + 4B74CF802312FA9C00500CE8 /* HFV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HFV.cpp; sourceTree = ""; }; + 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MacintoshVolume.cpp; path = Encodings/MacintoshVolume.cpp; sourceTree = ""; }; + 4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MacintoshVolume.hpp; path = Encodings/MacintoshVolume.hpp; sourceTree = ""; }; 4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = ""; }; 4B770A961FE9EE770026DC70 /* CompoundSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompoundSource.hpp; sourceTree = ""; }; 4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = ""; }; @@ -1422,6 +1446,8 @@ 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; + 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DirectAccessDevice.cpp; sourceTree = ""; }; + 4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DirectAccessDevice.hpp; sourceTree = ""; }; 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = ""; }; 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = ""; }; 4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = ""; }; @@ -1481,6 +1507,8 @@ 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = ""; }; 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+CRC32.m"; sourceTree = ""; }; 4BDA00E322E663B900AC3CD0 /* NSData+CRC32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+CRC32.h"; sourceTree = ""; }; + 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ncr5380.cpp; sourceTree = ""; }; + 4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ncr5380.hpp; sourceTree = ""; }; 4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = ""; }; 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = ""; }; @@ -2171,6 +2199,7 @@ 4BEE0A691D72496600532C7B /* Cartridge */, 4B8805F81DCFF6CD003085B1 /* Data */, 4BAB62AA1D3272D200DF5BA0 /* Disk */, + 4B6AAEA1230E3E1D0078E864 /* MassStorage */, 4B69FB3A1C4D908A00B5F0AA /* Tape */, ); name = Storage; @@ -2234,6 +2263,33 @@ path = Implementation; sourceTree = ""; }; + 4B6AAEA1230E3E1D0078E864 /* MassStorage */ = { + isa = PBXGroup; + children = ( + 4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */, + 4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */, + 4B74CF7E2312FA9C00500CE8 /* Formats */, + 4B6AAEA5230E40250078E864 /* SCSI */, + 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */, + 4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */, + ); + path = MassStorage; + sourceTree = ""; + }; + 4B6AAEA5230E40250078E864 /* SCSI */ = { + isa = PBXGroup; + children = ( + 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */, + 4B6AAEA7230E40250078E864 /* SCSI.cpp */, + 4B6AAEA8230E40250078E864 /* Target.cpp */, + 4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */, + 4B6AAEA9230E40250078E864 /* SCSI.hpp */, + 4B6AAEA6230E40250078E864 /* Target.hpp */, + 4B6AAEAA230E40250078E864 /* TargetImplementation.hpp */, + ); + path = SCSI; + sourceTree = ""; + }; 4B7136831F78724F008B8ED9 /* MFM */ = { isa = PBXGroup; children = ( @@ -2252,6 +2308,15 @@ path = Encodings/MFM; sourceTree = ""; }; + 4B74CF7E2312FA9C00500CE8 /* Formats */ = { + isa = PBXGroup; + children = ( + 4B74CF7F2312FA9C00500CE8 /* HFV.hpp */, + 4B74CF802312FA9C00500CE8 /* HFV.cpp */, + ); + path = Formats; + sourceTree = ""; + }; 4B77069E1EC9045B0053B588 /* Z80 */ = { isa = PBXGroup; children = ( @@ -3111,6 +3176,7 @@ 4BC9DF4A1D04691600F44158 /* Components */ = { isa = PBXGroup; children = ( + 4BDACBE922FFA5B50045EF7E /* 5380 */, 4BD468F81D8DF4290084958B /* 1770 */, 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, @@ -3317,6 +3383,15 @@ path = ROMRequester; sourceTree = ""; }; + 4BDACBE922FFA5B50045EF7E /* 5380 */ = { + isa = PBXGroup; + children = ( + 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */, + 4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */, + ); + path = 5380; + sourceTree = ""; + }; 4BE5F85A1C3E1C2500C43F01 /* Resources */ = { isa = PBXGroup; children = ( @@ -3938,6 +4013,7 @@ 4B8318B322D3E540006DB630 /* Audio.cpp in Sources */, 4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */, 4B89452B201967B4007DE474 /* File.cpp in Sources */, + 4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */, 4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */, 4BD424E62193B5830097291A /* Shader.cpp in Sources */, 4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */, @@ -3949,11 +4025,14 @@ 4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */, 4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, 4B89451D201967B4007DE474 /* Disk.cpp in Sources */, + 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, 4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, 4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, + 4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */, + 4B6AAEAE230E40250078E864 /* Target.cpp in Sources */, 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, 4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */, 4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */, @@ -3976,6 +4055,7 @@ 4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */, 4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */, 4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */, + 4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */, 4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */, 4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */, 4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */, @@ -4009,6 +4089,7 @@ 4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */, 4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */, 4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */, + 4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */, 4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */, 4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */, 4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */, @@ -4056,6 +4137,7 @@ 4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */, 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, + 4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */, 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, 4B9378E422A199C600973513 /* Audio.cpp in Sources */, 4B89451E201967B4007DE474 /* Tape.cpp in Sources */, @@ -4065,6 +4147,7 @@ 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, 4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */, 4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */, + 4B6AAEAD230E40250078E864 /* Target.cpp in Sources */, 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */, 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */, 4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */, @@ -4104,8 +4187,10 @@ 4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */, 4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, 4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */, + 4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */, 4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */, + 4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */, 4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */, 4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */, 4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */, @@ -4119,6 +4204,7 @@ 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */, 4BCE0060227D39AB000CA200 /* Video.cpp in Sources */, + 4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */, 4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */, @@ -4165,6 +4251,7 @@ 4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */, 4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */, 4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */, + 4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */, 4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */, @@ -4180,6 +4267,7 @@ 4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, 4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, + 4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, 4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */, 4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */, diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp index 98c65926e..b85db192b 100644 --- a/Storage/Disk/Disk.hpp +++ b/Storage/Disk/Disk.hpp @@ -21,6 +21,9 @@ namespace Storage { namespace Disk { +/*! + Models a flopy disk. +*/ class Disk { public: virtual ~Disk() {} diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 5bf2c0bde..d661e14c2 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -138,7 +138,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { void set_event_delegate(EventDelegate *); // As per Sleeper. - ClockingHint::Preference preferred_clocking() override; + ClockingHint::Preference preferred_clocking() final; /// Adds an activity observer; it'll be notified of disk activity. /// The caller can specify whether to add an LED based on disk motor. diff --git a/Storage/MassStorage/Encodings/MacintoshVolume.cpp b/Storage/MassStorage/Encodings/MacintoshVolume.cpp new file mode 100644 index 000000000..4c3ca0893 --- /dev/null +++ b/Storage/MassStorage/Encodings/MacintoshVolume.cpp @@ -0,0 +1,301 @@ +// +// MacintoshVolume.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "MacintoshVolume.hpp" + +#include + +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 Mapper::get_number_of_blocks() { + return number_of_blocks_ + 0x60; +} + +ssize_t Mapper::to_source_address(size_t address) { + return ssize_t(address) - 0x60; +} + +std::vector Mapper::convert_source_block(ssize_t source_address, std::vector 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 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 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[] = { + 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, + 0x00, 0x06, 0x4e, 0x75, 0x48, 0x7a, 0x00, 0x4e, 0x20, 0x1f, 0x4e, 0x75, 0x70, 0x00, 0x31, 0x40, 0x00, 0x10, 0x60, 0x2a, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x05, 0x4a, 0x60, 0x1c, 0x2f, 0x08, + 0x2f, 0x09, 0x4e, 0xba, 0x07, 0x82, 0x60, 0x12, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x08, 0xca, 0x60, 0x08, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x05, 0xf2, 0x22, 0x5f, 0x20, 0x5f, 0x4a, 0x40, + 0x67, 0x04, 0x31, 0xc0, 0x01, 0x42, 0x08, 0x28, 0x00, 0x09, 0x00, 0x06, 0x66, 0x04, 0x2f, 0x38, 0x08, 0xfc, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, + 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, + 0x80, 0x00, 0x00, 0x01, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x53, 0x43, 0x53, 0x49, 0x20, 0x30, 0x00, 0x48, 0xe7, 0x3f, 0x3e, + 0x49, 0xfa, 0xfe, 0x50, 0x26, 0x14, 0x42, 0x94, 0x70, 0x00, 0x42, 0x42, 0x28, 0x3a, 0xfe, 0x40, 0x53, 0x44, 0x49, 0xfa, 0xfe, 0x2c, 0x14, 0x1c, 0xd0, 0x42, 0xe3, 0x58, 0x51, 0xcc, 0xff, 0xf8, + 0x4a, 0x40, 0x66, 0x02, 0x53, 0x40, 0x49, 0xfa, 0xfe, 0x2a, 0x28, 0x80, 0xb6, 0x80, 0x67, 0x0e, 0x49, 0xfa, 0xfe, 0x20, 0x38, 0x83, 0x4c, 0xdf, 0x7c, 0xfc, 0x70, 0xff, 0x4e, 0x75, 0x28, 0x48, + 0x41, 0xfa, 0xfd, 0xfe, 0x20, 0x3a, 0xfe, 0x08, 0xa0, 0x20, 0x70, 0x06, 0x22, 0x78, 0x01, 0x1c, 0xd2, 0xfc, 0x00, 0x9c, 0x24, 0x21, 0x67, 0x1e, 0x24, 0x42, 0x20, 0x52, 0x24, 0x50, 0x0c, 0xaa, + 0x41, 0xbd, 0x30, 0xb0, 0x00, 0x1a, 0x66, 0x0e, 0xb6, 0xaa, 0xff, 0xfc, 0x66, 0x08, 0x41, 0xfa, 0xfd, 0xd0, 0x4e, 0xea, 0x00, 0x26, 0x51, 0xc8, 0xff, 0xdc, 0x4a, 0x85, 0x6b, 0x14, 0x20, 0x78, + 0x02, 0xa6, 0x20, 0x50, 0xd0, 0xfc, 0x1f, 0x40, 0x2f, 0x38, 0x01, 0x18, 0xa0, 0x57, 0x21, 0xdf, 0x01, 0x18, 0x70, 0x20, 0xd0, 0x45, 0x38, 0x00, 0x46, 0x40, 0xa0, 0x3d, 0xe5, 0x44, 0x22, 0x78, + 0x01, 0x1c, 0x20, 0x71, 0x40, 0x00, 0x2c, 0x50, 0xa0, 0x29, 0x41, 0xfa, 0xfd, 0xaa, 0x2c, 0x88, 0x3d, 0x58, 0x00, 0x04, 0x2d, 0x58, 0x00, 0x22, 0x3d, 0x58, 0x00, 0x26, 0x08, 0xee, 0x00, 0x05, + 0x00, 0x05, 0x08, 0xae, 0x00, 0x06, 0x00, 0x05, 0x70, 0x01, 0x08, 0x05, 0x00, 0x1e, 0x67, 0x02, 0x70, 0x00, 0x2f, 0x00, 0x2f, 0x0e, 0x4e, 0xba, 0x00, 0x3c, 0x50, 0x8f, 0x4a, 0x40, 0x67, 0x02, + 0x70, 0xe9, 0x4c, 0xdf, 0x7c, 0xfc, 0x4e, 0x75, 0x2f, 0x02, 0x42, 0x67, 0x2f, 0x2f, 0x00, 0x0a, 0x3f, 0x3c, 0x00, 0x01, 0x3f, 0x3c, 0x00, 0x03, 0xa8, 0x95, 0x54, 0x8f, 0x24, 0x1f, 0x4e, 0x75, + 0x2f, 0x02, 0x42, 0x67, 0x2f, 0x2f, 0x00, 0x0a, 0x3f, 0x3c, 0x00, 0x04, 0xa8, 0x95, 0x54, 0x8f, 0x24, 0x1f, 0x4e, 0x75, 0x4e, 0x56, 0xfd, 0xf4, 0x48, 0xe7, 0x3e, 0x30, 0x26, 0x6e, 0x00, 0x08, + 0x24, 0x2e, 0x00, 0x0c, 0x4e, 0xba, 0xfd, 0x66, 0x4a, 0x80, 0x67, 0x0c, 0x4e, 0xba, 0xfd, 0x64, 0x2d, 0x40, 0xff, 0xf6, 0x60, 0x00, 0x00, 0x9a, 0x4e, 0xba, 0x0e, 0xde, 0x28, 0x00, 0x4e, 0xba, + 0x0e, 0xe8, 0x2f, 0x00, 0x4e, 0xba, 0x0e, 0xd8, 0x4e, 0xba, 0x0f, 0x92, 0x26, 0x00, 0x2f, 0x04, 0x4e, 0xba, 0x0e, 0xcc, 0x4a, 0x83, 0x50, 0x8f, 0x66, 0x06, 0x70, 0xff, 0x60, 0x00, 0x02, 0x9c, + 0x2f, 0x03, 0x4e, 0xba, 0x0f, 0x48, 0x2f, 0x03, 0x4e, 0xba, 0xfd, 0x18, 0x4e, 0xba, 0xfd, 0x24, 0x2d, 0x40, 0xff, 0xf6, 0x20, 0x38, 0x02, 0xae, 0x50, 0x80, 0x24, 0x40, 0x1b, 0x52, 0xfc, 0x70, + 0x50, 0x8f, 0x66, 0x06, 0x42, 0x2d, 0xfc, 0x74, 0x60, 0x14, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x74, 0x0c, 0x2d, 0x00, 0x03, 0xfc, 0x70, 0x65, 0x06, 0x1b, 0x7c, 0x00, 0x02, 0xfc, 0x70, 0x42, 0x6d, + 0xfc, 0x88, 0x42, 0xa7, 0x4e, 0xba, 0xfc, 0xf4, 0x42, 0x6d, 0xfc, 0x8c, 0x42, 0x2d, 0xfc, 0x78, 0x78, 0x00, 0x58, 0x8f, 0x60, 0x0e, 0x20, 0x04, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x42, 0x70, + 0x08, 0x00, 0x52, 0x84, 0x70, 0x08, 0xb0, 0x84, 0x62, 0x00, 0xff, 0xec, 0x42, 0x2d, 0xfc, 0x7c, 0x36, 0x2b, 0x00, 0x18, 0x46, 0x43, 0x04, 0x43, 0x00, 0x20, 0x72, 0x00, 0x32, 0x03, 0x20, 0x01, + 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x45, 0xed, 0xfc, 0xa0, 0xd5, 0xc1, 0x4e, 0xba, 0xfc, 0x94, 0x27, 0x40, 0x00, 0x14, 0x42, 0xa7, + 0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x4e, 0xba, 0x07, 0x62, 0x42, 0x46, 0x42, 0x6e, 0xff, 0xfe, 0x7a, 0x01, 0x2d, 0x45, 0xff, 0xfa, 0x78, 0x01, 0x50, 0x8f, 0x60, 0x00, 0x01, 0x50, 0x48, 0x6e, + 0xfd, 0xf6, 0x2f, 0x04, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x4e, 0xba, 0x08, 0x58, 0x3a, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x67, 0x08, 0x0c, 0x45, 0xff, 0xfc, + 0x66, 0x00, 0x01, 0x28, 0x0c, 0x6e, 0x50, 0x4d, 0xfd, 0xf6, 0x66, 0x00, 0x01, 0x1e, 0x70, 0x01, 0xb0, 0x84, 0x66, 0x06, 0x2d, 0x6e, 0xfd, 0xfa, 0xff, 0xfa, 0x4a, 0x6e, 0xff, 0xfe, 0x66, 0x4e, + 0x41, 0xee, 0xfe, 0x26, 0x0c, 0x90, 0x41, 0x70, 0x70, 0x6c, 0x66, 0x42, 0x41, 0xee, 0xfe, 0x2a, 0x0c, 0x90, 0x65, 0x5f, 0x44, 0x72, 0x66, 0x36, 0x0c, 0xae, 0x00, 0x01, 0x06, 0x00, 0xfe, 0x7e, + 0x66, 0x2c, 0x72, 0x01, 0x3d, 0x41, 0xff, 0xfe, 0x70, 0x00, 0x30, 0x03, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x31, 0xae, 0xfe, 0x8c, 0x08, 0x00, 0x35, 0x6e, 0xfe, 0x84, 0x00, 0x2c, 0x15, 0x6e, + 0xfe, 0x89, 0x00, 0x25, 0x15, 0x6e, 0xfe, 0x8b, 0x00, 0x27, 0x60, 0x00, 0x00, 0xbe, 0x4a, 0x46, 0x66, 0x00, 0x00, 0xb8, 0x41, 0xee, 0xfe, 0x26, 0x0c, 0x90, 0x41, 0x70, 0x70, 0x6c, 0x66, 0x00, + 0x00, 0xaa, 0x41, 0xee, 0xfe, 0x2a, 0x0c, 0x90, 0x65, 0x5f, 0x48, 0x46, 0x66, 0x00, 0x00, 0x9c, 0x7c, 0x01, 0x7a, 0x08, 0x20, 0x78, 0x03, 0x0a, 0x60, 0x0e, 0x30, 0x28, 0x00, 0x06, 0xb0, 0x45, + 0x66, 0x04, 0x52, 0x45, 0x60, 0xee, 0x20, 0x50, 0x20, 0x08, 0x66, 0x00, 0xff, 0xee, 0x42, 0x52, 0x08, 0x2e, 0x00, 0x05, 0xfe, 0x51, 0x67, 0x04, 0x70, 0x00, 0x60, 0x06, 0x20, 0x3c, 0x00, 0x00, + 0x00, 0x80, 0x15, 0x40, 0x00, 0x02, 0x15, 0x7c, 0x00, 0x08, 0x00, 0x03, 0x15, 0x7c, 0x00, 0x01, 0x00, 0x04, 0x42, 0x2a, 0x00, 0x05, 0x35, 0x7c, 0x00, 0x01, 0x00, 0x0a, 0x42, 0xaa, 0x00, 0x06, + 0x42, 0x6a, 0x00, 0x10, 0x35, 0x6e, 0xfe, 0x4c, 0x00, 0x12, 0x20, 0x2e, 0xfe, 0x4a, 0x72, 0x10, 0xe2, 0xa8, 0x35, 0x40, 0x00, 0x14, 0x20, 0x2e, 0xfe, 0x46, 0xd0, 0xae, 0xfd, 0xfe, 0x25, 0x40, + 0x00, 0x18, 0x25, 0x40, 0x00, 0x1c, 0x25, 0x6e, 0xfe, 0x4a, 0x00, 0x20, 0x35, 0x45, 0x00, 0x16, 0x48, 0x6a, 0x00, 0x06, 0x70, 0x00, 0x30, 0x05, 0x2f, 0x00, 0x30, 0x2b, 0x00, 0x18, 0x48, 0xc0, + 0x2f, 0x00, 0x4e, 0xba, 0x0c, 0x98, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x84, 0xb8, 0xae, 0xff, 0xfa, 0x63, 0x00, 0xfe, 0xac, 0x4a, 0x6e, 0xff, 0xfe, 0x66, 0x06, 0x7a, 0xfe, 0x60, 0x00, 0x00, 0x6e, + 0x4a, 0x46, 0x66, 0x04, 0x42, 0x45, 0x60, 0x64, 0x70, 0x00, 0x30, 0x03, 0x41, 0xed, 0xfc, 0x80, 0x11, 0xbc, 0x00, 0x01, 0x08, 0x00, 0x70, 0x01, 0x12, 0x03, 0xe3, 0xa0, 0x36, 0x00, 0x4a, 0x82, + 0x67, 0x06, 0x30, 0x03, 0x81, 0x6d, 0xfc, 0x8c, 0x4a, 0x6d, 0xfc, 0x88, 0x66, 0x28, 0x3b, 0x7c, 0x00, 0x01, 0xfc, 0x64, 0x41, 0xfa, 0x04, 0x58, 0x2b, 0x48, 0xfc, 0x66, 0x3b, 0x7c, 0x00, 0x65, + 0xfc, 0x6a, 0x42, 0x6d, 0xfc, 0x6c, 0x48, 0x6d, 0xfc, 0x60, 0x4e, 0xba, 0x0c, 0xc0, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x7c, 0x58, 0x8f, 0x30, 0x03, 0x81, 0x6d, 0xfc, 0x88, 0x70, 0x00, 0x30, 0x2d, + 0xfc, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0xfa, 0xb4, 0x42, 0x45, 0x58, 0x8f, 0x2f, 0x2e, 0xff, 0xf6, 0x4e, 0xba, 0x0c, 0xe4, 0x30, 0x05, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xee, 0x0c, 0x7c, 0xfd, 0xd8, + 0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x38, 0x20, 0x24, 0x6f, 0x00, 0x14, 0x20, 0x2f, 0x00, 0x18, 0x4e, 0xba, 0xfa, 0x80, 0x28, 0x00, 0x34, 0x2a, 0x00, 0x18, 0x46, 0x42, 0x04, 0x42, 0x00, 0x20, + 0x70, 0x01, 0x12, 0x02, 0xe3, 0xa0, 0x46, 0x40, 0xc1, 0x6d, 0xfc, 0x88, 0x70, 0x00, 0x30, 0x2d, 0xfc, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0xfa, 0x64, 0x36, 0x2d, 0xfc, 0x88, 0x48, 0x78, 0x03, 0x08, + 0x32, 0x02, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0x48, 0x70, 0x18, 0x06, 0x4e, 0xba, + 0x0c, 0x04, 0x4a, 0x43, 0x4f, 0xef, 0x00, 0x0c, 0x66, 0x22, 0x4a, 0x2d, 0xfc, 0x7c, 0x67, 0x0a, 0x48, 0x6d, 0xfc, 0x60, 0x4e, 0xba, 0x0c, 0x20, 0x58, 0x8f, 0x4a, 0x2d, 0xfc, 0x78, 0x67, 0x0c, + 0x45, 0xfa, 0x04, 0x40, 0x2f, 0x0a, 0x4e, 0xba, 0xfc, 0x78, 0x58, 0x8f, 0x2f, 0x04, 0x4e, 0xba, 0x0c, 0x46, 0x4a, 0x43, 0x58, 0x8f, 0x66, 0x2a, 0x4e, 0xba, 0x0b, 0x7e, 0x24, 0x00, 0x4e, 0xba, + 0x0b, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0x0b, 0x78, 0x4e, 0xba, 0xf9, 0xe2, 0x2f, 0x00, 0x4e, 0xba, 0x0c, 0x3c, 0x2f, 0x02, 0x4e, 0xba, 0x0b, 0x68, 0x42, 0xa7, 0x4e, 0xba, 0xf9, 0xc6, 0x4f, 0xef, + 0x00, 0x10, 0x70, 0x00, 0x4c, 0xdf, 0x04, 0x1c, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf0, 0x48, 0xe7, 0x3e, 0x38, 0x24, 0x2e, 0x00, 0x08, 0x24, 0x6e, 0x00, 0x0c, 0x4e, 0xba, 0xf9, 0xb6, 0x2d, 0x40, + 0xff, 0xf2, 0x28, 0x42, 0x3c, 0x2c, 0x00, 0x18, 0x46, 0x46, 0x04, 0x46, 0x00, 0x20, 0x32, 0x06, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, + 0x42, 0x40, 0xd2, 0x80, 0x47, 0xed, 0xfc, 0xa0, 0xd7, 0xc1, 0x78, 0x00, 0x0c, 0x2a, 0x00, 0x03, 0x00, 0x07, 0x57, 0xc4, 0x44, 0x04, 0x28, 0x42, 0x2d, 0x6c, 0x00, 0x10, 0xff, 0xfc, 0x70, 0x09, + 0x26, 0x2e, 0xff, 0xfc, 0xe0, 0xa3, 0x2d, 0x43, 0xff, 0xfc, 0x2d, 0x6a, 0x00, 0x24, 0xff, 0xf8, 0x70, 0x09, 0x26, 0x2e, 0xff, 0xf8, 0xe0, 0xa3, 0x2d, 0x43, 0xff, 0xf8, 0x30, 0x2b, 0x00, 0x16, + 0xb0, 0x6a, 0x00, 0x16, 0x67, 0x06, 0x76, 0xc8, 0x60, 0x00, 0x00, 0xe4, 0x20, 0x2a, 0x00, 0x24, 0x02, 0x80, 0x00, 0x00, 0x01, 0xff, 0x66, 0x00, 0x00, 0x18, 0x4a, 0xab, 0x00, 0x1c, 0x67, 0x16, + 0x20, 0x2b, 0x00, 0x20, 0x22, 0x2e, 0xff, 0xf8, 0xd2, 0xae, 0xff, 0xfc, 0xb0, 0x81, 0x64, 0x06, 0x76, 0xce, 0x60, 0x00, 0x00, 0xba, 0x30, 0x06, 0x48, 0xc0, 0x41, 0xed, 0xfc, 0x80, 0x4a, 0x30, + 0x08, 0x00, 0x67, 0x20, 0x30, 0x06, 0x48, 0xc0, 0x41, 0xed, 0xfc, 0x80, 0x42, 0x30, 0x08, 0x00, 0x70, 0x00, 0x10, 0x2d, 0xfc, 0x74, 0x2f, 0x00, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, + 0x03, 0xca, 0x50, 0x8f, 0x08, 0x2a, 0x00, 0x06, 0x00, 0x2d, 0x67, 0x0a, 0x4a, 0x44, 0x66, 0x06, 0x42, 0x43, 0x60, 0x00, 0x00, 0x6c, 0x42, 0x6e, 0xff, 0xf6, 0x7a, 0xff, 0x2f, 0x2a, 0x00, 0x20, + 0x20, 0x2b, 0x00, 0x1c, 0xd0, 0xae, 0xff, 0xfc, 0x2f, 0x00, 0x2f, 0x2e, 0xff, 0xf8, 0x30, 0x04, 0x48, 0xc0, 0x2f, 0x00, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, 0x04, 0xae, 0x36, 0x00, + 0x4f, 0xef, 0x00, 0x14, 0x67, 0x3a, 0x53, 0x43, 0x66, 0x10, 0x2f, 0x2b, 0x00, 0x28, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, 0x07, 0x42, 0x50, 0x8f, 0xba, 0xab, 0x00, 0x28, 0x66, 0x06, + 0x52, 0x6e, 0xff, 0xf6, 0x60, 0x08, 0x42, 0x6e, 0xff, 0xf6, 0x2a, 0x2b, 0x00, 0x28, 0x0c, 0x6e, 0x00, 0x04, 0xff, 0xf6, 0x6d, 0x00, 0xff, 0xa6, 0x76, 0xdc, 0x42, 0xaa, 0x00, 0x28, 0x60, 0x0e, + 0x20, 0x2a, 0x00, 0x24, 0x25, 0x40, 0x00, 0x28, 0x20, 0x42, 0xd1, 0xa8, 0x00, 0x10, 0x2f, 0x2e, 0xff, 0xf2, 0x4e, 0xba, 0x0a, 0xa2, 0x30, 0x03, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xee, 0x1c, 0x7c, + 0xff, 0xd0, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xfd, 0xfc, 0x48, 0xe7, 0x3c, 0x30, 0x26, 0x6e, 0x00, 0x08, 0x24, 0x6e, 0x00, 0x0c, 0x4e, 0xba, 0xf8, 0x3a, 0x2a, 0x00, 0x38, 0x2b, 0x00, 0x18, + 0x46, 0x44, 0x04, 0x44, 0x00, 0x20, 0x30, 0x04, 0x48, 0xc0, 0x24, 0x00, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x42, 0xc4, 0xfc, 0x00, 0x6c, 0x48, 0x42, 0x42, 0x42, 0xd0, 0x82, 0x41, 0xed, 0xfc, 0xa0, + 0xd1, 0xc0, 0x42, 0x43, 0x0c, 0x6a, 0x00, 0x41, 0x00, 0x1a, 0x66, 0x44, 0x4a, 0x2d, 0xfc, 0x78, 0x66, 0x34, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x9f, 0x4e, 0xba, 0x09, 0xda, 0x24, 0x00, + 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x95, 0x4e, 0xba, 0x09, 0xcc, 0xb4, 0x80, 0x4f, 0xef, 0x00, 0x10, 0x67, 0x12, 0x45, 0xfa, 0x02, 0x0c, 0x2f, 0x0a, 0x4e, 0xba, 0xfa, 0x2c, 0x1b, 0x7c, + 0x00, 0x01, 0xfc, 0x78, 0x58, 0x8f, 0x02, 0x6b, 0x4f, 0xff, 0x00, 0x04, 0x60, 0x00, 0x00, 0xb2, 0x30, 0x28, 0x00, 0x16, 0xb0, 0x6a, 0x00, 0x16, 0x67, 0x06, 0x76, 0xc8, 0x60, 0x00, 0x00, 0xa2, + 0x30, 0x2a, 0x00, 0x1a, 0x0c, 0x40, 0x00, 0x07, 0x6d, 0x06, 0x6e, 0x1e, 0x60, 0x00, 0x00, 0x7e, 0x0c, 0x40, 0x00, 0x05, 0x6d, 0x00, 0x00, 0x88, 0x6e, 0x04, 0x60, 0x00, 0x00, 0x84, 0x0c, 0x40, + 0x00, 0x06, 0x66, 0x00, 0x00, 0x7a, 0x60, 0x00, 0x00, 0x78, 0x0c, 0x40, 0x00, 0x11, 0x6d, 0x00, 0x00, 0x6e, 0x6e, 0x02, 0x60, 0x08, 0x0c, 0x40, 0x00, 0x15, 0x66, 0x62, 0x60, 0x18, 0x43, 0xea, + 0x00, 0x1c, 0x0c, 0x51, 0x00, 0x01, 0x66, 0x06, 0x42, 0xa8, 0x00, 0x1c, 0x60, 0x52, 0x21, 0x68, 0x00, 0x18, 0x00, 0x1c, 0x60, 0x4a, 0x4e, 0xba, 0xf7, 0x6c, 0x2d, 0x40, 0xff, 0xfc, 0x20, 0x2e, + 0xff, 0xfc, 0x06, 0x80, 0x00, 0x00, 0x01, 0x00, 0x5c, 0x80, 0x32, 0x04, 0x48, 0xc1, 0x74, 0x30, 0xd2, 0x82, 0x26, 0x40, 0x16, 0x81, 0x48, 0x78, 0x00, 0x04, 0x48, 0x6e, 0xff, 0xfc, 0x48, 0x6a, + 0x00, 0x1c, 0x4e, 0xba, 0x08, 0x02, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x14, 0x70, 0x00, 0x30, 0x28, 0x00, 0x16, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x07, 0x4e, 0xba, 0x08, 0xdc, 0x50, 0x8f, 0x76, 0xef, + 0x2f, 0x05, 0x4e, 0xba, 0x09, 0x52, 0x34, 0x03, 0x48, 0xc2, 0x58, 0x8f, 0x20, 0x02, 0x4c, 0xee, 0x0c, 0x3c, 0xfd, 0xe4, 0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x30, 0x30, 0x26, 0x6f, 0x00, 0x14, + 0x24, 0x6f, 0x00, 0x18, 0x42, 0x42, 0x4e, 0xba, 0xf6, 0xea, 0x26, 0x00, 0x32, 0x2b, 0x00, 0x18, 0x46, 0x41, 0x04, 0x41, 0x00, 0x20, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, + 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0xd1, 0xc1, 0x30, 0x28, 0x00, 0x16, 0xb0, 0x6a, 0x00, 0x16, 0x67, 0x04, 0x74, 0xc8, 0x60, 0x1e, 0x30, 0x2a, + 0x00, 0x1a, 0x51, 0x40, 0x66, 0x14, 0x48, 0x78, 0x00, 0x16, 0x48, 0x50, 0x48, 0x6a, 0x00, 0x1c, 0x4e, 0xba, 0x07, 0x74, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x02, 0x74, 0xee, 0x2f, 0x03, 0x4e, 0xba, + 0x08, 0xd6, 0x30, 0x02, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xdf, 0x0c, 0x0c, 0x4e, 0x75, 0x48, 0xe7, 0x38, 0x00, 0x4e, 0xba, 0xf6, 0x7e, 0x28, 0x00, 0x3b, 0x7c, 0x00, 0x65, 0xfc, 0x6a, 0x4a, 0x6d, + 0xfc, 0x8c, 0x67, 0x58, 0x36, 0x3c, 0x00, 0x80, 0x74, 0x07, 0x72, 0x00, 0x32, 0x2d, 0xfc, 0x8c, 0x30, 0x03, 0x48, 0xc0, 0xc2, 0x80, 0x67, 0x38, 0x32, 0x02, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, + 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0x70, 0x00, 0x30, 0x30, 0x18, 0x16, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x07, 0x4e, 0xba, + 0x07, 0xf6, 0x4a, 0x80, 0x50, 0x8f, 0x66, 0x08, 0x30, 0x03, 0x46, 0x40, 0xc1, 0x6d, 0xfc, 0x8c, 0x30, 0x03, 0xe2, 0x40, 0x36, 0x00, 0x53, 0x42, 0x6c, 0x00, 0xff, 0xb0, 0x4a, 0x2d, 0xfc, 0x74, + 0x66, 0x32, 0x22, 0x38, 0x02, 0xae, 0x20, 0x38, 0x0c, 0x54, 0x02, 0x80, 0x00, 0xff, 0xff, 0xff, 0xb2, 0x80, 0x63, 0x20, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x74, 0x42, 0x42, 0x30, 0x02, 0x48, 0xc0, + 0x41, 0xed, 0xfc, 0x80, 0x11, 0xbc, 0x00, 0x01, 0x08, 0x00, 0x52, 0x42, 0x0c, 0x42, 0x00, 0x08, 0x6d, 0x00, 0xff, 0xea, 0x2f, 0x04, 0x4e, 0xba, 0x08, 0x1e, 0x58, 0x8f, 0x4c, 0xdf, 0x00, 0x1c, + 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf8, 0x48, 0xe7, 0x3c, 0x00, 0x4e, 0xba, 0xf5, 0xc6, 0x2a, 0x00, 0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x1b, 0x48, 0x6e, 0xff, 0xfa, 0x4e, 0xba, 0x06, 0xa2, + 0x70, 0x01, 0x12, 0x2d, 0xfc, 0x70, 0xe3, 0xa0, 0x38, 0x00, 0x74, 0x00, 0x4f, 0xef, 0x00, 0x0c, 0x20, 0x02, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x36, 0x30, 0x08, 0x00, 0x70, 0x00, 0x30, 0x04, + 0x72, 0x00, 0x32, 0x03, 0xc0, 0x81, 0x67, 0x46, 0x4a, 0x82, 0x67, 0x00, 0x00, 0x0e, 0x70, 0x00, 0x30, 0x03, 0x02, 0x80, 0x00, 0x00, 0x40, 0x00, 0x66, 0x34, 0x70, 0x00, 0x30, 0x03, 0x02, 0x80, + 0x00, 0x00, 0x80, 0x00, 0x67, 0x08, 0x1d, 0x7c, 0x00, 0x01, 0xff, 0xfb, 0x60, 0x04, 0x42, 0x2e, 0xff, 0xfb, 0x48, 0x78, 0x03, 0x84, 0x42, 0xa7, 0x42, 0xa7, 0x42, 0xa7, 0x2f, 0x02, 0x48, 0x78, + 0x00, 0x06, 0x48, 0x6e, 0xff, 0xfa, 0x4e, 0xba, 0x02, 0x70, 0x4f, 0xef, 0x00, 0x1c, 0x52, 0x82, 0x70, 0x08, 0xb0, 0x82, 0x6e, 0x00, 0xff, 0x9a, 0x2f, 0x05, 0x4e, 0xba, 0x07, 0x7a, 0x58, 0x8f, + 0x4c, 0xee, 0x00, 0x3c, 0xff, 0xe8, 0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x30, 0x00, 0x22, 0x2f, 0x00, 0x0c, 0x26, 0x2f, 0x00, 0x10, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, + 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0xd1, 0xc1, 0x22, 0x48, 0x70, 0x01, 0xb0, 0x83, 0x66, 0x1a, 0x70, 0x00, 0x10, 0x29, 0x00, 0x27, 0x72, 0x01, 0x14, 0x2d, + 0xfc, 0x70, 0xe5, 0xa1, 0xc0, 0x81, 0x67, 0x08, 0x13, 0x7c, 0x00, 0x01, 0x00, 0x26, 0x60, 0x04, 0x42, 0x29, 0x00, 0x26, 0x33, 0x7c, 0x00, 0x01, 0x00, 0x30, 0x4a, 0x83, 0x67, 0x00, 0x00, 0x08, + 0x4a, 0x29, 0x00, 0x25, 0x66, 0x16, 0x42, 0x29, 0x00, 0x24, 0x23, 0x7c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x3a, 0x60, 0x00, 0x00, 0xaa, 0x13, 0x69, 0x00, 0x25, + 0x00, 0x24, 0x70, 0x00, 0x10, 0x29, 0x00, 0x24, 0x0c, 0x40, 0x00, 0x01, 0x6d, 0x00, 0x00, 0x96, 0x6e, 0x02, 0x60, 0x08, 0x55, 0x40, 0x66, 0x00, 0x00, 0x8c, 0x60, 0x22, 0x23, 0x7c, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x05, 0x00, 0x3a, 0x70, 0xf6, 0x23, 0x40, 0x00, 0x3c, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x44, 0x33, 0x7c, 0x00, 0x01, 0x00, 0x2e, 0x60, 0x66, 0x70, 0x00, + 0x30, 0x29, 0x00, 0x2c, 0x23, 0x40, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x04, 0x00, 0x3a, 0x41, 0xe9, 0x00, 0x32, 0x23, 0x48, 0x00, 0x3c, 0x41, 0xe9, 0x00, 0x46, 0x23, 0x48, 0x00, 0x40, 0x33, 0x7c, + 0x00, 0x01, 0x00, 0x44, 0x20, 0x3c, 0x00, 0x00, 0x02, 0x00, 0x72, 0x00, 0x32, 0x29, 0x00, 0x2c, 0x90, 0x81, 0x23, 0x40, 0x00, 0x4a, 0x33, 0x7c, 0x00, 0x04, 0x00, 0x4e, 0x41, 0xe9, 0x00, 0x46, + 0x23, 0x48, 0x00, 0x50, 0x41, 0xe9, 0x00, 0x32, 0x23, 0x48, 0x00, 0x54, 0x33, 0x7c, 0x00, 0x05, 0x00, 0x58, 0x70, 0xd8, 0x23, 0x40, 0x00, 0x5a, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x62, 0x33, 0x7c, + 0x00, 0x04, 0x00, 0x2e, 0x4c, 0xdf, 0x00, 0x0c, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf0, 0x48, 0xe7, 0x3c, 0x20, 0x38, 0x2e, 0x00, 0x0a, 0x36, 0x2e, 0x00, 0x0e, 0x24, 0x2e, 0x00, 0x10, 0x4a, 0x43, + 0x67, 0x04, 0x70, 0x0a, 0x60, 0x02, 0x70, 0x08, 0x48, 0x78, 0x00, 0x06, 0x2f, 0x00, 0x48, 0x6e, 0xff, 0xf2, 0x4e, 0xba, 0x04, 0xcc, 0x72, 0x00, 0x32, 0x04, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, + 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x45, 0xed, 0xfc, 0xa0, 0xd5, 0xc1, 0x25, 0x6e, 0x00, 0x18, 0x00, 0x32, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x00, 0x00, 0xc8, + 0x20, 0x2e, 0x00, 0x14, 0x72, 0x10, 0xe2, 0xa8, 0x1d, 0x40, 0xff, 0xf3, 0x41, 0xee, 0xff, 0xf4, 0x30, 0xae, 0x00, 0x16, 0x4a, 0x2a, 0x00, 0x24, 0x66, 0x04, 0x7a, 0x01, 0x60, 0x28, 0x0c, 0x82, + 0x00, 0x00, 0x01, 0x00, 0x63, 0x08, 0x2a, 0x3c, 0x00, 0x00, 0x01, 0x00, 0x60, 0x02, 0x2a, 0x02, 0x72, 0x00, 0x32, 0x2a, 0x00, 0x2e, 0xd2, 0x81, 0x20, 0x01, 0xe5, 0x81, 0xd2, 0x80, 0x20, 0x05, + 0x2a, 0x00, 0x25, 0x80, 0x18, 0x36, 0x1d, 0x45, 0xff, 0xf6, 0x48, 0x78, 0x2a, 0x30, 0x70, 0x00, 0x10, 0x2a, 0x00, 0x26, 0x2f, 0x00, 0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x48, 0x6a, 0x00, 0x30, + 0x70, 0x00, 0x30, 0x04, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x06, 0x48, 0x6e, 0xff, 0xf2, 0x4e, 0xba, 0x00, 0x68, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x1c, 0x67, 0x44, 0x0c, 0x40, 0x00, 0x02, 0x67, 0x04, + 0x48, 0xc0, 0x60, 0x4a, 0x48, 0x78, 0x00, 0x08, 0x48, 0x6e, 0xff, 0xf8, 0x70, 0x00, 0x30, 0x04, 0x2f, 0x00, 0x4e, 0xba, 0x01, 0x58, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x0c, 0x67, 0x04, 0x70, 0xf9, + 0x60, 0x2c, 0x48, 0x78, 0x00, 0x04, 0x48, 0x6e, 0xff, 0xfb, 0x48, 0x6a, 0x00, 0x28, 0x4e, 0xba, 0x03, 0xd6, 0x70, 0x00, 0x10, 0x2e, 0xff, 0xfa, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x10, 0x94, 0x85, + 0x20, 0x05, 0xd1, 0xae, 0x00, 0x14, 0x4a, 0x82, 0x62, 0x00, 0xff, 0x36, 0x70, 0x00, 0x4c, 0xee, 0x04, 0x3c, 0xff, 0xdc, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf8, 0x48, 0xe7, 0x3e, 0x00, + 0x26, 0x2e, 0x00, 0x08, 0x38, 0x2e, 0x00, 0x0e, 0x3a, 0x2e, 0x00, 0x12, 0x42, 0x46, 0x42, 0x6e, 0xff, 0xfe, 0x55, 0x8f, 0x4e, 0xba, 0x03, 0xcc, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x1e, + 0x52, 0x6e, 0xff, 0xfe, 0x0c, 0x6e, 0x00, 0x03, 0xff, 0xfe, 0x6f, 0x00, 0xff, 0xe6, 0x55, 0x8f, 0x4e, 0xba, 0x04, 0x00, 0x30, 0x1f, 0x08, 0x00, 0x00, 0x06, 0x67, 0xd2, 0x60, 0xf0, 0x55, 0x8f, + 0x3f, 0x05, 0x4e, 0xba, 0x03, 0xa8, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x06, 0x70, 0xfe, 0x60, 0x00, 0x00, 0xb0, 0x42, 0x6e, 0xff, 0xfe, 0x55, 0x8f, 0x2f, 0x03, 0x3f, 0x04, 0x4e, 0xba, + 0x03, 0x96, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x12, 0x52, 0x6e, 0xff, 0xfe, 0x0c, 0x6e, 0x00, 0x03, 0xff, 0xfe, 0x6f, 0x00, 0xff, 0xe2, 0x7c, 0xfd, 0x60, 0x52, 0x4a, 0xae, 0x00, 0x14, + 0x67, 0x4c, 0x4a, 0x6e, 0x00, 0x1e, 0x67, 0x24, 0x4a, 0x6e, 0x00, 0x1a, 0x67, 0x0c, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x92, 0x60, 0x0a, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, + 0x4e, 0xba, 0x03, 0x7c, 0x30, 0x1f, 0x48, 0xc0, 0x2c, 0x00, 0x60, 0x22, 0x4a, 0x6e, 0x00, 0x1a, 0x67, 0x0c, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x5a, 0x60, 0x0a, 0x55, 0x8f, + 0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x44, 0x30, 0x1f, 0x48, 0xc0, 0x2c, 0x00, 0x55, 0x8f, 0x48, 0x6e, 0xff, 0xfa, 0x48, 0x6e, 0xff, 0xfc, 0x2f, 0x2e, 0x00, 0x20, 0x4e, 0xba, 0x03, 0x22, + 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x04, 0x70, 0xfc, 0x60, 0x16, 0x4a, 0x46, 0x67, 0x0c, 0x0c, 0x6e, 0x00, 0x02, 0xff, 0xfa, 0x67, 0x04, 0x30, 0x06, 0x60, 0x04, 0x30, 0x2e, 0xff, 0xfa, + 0x48, 0xc0, 0x4c, 0xee, 0x00, 0x7c, 0xff, 0xe4, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xe4, 0x48, 0xe7, 0x38, 0x00, 0x36, 0x2e, 0x00, 0x0a, 0x28, 0x2e, 0x00, 0x0c, 0x34, 0x2e, 0x00, 0x12, + 0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x03, 0x48, 0x6e, 0xff, 0xe6, 0x4e, 0xba, 0x02, 0x92, 0x1d, 0x42, 0xff, 0xea, 0x3d, 0x7c, 0x00, 0x02, 0xff, 0xec, 0x2d, 0x44, 0xff, 0xee, 0x70, 0x00, + 0x30, 0x02, 0x2d, 0x40, 0xff, 0xf2, 0x3d, 0x7c, 0x00, 0x07, 0xff, 0xf6, 0x48, 0x78, 0x02, 0x58, 0x42, 0xa7, 0x42, 0xa7, 0x48, 0x6e, 0xff, 0xec, 0x30, 0x03, 0x48, 0xc0, 0x2f, 0x00, 0x48, 0x78, + 0x00, 0x06, 0x48, 0x6e, 0xff, 0xe6, 0x4e, 0xba, 0xfe, 0x90, 0x4f, 0xef, 0x00, 0x28, 0x4c, 0xee, 0x00, 0x1c, 0xff, 0xd8, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf4, 0x48, 0xe7, 0x3e, 0x30, + 0x24, 0x2e, 0x00, 0x08, 0x26, 0x2e, 0x00, 0x0c, 0x4e, 0xba, 0x02, 0xce, 0x2d, 0x40, 0xff, 0xf6, 0x4e, 0xba, 0x02, 0xd6, 0x2f, 0x00, 0x4e, 0xba, 0x02, 0xc6, 0x48, 0x78, 0x04, 0x00, 0x4e, 0xba, + 0x02, 0xd8, 0x24, 0x40, 0xb4, 0xfc, 0x00, 0x00, 0x50, 0x8f, 0x66, 0x1e, 0x4e, 0xba, 0x02, 0xc2, 0x2f, 0x00, 0x4e, 0xba, 0x02, 0xaa, 0x48, 0x78, 0x04, 0x00, 0x4e, 0xba, 0x02, 0xbc, 0x24, 0x40, + 0xb4, 0xfc, 0x00, 0x00, 0x50, 0x8f, 0x67, 0x00, 0x01, 0x16, 0x2f, 0x0a, 0x4e, 0xba, 0x02, 0xbe, 0x26, 0x52, 0x20, 0x4b, 0x41, 0xe8, 0x02, 0x00, 0x2d, 0x48, 0xff, 0xfc, 0x42, 0x6e, 0xff, 0xfa, + 0x58, 0x8f, 0x42, 0x45, 0x42, 0x46, 0x42, 0x44, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0xd2, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, + 0x67, 0x20, 0x53, 0x40, 0x66, 0x18, 0x52, 0x46, 0xba, 0x44, 0x66, 0x12, 0x48, 0x78, 0x02, 0x00, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x0b, 0x4e, 0xba, 0x01, 0x8e, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x45, + 0x60, 0x12, 0x48, 0x78, 0x02, 0x00, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x0b, 0x4e, 0xba, 0x01, 0x78, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x44, 0x0c, 0x44, 0x00, 0x0a, 0x6d, 0x00, 0xff, 0xac, 0x4a, 0x45, + 0x67, 0x00, 0x00, 0x9c, 0x4a, 0x46, 0x67, 0x00, 0x00, 0x96, 0x42, 0x44, 0x2f, 0x0b, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x01, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x6e, 0x30, 0x00, + 0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, 0x00, 0x28, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x52, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, + 0x66, 0x00, 0x00, 0x0c, 0x52, 0x44, 0x0c, 0x44, 0x00, 0x0a, 0x6c, 0x52, 0x60, 0xbe, 0x52, 0x6e, 0xff, 0xfa, 0x0c, 0x6e, 0x00, 0x02, 0xff, 0xfa, 0x6e, 0x00, 0x00, 0x44, 0x2f, 0x03, 0x2f, 0x02, + 0x4e, 0xba, 0x00, 0x56, 0x50, 0x8f, 0x2f, 0x0b, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x01, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x14, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, + 0x00, 0x1e, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfb, 0xf8, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, 0xff, 0x06, 0x2f, 0x0a, + 0x4e, 0xba, 0x01, 0xa0, 0x2f, 0x2e, 0xff, 0xf6, 0x4e, 0xba, 0x01, 0x74, 0x50, 0x8f, 0x4c, 0xee, 0x0c, 0x7c, 0xff, 0xd8, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xd4, 0x48, 0xe7, 0x30, 0x00, + 0x34, 0x2e, 0x00, 0x0a, 0x26, 0x2e, 0x00, 0x0c, 0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x07, 0x48, 0x6e, 0xff, 0xde, 0x4e, 0xba, 0x00, 0xaa, 0x48, 0x78, 0x00, 0x08, 0x42, 0xa7, 0x48, 0x6e, + 0xff, 0xd6, 0x4e, 0xba, 0x00, 0x9c, 0x1d, 0x7c, 0x00, 0x04, 0xff, 0xd9, 0x3d, 0x7c, 0x00, 0x02, 0xff, 0xec, 0x41, 0xee, 0xff, 0xd6, 0x2d, 0x48, 0xff, 0xee, 0x70, 0x08, 0x2d, 0x40, 0xff, 0xf2, + 0x3d, 0x7c, 0x00, 0x07, 0xff, 0xf6, 0x41, 0xee, 0xff, 0xda, 0x20, 0x83, 0x48, 0x78, 0x0e, 0x10, 0x42, 0xa7, 0x48, 0x78, 0x00, 0x01, 0x48, 0x6e, 0xff, 0xec, 0x30, 0x02, 0x48, 0xc0, 0x2f, 0x00, + 0x48, 0x78, 0x00, 0x06, 0x48, 0x6e, 0xff, 0xde, 0x4e, 0xba, 0xfc, 0x8e, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x34, 0x67, 0x26, 0x48, 0x78, 0x00, 0x08, 0x48, 0x6e, 0xff, 0xe4, 0x30, 0x02, 0x48, 0xc0, + 0x2f, 0x00, 0x4e, 0xba, 0xfd, 0x88, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x0c, 0x67, 0x04, 0x70, 0xff, 0x60, 0x0a, 0x70, 0x00, 0x10, 0x2e, 0xff, 0xe6, 0x60, 0x02, 0x70, 0x00, 0x4c, 0xee, 0x00, 0x0c, + 0xff, 0xcc, 0x4e, 0x5e, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0x22, 0x6f, 0x00, 0x08, 0x20, 0x2f, 0x00, 0x0c, 0x60, 0x04, 0x10, 0xd9, 0x53, 0x80, 0x4a, 0x80, 0x66, 0x00, 0xff, 0xf8, 0x4e, 0x75, + 0x22, 0x6f, 0x00, 0x04, 0x10, 0x2f, 0x00, 0x0b, 0x32, 0x2f, 0x00, 0x0e, 0x48, 0xc1, 0x60, 0x06, 0x20, 0x41, 0xd1, 0xc9, 0x42, 0x10, 0x53, 0x81, 0x4a, 0x81, 0x6c, 0x00, 0xff, 0xf4, 0x12, 0x80, + 0x4e, 0x75, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x01, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x02, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x03, 0x2f, 0x08, 0xac, 0x15, + 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x04, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x05, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x06, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, + 0x3f, 0x3c, 0x00, 0x08, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x09, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x0a, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x6f, 0x00, 0x0c, + 0x30, 0x2f, 0x00, 0x0a, 0x48, 0x40, 0x30, 0x2f, 0x00, 0x06, 0xa0, 0x4e, 0x4e, 0x75, 0x70, 0x00, 0x31, 0xc0, 0x02, 0x20, 0x20, 0x08, 0x4e, 0x75, 0xa1, 0x1a, 0x4e, 0xfa, 0xff, 0xf4, 0x20, 0x6f, + 0x00, 0x04, 0xa0, 0x1b, 0x4e, 0xfa, 0xff, 0xea, 0x20, 0x78, 0x02, 0xa6, 0x4e, 0xfa, 0xff, 0xe0, 0x20, 0x78, 0x02, 0xaa, 0x4e, 0xfa, 0xff, 0xd8, 0x20, 0x2f, 0x00, 0x04, 0xa1, 0x22, 0x4e, 0xfa, + 0xff, 0xd0, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x23, 0x4e, 0xfa, 0xff, 0xc6, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x29, 0x4e, 0xfa, 0xff, 0xbc, 0x30, 0x6f, 0x00, 0x06, 0x20, 0x2f, 0x00, 0x08, 0xa0, 0x2f, + 0x48, 0xc0, 0x4e, 0x75, 0x22, 0x6f, 0x00, 0x08, 0x20, 0x6f, 0x00, 0x04, 0x2f, 0x02, 0xa9, 0x6e, 0x24, 0x1f, 0x48, 0xc0, 0x4e, 0x75, 0x30, 0x2f, 0x00, 0x06, 0x12, 0x2f, 0x00, 0x0b, 0x4a, 0x01, + 0x67, 0x04, 0xa7, 0x46, 0x60, 0x02, 0xa3, 0x46, 0x20, 0x08, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x33, 0x48, 0xc0, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x34, 0x48, 0xc0, 0x4e, 0x75, + 0x4e, 0xba, 0x00, 0x58, 0x06, 0x80, 0x00, 0x00, 0x00, 0x20, 0x4e, 0x75, 0x2f, 0x0d, 0x20, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x66, 0x0c, 0x20, 0x6f, 0x00, 0x08, 0x70, 0x07, 0x20, 0xdd, 0x51, 0xc8, + 0xff, 0xfc, 0x2a, 0x6f, 0x00, 0x08, 0x4e, 0xba, 0x00, 0x3a, 0x2a, 0x5f, 0x4e, 0x75, 0x20, 0x0d, 0x2a, 0x6f, 0x00, 0x04, 0x4e, 0x75, 0x2a, 0x6f, 0x00, 0x04, 0x4e, 0x75, 0x4e, 0xba, 0xff, 0xc2, + 0xa1, 0x1e, 0x2f, 0x08, 0x4e, 0xba, 0x00, 0x14, 0xd0, 0x9f, 0x4e, 0x75, 0x4e, 0xba, 0x00, 0x0c, 0x20, 0x6f, 0x00, 0x04, 0x91, 0xc0, 0xa0, 0x1f, 0x4e, 0x75, 0x41, 0xfa, 0x00, 0xce, 0x20, 0x10, + 0x4e, 0x75, 0x48, 0xe7, 0xff, 0xf0, 0x42, 0x47, 0x41, 0xfa, 0x00, 0xc0, 0x22, 0x18, 0x6f, 0x00, 0x00, 0xa6, 0x2a, 0x18, 0x66, 0x04, 0x2a, 0x0d, 0x9a, 0x81, 0x26, 0x45, 0x24, 0x4b, 0xe2, 0x81, + 0x60, 0x02, 0x42, 0x5a, 0x51, 0xc9, 0xff, 0xfc, 0x30, 0x18, 0x3e, 0x18, 0x60, 0x00, 0x00, 0x84, 0x78, 0x00, 0x18, 0x18, 0x22, 0x04, 0x02, 0x01, 0x00, 0x0f, 0x08, 0x04, 0x00, 0x04, 0x67, 0x0e, + 0xe1, 0x41, 0x12, 0x18, 0x08, 0x81, 0x00, 0x0b, 0x67, 0x04, 0xe1, 0x81, 0x12, 0x18, 0x74, 0x01, 0x08, 0x04, 0x00, 0x07, 0x67, 0x16, 0x14, 0x18, 0x08, 0x82, 0x00, 0x07, 0x67, 0x0e, 0xe1, 0x42, + 0x14, 0x18, 0x08, 0x82, 0x00, 0x0e, 0x67, 0x04, 0xe1, 0x82, 0x14, 0x18, 0x7c, 0x02, 0x4e, 0xba, 0x00, 0x4c, 0x08, 0x85, 0x00, 0x0f, 0x67, 0x04, 0xe1, 0x85, 0x1a, 0x18, 0x43, 0xf3, 0x58, 0x00, + 0x24, 0x49, 0x08, 0x04, 0x00, 0x05, 0x67, 0x1c, 0x2c, 0x01, 0x4e, 0xba, 0x00, 0x30, 0x0c, 0x41, 0x00, 0x02, 0x67, 0x06, 0x6d, 0x08, 0xdb, 0x92, 0x60, 0x0e, 0xdb, 0x52, 0x60, 0x0a, 0xdb, 0x12, + 0x60, 0x06, 0x12, 0xd8, 0x51, 0xc9, 0xff, 0xfc, 0x08, 0x04, 0x00, 0x06, 0x67, 0x04, 0x26, 0x0d, 0xd7, 0x92, 0x51, 0xc8, 0xff, 0x7c, 0x4c, 0xdf, 0x0f, 0xff, 0x4e, 0x75, 0x7a, 0x00, 0x60, 0x04, + 0xe1, 0x8d, 0x1a, 0x18, 0x51, 0xce, 0xff, 0xfa, 0x4e, 0x75, 0x00, 0x00, 0x03, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x8a, 0x25, 0x41, 0x35, 0x69, 0x80, 0x00, + 0x03, 0x7e, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x19, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x53, 0x43, 0x53, 0x49, 0x20, 0x64, 0x72, 0x69, 0x76, + 0x65, 0x73, 0x2e, 0x2c, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x2e, 0x2e, 0x2e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, + 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(&driver[offset], &driver[offset + 512]); + } + + // Default: return an empty block. + return std::vector(512); +} diff --git a/Storage/MassStorage/Encodings/MacintoshVolume.hpp b/Storage/MassStorage/Encodings/MacintoshVolume.hpp new file mode 100644 index 000000000..185c658c0 --- /dev/null +++ b/Storage/MassStorage/Encodings/MacintoshVolume.hpp @@ -0,0 +1,87 @@ +// +// MacintoshVolume.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef MacintoshVolume_hpp +#define MacintoshVolume_hpp + +#include +#include + +namespace Storage { +namespace MassStorage { +namespace Encodings { +namespace Macintosh { + +enum class DriveType { + SCSI +}; + +/*! + On the Macintosh life is slightly complicated by Apple's + decision to include device drivers on mass storage drives + themselves — therefore a mass-storage device that is + connected by SCSI will have different preliminary data on it + than the same volume connected by ATA or as an HD20. + + Mass storage devices that respond to @c Volume can be made + to provide the proper whole-volume encoding necessary to + impersonate different types of Macintosh drive. +*/ +class Volume { + public: + virtual void set_drive_type(DriveType type) = 0; +}; + +/*! + A Mapper can used by a mass-storage device that knows the + contents of an HFS or MFS 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 convert_source_block(ssize_t source_address, std::vector 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_; +}; + +} +} +} +} + +#endif /* MacintoshVolume_hpp */ diff --git a/Storage/MassStorage/Formats/HFV.cpp b/Storage/MassStorage/Formats/HFV.cpp new file mode 100644 index 000000000..3e454e892 --- /dev/null +++ b/Storage/MassStorage/Formats/HFV.cpp @@ -0,0 +1,56 @@ +// +// HFV.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "HFV.hpp" + +using namespace Storage::MassStorage; + +HFV::HFV(const std::string &file_name) : file_(file_name) { + // Is the file a multiple of 512 bytes in size and larger than a floppy disk? + const auto file_size = file_.stats().st_size; + if(file_size & 511 || file_size <= 800*1024) throw std::exception(); + + // TODO: check filing system for MFS, HFS or HFS+. +} + +size_t HFV::get_block_size() { + return 512; +} + +size_t HFV::get_number_of_blocks() { + return mapper_.get_number_of_blocks(); +} + +std::vector HFV::get_block(size_t address) { + const auto written = writes_.find(address); + if(written != writes_.end()) return written->second; + + const auto source_address = mapper_.to_source_address(address); + if(source_address >= 0 && size_t(source_address)*get_block_size() < size_t(file_.stats().st_size)) { + const long file_offset = long(get_block_size()) * long(source_address); + 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 HFV::set_block(size_t address, const std::vector &contents) { + const auto source_address = mapper_.to_source_address(address); + if(source_address >= 0 && size_t(source_address)*get_block_size() < size_t(file_.stats().st_size)) { + const long file_offset = long(get_block_size()) * long(source_address); + file_.seek(file_offset, SEEK_SET); + file_.write(contents); + } else { + writes_[address] = contents; + } +} + +void HFV::set_drive_type(Encodings::Macintosh::DriveType drive_type) { + mapper_.set_drive_type(drive_type, size_t(file_.stats().st_size) / get_block_size()); +} diff --git a/Storage/MassStorage/Formats/HFV.hpp b/Storage/MassStorage/Formats/HFV.hpp new file mode 100644 index 000000000..ed1db3179 --- /dev/null +++ b/Storage/MassStorage/Formats/HFV.hpp @@ -0,0 +1,54 @@ +// +// HFV.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef HFV_hpp +#define HFV_hpp + +#include "../MassStorageDevice.hpp" +#include "../../FileHolder.hpp" +#include "../Encodings/MacintoshVolume.hpp" + +#include +#include + +namespace Storage { +namespace MassStorage { + +/*! + Provides a @c MassStorageDevice containing an HFV image, which is a sector dump of + the HFS volume of a Macintosh drive that is not the correct size to be a floppy disk. +*/ +class HFV: public MassStorageDevice, public Encodings::Macintosh::Volume { + public: + /*! + Constructs an HFV with the contents of the file named @c file_name. + Raises an exception if the file name doesn't appear to identify a valid + Macintosh mass storage image. + */ + HFV(const std::string &file_name); + + private: + FileHolder file_; + Encodings::Macintosh::Mapper mapper_; + + /* MassStorageDevices overrides. */ + size_t get_block_size() final; + size_t get_number_of_blocks() final; + std::vector get_block(size_t address) final; + void set_block(size_t address, const std::vector &) final; + + /* Encodings::Macintosh::Volume overrides. */ + void set_drive_type(Encodings::Macintosh::DriveType) final; + + std::map> writes_; +}; + +} +} + +#endif /* HFV_hpp */ diff --git a/Storage/MassStorage/MassStorageDevice.cpp b/Storage/MassStorage/MassStorageDevice.cpp new file mode 100644 index 000000000..97964f16c --- /dev/null +++ b/Storage/MassStorage/MassStorageDevice.cpp @@ -0,0 +1,9 @@ +// +// MassStorageDevice.cpp +// Clock Signal +// +// Created by Thomas Harte on 21/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "MassStorageDevice.hpp" diff --git a/Storage/MassStorage/MassStorageDevice.hpp b/Storage/MassStorage/MassStorageDevice.hpp new file mode 100644 index 000000000..567711c80 --- /dev/null +++ b/Storage/MassStorage/MassStorageDevice.hpp @@ -0,0 +1,61 @@ +// +// MassStorageDevice.hpp +// Clock Signal +// +// Created by Thomas Harte on 21/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef MassStorageDevice_hpp +#define MassStorageDevice_hpp + +#include +#include +#include + +namespace Storage { +namespace MassStorage { + +/*! + A mass storage device is usually: + + * large; + * fixed; and + * part of a class with a very wide array of potential speeds and timings. + + Within this emulator, mass storage devices don't attempt to emulate + any specific medium, they just offer block-based access to a + linearly-addressed store. +*/ +class MassStorageDevice { + public: + virtual ~MassStorageDevice() {} + + /*! + @returns The size of each individual block. + */ + virtual size_t get_block_size() = 0; + + /*! + Block addresses run from 0 to n. The total number of blocks, n, + therefore provides the range of valid addresses. + + @returns The total number of blocks on the device. + */ + virtual size_t get_number_of_blocks() = 0; + + /*! + @returns The current contents of the block at @c address. + */ + virtual std::vector get_block(size_t address) = 0; + + /*! + Sets new contents for the block at @c address. + */ + virtual void set_block(size_t address, const std::vector &) {} +}; + +} +} + +#endif /* MassStorageDevice_hpp */ diff --git a/Storage/MassStorage/SCSI/DirectAccessDevice.cpp b/Storage/MassStorage/SCSI/DirectAccessDevice.cpp new file mode 100644 index 000000000..58d0f21e8 --- /dev/null +++ b/Storage/MassStorage/SCSI/DirectAccessDevice.cpp @@ -0,0 +1,86 @@ +// +// DirectAccessDevice.cpp +// Clock Signal +// +// Created by Thomas Harte on 22/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "DirectAccessDevice.hpp" +#include "../../../Outputs/Log.hpp" + +using namespace SCSI; + +void DirectAccessDevice::set_storage(const std::shared_ptr &device) { + device_ = device; +} + +bool DirectAccessDevice::read(const Target::CommandState &state, Target::Responder &responder) { + if(!device_) return false; + + const auto specs = state.read_write_specs(); + LOG("Read: " << specs.number_of_blocks << " from " << specs.address); + + std::vector output = device_->get_block(specs.address); + for(uint32_t offset = 1; offset < specs.number_of_blocks; ++offset) { + const auto next_block = device_->get_block(specs.address + offset); + std::copy(next_block.begin(), next_block.end(), std::back_inserter(output)); + } + + responder.send_data(std::move(output), [] (const Target::CommandState &state, Target::Responder &responder) { + responder.terminate_command(Target::Responder::Status::Good); + }); + + return true; +} + +bool DirectAccessDevice::write(const Target::CommandState &state, Target::Responder &responder) { + if(!device_) return false; + + const auto specs = state.read_write_specs(); + + responder.receive_data(device_->get_block_size() * specs.number_of_blocks, [this, specs] (const Target::CommandState &state, Target::Responder &responder) { + const auto received_data = state.received_data(); + const auto block_size = ssize_t(device_->get_block_size()); + for(uint32_t offset = 0; offset < specs.number_of_blocks; ++offset) { + // TODO: clean up this gross inefficiency when std::span is standard. + std::vector sub_vector(received_data.begin() + ssize_t(offset)*block_size, received_data.begin() + ssize_t(offset+1)*block_size); + this->device_->set_block(specs.address + offset, sub_vector); + } + responder.terminate_command(Target::Responder::Status::Good); + }); + + return true; +} + +bool DirectAccessDevice::read_capacity(const Target::CommandState &state, Target::Responder &responder) { + const auto final_block = device_->get_number_of_blocks() - 1; + const auto block_size = device_->get_block_size(); + std::vector data = { + uint8_t(final_block >> 24), + uint8_t(final_block >> 16), + uint8_t(final_block >> 8), + uint8_t(final_block >> 0), + + uint8_t(block_size >> 24), + uint8_t(block_size >> 16), + uint8_t(block_size >> 8), + uint8_t(block_size >> 0), + }; + + responder.send_data(std::move(data), [] (const Target::CommandState &state, Target::Responder &responder) { + responder.terminate_command(Target::Responder::Status::Good); + }); + + return true; +} + +Target::Executor::Inquiry DirectAccessDevice::inquiry_values() { + return Inquiry("Apple", "ProFile", "1"); // All just guesses. +} + +bool DirectAccessDevice::format_unit(const Target::CommandState &state, Target::Responder &responder) { + // Formatting: immediate. + responder.terminate_command(Target::Responder::Status::Good); + return true; +} diff --git a/Storage/MassStorage/SCSI/DirectAccessDevice.hpp b/Storage/MassStorage/SCSI/DirectAccessDevice.hpp new file mode 100644 index 000000000..5f96a5747 --- /dev/null +++ b/Storage/MassStorage/SCSI/DirectAccessDevice.hpp @@ -0,0 +1,40 @@ +// +// DirectAccessDevice.hpp +// Clock Signal +// +// Created by Thomas Harte on 22/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef SCSI_DirectAccessDevice_hpp +#define SCSI_DirectAccessDevice_hpp + +#include "Target.hpp" +#include "../MassStorageDevice.hpp" + +#include + +namespace SCSI { + +class DirectAccessDevice: public Target::Executor { + public: + + /*! + Sets the backing storage exposed by this direct-access device. + */ + void set_storage(const std::shared_ptr &device); + + /* SCSI commands. */ + bool read(const Target::CommandState &, Target::Responder &); + bool write(const Target::CommandState &, Target::Responder &); + Inquiry inquiry_values(); + bool read_capacity(const Target::CommandState &, Target::Responder &); + bool format_unit(const Target::CommandState &, Target::Responder &); + + private: + std::shared_ptr device_; +}; + +} + +#endif /* SCSI_DirectAccessDevice_hpp */ diff --git a/Storage/MassStorage/SCSI/SCSI.cpp b/Storage/MassStorage/SCSI/SCSI.cpp new file mode 100644 index 000000000..446f14875 --- /dev/null +++ b/Storage/MassStorage/SCSI/SCSI.cpp @@ -0,0 +1,104 @@ +// +// SCSI.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "SCSI.hpp" + +using namespace SCSI; + +Bus::Bus(HalfCycles clock_rate) { + cycles_to_time_ = 1.0 / double(clock_rate.as_int()); + + // NB: note that the dispatch times below are **ORDERED** + // from least to greatest. Each box should contain the number + // of whole clock periods it will take to get the the first + // discrete moment after the required delay interval has been met. + dispatch_times_[0] = 1 + int(CableSkew / cycles_to_time_); + dispatch_times_[1] = 1 + int(DeskewDelay / cycles_to_time_); + dispatch_times_[2] = 1 + int(BusFreeDelay / cycles_to_time_); + dispatch_times_[3] = 1 + int(BusSettleDelay / cycles_to_time_); + dispatch_times_[4] = 1 + int(BusClearDelay / cycles_to_time_); + dispatch_times_[5] = 1 + int(BusSetDelay / cycles_to_time_); + dispatch_times_[6] = 1 + int(ArbitrationDelay / cycles_to_time_); + dispatch_times_[7] = 1 + int(ResetHoldTime / cycles_to_time_); +} + +size_t Bus::add_device() { + const auto slot = device_states_.size(); + device_states_.push_back(DefaultBusState); + return slot; +} + +void Bus::set_device_output(size_t device, BusState output) { + if(device_states_[device] == output) return; + device_states_[device] = output; + + const auto previous_state = state_; + state_ = DefaultBusState; + for(auto state: device_states_) { + state_ |= state; + } + if(state_ == previous_state) return; + +// printf("SCSI bus: %02x %c%c%c%c%c%c%c%c%c%c\n", +// state_ & 0xff, +// (state_ & Line::Parity) ? 'p' : '-', +// (state_ & Line::SelectTarget) ? 's' : '-', +// (state_ & Line::Attention) ? 't' : '-', +// (state_ & Line::Control) ? 'c' : '-', +// (state_ & Line::Busy) ? 'b' : '-', +// (state_ & Line::Acknowledge) ? 'a' : '-', +// (state_ & Line::Reset) ? 'r' : '-', +// (state_ & Line::Input) ? 'i' : '-', +// (state_ & Line::Message) ? 'm' : '-', +// (state_ & Line::Request) ? 'q' : '-' +// ); + + bool was_asleep = preferred_clocking() == ClockingHint::Preference::None; + dispatch_index_ = 0; + time_in_state_ = HalfCycles(0); + if(was_asleep) update_clocking_observer(); +} + +BusState Bus::get_state() { + return state_; +} + +void Bus::add_observer(Observer *observer) { + observers_.push_back(observer); +} + +ClockingHint::Preference Bus::preferred_clocking() { + return (dispatch_index_ < dispatch_times_.size()) ? ClockingHint::Preference::RealTime : ClockingHint::Preference::None; +} + +void Bus::update_observers() { + const auto time_elapsed = double(time_in_state_.as_int()) * cycles_to_time_; + for(auto &observer: observers_) { + observer->scsi_bus_did_change(this, state_, time_elapsed); + } +} + +void Bus::run_for(HalfCycles time) { + if(dispatch_index_ < dispatch_times_.size()) { + time_in_state_ += time; + + const auto old_index = dispatch_index_; + const auto time_as_int = time_in_state_.as_int(); + while(time_as_int >= dispatch_times_[dispatch_index_] && dispatch_index_ < dispatch_times_.size()) { + ++dispatch_index_; + } + + if(dispatch_index_ != old_index) { + update_observers(); + } + + if(preferred_clocking() == ClockingHint::Preference::None) { + update_clocking_observer(); + } + } +} diff --git a/Storage/MassStorage/SCSI/SCSI.hpp b/Storage/MassStorage/SCSI/SCSI.hpp new file mode 100644 index 000000000..ddc8703d6 --- /dev/null +++ b/Storage/MassStorage/SCSI/SCSI.hpp @@ -0,0 +1,156 @@ +// +// SCSI.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef SCSI_hpp +#define SCSI_hpp + +#include +#include +#include + +#include "../../../ClockReceiver/ClockReceiver.hpp" +#include "../../../ClockReceiver/ClockingHintSource.hpp" + +namespace SCSI { + +typedef int BusState; + +static const BusState DefaultBusState = 0; + +/*! + SCSI bus state is encoded entirely within an int. + Bits correlate mostly but not exactly to the real SCSI bus. + + TODO: validate levels below. The bus uses open collector logic, + so active low needs to be respected. +*/ +enum Line: BusState { + /// Provides the value currently on the data lines. + Data = 0xff, + /// Parity of the data lines. + Parity = 1 << 8, + /// Set if the SEL line is currently selecting a target. + /// Reset if it is selecting an initiator. + SelectTarget = 1 << 9, + /// Set to indicate an attention condition. Reset otherwise. + Attention = 1 << 10, + /// Set if control is on the bus. Reset if data is on the bus. + Control = 1 << 11, + /// Set if the bus is busy. Reset otherwise. + Busy = 1 << 12, + /// Set if acknowledging a data transfer request. Reset otherwise. + Acknowledge = 1 << 13, + /// Set if a bus reset is being requested. Reset otherwise. + Reset = 1 << 14, + /// Set if data is currently an input to the initiator. Reset if it is an output. + Input = 1 << 15, + /// Set during the message phase. Reset otherwise. + Message = 1 << 16, + /// Set if requesting a data transfer. Reset otherwise. + Request = 1 << 17, +}; + +#define us(x) (x) / 1000000.0 +#define ns(x) (x) / 1000000000.0 + +/// The minimum amount of time that reset must be held for. +constexpr double ResetHoldTime = us(25.0); + +/// The minimum amount of time a SCSI device must wait after asserting ::Busy +/// until the data bus can be inspected to see whether arbitration has been won. +constexpr double ArbitrationDelay = us(1.7); + +/// The maximum amount of time a SCSI device can take from a detection that the +/// bus is free until it asserts ::Busy and its ID for the purposes of arbitration. +constexpr double BusSetDelay = us(1.1); + +/// The maximum amount of time a SCSI device is permitted to take to stop driving +/// all bus signals after: (i) the release of ::Busy ushering in a bus free phase; +/// or (ii) some other device has asserted ::Select during an arbitration phase. +constexpr double BusClearDelay = ns(650.0); + +/// The minimum amount of time to wait for the bus to settle after changing +/// "certain control signals". TODO: which? +constexpr double BusSettleDelay = ns(450.0); + +/// The minimum amount of time a SCSI must wait from detecting that the bus is free +/// and asserting ::Busy if starting an arbitration phase. +constexpr double BusFreeDelay = ns(100.0); + +/// The minimum amount of time required for deskew of "certain signals". TODO: which? +constexpr double DeskewDelay = ns(45.0); + +/// The maximum amount of time that propagation of a SCSI bus signal can take between +/// any two devices. +constexpr double CableSkew = ns(10.0); + +#undef ns +#undef us + +class Bus: public ClockingHint::Source { + public: + Bus(HalfCycles clock_rate); + + /*! + Adds a device to the bus, returning the index it should use + to refer to itself in subsequent calls to set_device_output. + */ + size_t add_device(); + + /*! + Sets the current output for @c device. + */ + void set_device_output(size_t device, BusState output); + + /*! + @returns the current state of the bus. + */ + BusState get_state(); + + struct Observer { + /// Reports to an observer that the bus changed from a previous state to @c new_state, + /// along with the time since that change was observed. The time is in seconds, and is + /// intended for comparison with the various constants defined at namespace scope: + /// ArbitrationDelay et al. Observers will be notified each time one of the thresholds + /// defined by those constants is crossed. + virtual void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) = 0; + }; + /*! + Adds an observer. + */ + void add_observer(Observer *); + + /*! + SCSI buses don't have a clock. But devices on the bus are concerned with time-based factors, + and `run_for` is the way that time propagates within this emulator. So please permit this + fiction. + */ + void run_for(HalfCycles); + + /*! + Forces a `scsi_bus_did_change` propagation now. + */ + void update_observers(); + + /// As per ClockingHint::Source. + ClockingHint::Preference preferred_clocking() final; + + private: + HalfCycles time_in_state_; + double cycles_to_time_ = 1.0; + size_t dispatch_index_ = 0; + std::array dispatch_times_; + + std::vector device_states_; + BusState state_ = DefaultBusState; + std::vector observers_; +}; + +} + +#endif /* SCSI_hpp */ diff --git a/Storage/MassStorage/SCSI/Target.cpp b/Storage/MassStorage/SCSI/Target.cpp new file mode 100644 index 000000000..817a9461e --- /dev/null +++ b/Storage/MassStorage/SCSI/Target.cpp @@ -0,0 +1,93 @@ +// +// Target.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "Target.hpp" + +using namespace SCSI::Target; + +CommandState::CommandState(const std::vector &data, const std::vector &received) : data_(data), received_(received) {} + +uint32_t CommandState::address() const { + switch(data_.size()) { + default: return 0; + case 6: + return + (uint32_t(data_[1]) << 16) | + (uint32_t(data_[2]) << 8) | + uint32_t(data_[3]); + case 10: + case 12: + return + (uint32_t(data_[1]) << 24) | + (uint32_t(data_[2]) << 16) | + (uint32_t(data_[3]) << 8) | + uint32_t(data_[4]); + } +} + +uint16_t CommandState::number_of_blocks() const { + switch(data_.size()) { + default: return 0; + case 6: + return uint16_t(data_[4]); + case 10: + return uint16_t((data_[7] << 8) | data_[8]); + } +} + +CommandState::ReadWrite CommandState::read_write_specs() const { + ReadWrite specs; + + specs.address = address(); + specs.number_of_blocks = number_of_blocks(); + if(!specs.number_of_blocks && (data_.size() == 6)) { + specs.number_of_blocks = 256; + } + + return specs; +} + +size_t CommandState::allocated_inquiry_bytes() const { + // 0 means 256 bytes allocated for inquiry. + return size_t(((data_[4] - 1) & 0xff) + 1); +} + +CommandState::ModeSense CommandState::mode_sense_specs() const { + ModeSense specs; + + specs.exclude_block_descriptors = (data_[1] & 0x08); + specs.page_control_values = ModeSense::PageControlValues(data_[2] >> 5); + specs.page_code = data_[2] & 0x3f; + specs.subpage_code = data_[3]; + specs.allocated_bytes = number_of_blocks(); + + return specs; +} + +CommandState::ReadBuffer CommandState::read_buffer_specs() const { + ReadBuffer specs; + + specs.mode = ReadBuffer::Mode(data_[1]&7); + if(specs.mode > ReadBuffer::Mode::Reserved) specs.mode = ReadBuffer::Mode::Reserved; + specs.buffer_id = data_[2]; + specs.buffer_offset = uint32_t((data_[3] << 16) | (data_[4] << 8) | data_[5]); + specs.buffer_length = uint32_t((data_[6] << 16) | (data_[7] << 8) | data_[8]); + + return specs; +} + +CommandState::ModeSelect CommandState::mode_select_specs() const { + ModeSelect specs; + + specs.parameter_list_length = number_of_blocks(); + specs.content_is_vendor_specific = !(data_[1] & 0x10); + specs.revert_to_default = (data_[1] & 0x02); + specs.save_pages = (data_[1] & 0x01); + + return specs; +} diff --git a/Storage/MassStorage/SCSI/Target.hpp b/Storage/MassStorage/SCSI/Target.hpp new file mode 100644 index 000000000..371f13194 --- /dev/null +++ b/Storage/MassStorage/SCSI/Target.hpp @@ -0,0 +1,401 @@ +// +// Target.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef SCSI_Target_hpp +#define SCSI_Target_hpp + +#include "SCSI.hpp" +#include "../../../Outputs/Log.hpp" + +#include +#include + +namespace SCSI { +namespace Target { + +/*! + Encapsulates the arguments supplied for a target SCSI command during + the command phase plus any other data read since then. +*/ +class CommandState { + public: + CommandState(const std::vector &command, const std::vector &received); + + // For read and write commands. + struct ReadWrite { + uint32_t address, number_of_blocks; + }; + ReadWrite read_write_specs() const; + + // For inquiry commands. + size_t allocated_inquiry_bytes() const; + + // For mode sense commands. + struct ModeSense { + bool exclude_block_descriptors = false; + enum class PageControlValues { + Current = 0, + Changeable = 1, + Default = 2, + Saved = 3 + } page_control_values = PageControlValues::Current; + uint8_t page_code; + uint8_t subpage_code; + uint16_t allocated_bytes; + }; + ModeSense mode_sense_specs() const; + + struct ModeSelect { + bool content_is_vendor_specific = true; + bool revert_to_default = false; + bool save_pages = false; + uint16_t parameter_list_length = 0; + }; + ModeSelect mode_select_specs() const; + + struct ReadBuffer { + enum class Mode { + CombinedHeaderAndData = 0, + VendorSpecific = 1, + Data = 2, + Descriptor = 3, + Reserved = 4 + } mode = Mode::CombinedHeaderAndData; + uint8_t buffer_id = 0; + uint32_t buffer_offset = 0, buffer_length = 0; + }; + ReadBuffer read_buffer_specs() const; + + const std::vector &received_data() const { + return received_; + } + + private: + uint32_t address() const; + uint16_t number_of_blocks() const; + const std::vector &data_; + const std::vector &received_; +}; + +/*! + A Responder is supplied both (i) to the initial call-in to an Executor; and + (ii) to all continuations provided by that Executor. It allows the next + set of bus interactions to be dictated. +*/ +struct Responder { + using continuation = std::function; + + enum class Status { + Good = 0x00, + CheckCondition = 0x02, + ConditionMet = 0x04, + Busy = 0x08, + Intermediate = 0x10, + IntermediateConditionMet = 0x14, + ReservationConflict = 0x18, + CommandTerminated = 0x22, + TaskSetFull = 0x28, + ACAActive = 0x30, + TaskAborted = 0x40 + }; + + enum class Message { + CommandComplete = 0x00 + }; + + /*! + Causes the SCSI device to send @c data to the initiator and + call @c next when done. + */ + virtual void send_data(std::vector &&data, continuation next) = 0; + /*! + Causes the SCSI device to receive @c length bytes from the initiator and + call @c next when done. The bytes will be accessible via the CommandInput object. + */ + virtual void receive_data(size_t length, continuation next) = 0; + /*! + Communicates the supplied status to the initiator. + */ + virtual void send_status(Status, continuation next) = 0; + /*! + Communicates the supplied message to the initiator. + */ + virtual void send_message(Message, continuation next) = 0; + /*! + Ends the SCSI command. + */ + virtual void end_command() = 0; + /*! + Terminates a SCSI command, sending the proper sequence of status and message phases. + */ + void terminate_command(Status status) { + send_status(status, [] (const Target::CommandState &state, Target::Responder &responder) { + responder.send_message(Target::Responder::Message::CommandComplete, [] (const Target::CommandState &state, Target::Responder &responder) { + responder.end_command(); + }); + }); + } +}; + +/*! + Executors contain device-specific logic; when the target has completed + the command phase it will call the appropriate method on its executor, + supplying it with the command's arguments. + + If you implement a method, you should push a result and return @c true. + Return @c false if you do not implement a method (or, just inherit from + the basic executor below, and don't implement anything you don't support). +*/ +struct Executor { + /* Group 0 commands. */ + bool test_unit_ready(const CommandState &, Responder &responder) { + /* "Returns zero status if addressed unit is powered on and ready. */ + responder.terminate_command(Target::Responder::Status::Good); + return true; + } + bool rezero_unit(const CommandState &, Responder &) { return false; } + bool request_sense(const CommandState &, Responder &) { return false; } + bool format_unit(const CommandState &, Responder &) { return false; } + bool seek(const CommandState &, Responder &) { return false; } + bool reserve_unit(const CommandState &, Responder &) { return false; } + bool release_unit(const CommandState &, Responder &) { return false; } + bool read_diagnostic(const CommandState &, Responder &) { return false; } + bool write_diagnostic(const CommandState &, Responder &) { return false; } + + /// Mode sense: the default implementation will call into the appropriate + /// structured getter. + bool mode_sense(const CommandState &state, Responder &responder) { + const auto specs = state.mode_sense_specs(); + std::vector response = { + specs.page_code, + uint8_t(specs.allocated_bytes) + }; + switch(specs.page_code) { + default: + printf("Unknown mode sense page code %02x\n", specs.page_code); + response.resize(specs.allocated_bytes); + break; + + case 0x30: + response.resize(34); + strcpy(reinterpret_cast(&response[14]), "APPLE COMPUTER, INC"); // This seems to be required to satisfy the Apple HD SC Utility. + break; + } + + if(specs.allocated_bytes < response.size()) { + response.resize(specs.allocated_bytes); + } + responder.send_data(std::move(response), [] (const Target::CommandState &state, Target::Responder &responder) { + responder.terminate_command(Target::Responder::Status::Good); + }); + + return true; + } + + bool mode_select(const CommandState &state, Responder &responder) { + const auto specs = state.mode_select_specs(); + + responder.receive_data(specs.parameter_list_length, [] (const Target::CommandState &state, Target::Responder &responder) { + // TODO: parse data according to current sense mode. + responder.terminate_command(Target::Responder::Status::Good); + }); + + return true; + } + + /// Inquiry: the default implementation will call the structured version and + /// package appropriately. + struct Inquiry { + enum class DeviceType { + DirectAccess = 0, + SequentialAccess = 1, + Printer = 2, + Processor = 3, + WriteOnceMultipleRead = 4, + ReadOnlyDirectAccess = 5, + Scanner = 6, + OpticalMemory = 7, + MediumChanger = 8, + Communications = 9, + } device_type = DeviceType::DirectAccess; + bool is_removeable = false; + uint8_t iso_standard = 0, ecma_standard = 0, ansi_standard = 0; + bool supports_asynchronous_events = false; + bool supports_terminate_io_process = false; + bool supports_relative_addressing = false; + bool supports_synchronous_transfer = true; + bool supports_linked_commands = false; + bool supports_command_queing = false; + bool supports_soft_reset = false; + char vendor_identifier[9] = ""; + char product_identifier[17] = ""; + char product_revision_level[5] = ""; + + Inquiry(const char *vendor, const char *product, const char *revision) { + assert(strlen(vendor) <= 8); + assert(strlen(product) <= 16); + assert(strlen(revision) <= 4); + strcpy(vendor_identifier, vendor); + strcpy(product_identifier, product); + strcpy(product_revision_level, revision); + } + Inquiry() = default; + }; + Inquiry inquiry_values() { + return Inquiry(); + } + bool inquiry(const CommandState &state, Responder &responder) { + const Inquiry inq = inquiry_values(); + + // Set up the easy fields. + std::vector response = { + uint8_t(inq.device_type), + uint8_t(inq.is_removeable ? 0x80 : 0x00), + uint8_t((inq.iso_standard << 5) | (inq.ecma_standard << 3) | (inq.ansi_standard)), + uint8_t((inq.supports_asynchronous_events ? 0x80 : 0x00) | (inq.supports_terminate_io_process ? 0x40 : 0x00) | 0x02), + 32, /* Additional length: 36 - 4. */ + 0x00, /* Reserved. */ + 0x00, /* Reserved. */ + uint8_t( + (inq.supports_relative_addressing ? 0x80 : 0x00) | + /* b6: supports 32-bit data; b5: supports 16-bit data. */ + (inq.supports_synchronous_transfer ? 0x10 : 0x00) | + (inq.supports_linked_commands ? 0x08 : 0x00) | + /* b3: reserved. */ + (inq.supports_command_queing ? 0x02 : 0x00) | + (inq.supports_soft_reset ? 0x01 : 0x00) + ), + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Space for the vendor ID. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Space for the product ID. */ + 0x00, 0x00, 0x00, 0x00 /* Space for the revision level. */ + }; + + auto copy_string = [] (uint8_t *destination, const char *source, size_t length) -> void { + // Copy as much of the string as will fit, and pad with spaces. + uint8_t *end = reinterpret_cast(stpncpy(reinterpret_cast(destination), source, length)); + while(end < destination + length) { + *end = ' '; + ++end; + } + }; + copy_string(&response[8], inq.vendor_identifier, 8); + copy_string(&response[16], inq.product_identifier, 16); + copy_string(&response[32], inq.product_revision_level, 4); + + // Truncate if requested. + const auto allocated_bytes = state.allocated_inquiry_bytes(); + if(allocated_bytes < response.size()) { + response.resize(allocated_bytes); + } + + responder.send_data(std::move(response), [] (const Target::CommandState &state, Target::Responder &responder) { + responder.terminate_command(Target::Responder::Status::Good); + }); + + return true; + } + + /* Group 0/1 commands. */ + bool read(const CommandState &, Responder &) { return false; } + bool write(const CommandState &, Responder &) { return false; } + + /* Group 1 commands. */ + bool read_capacity(const CommandState &, Responder &) { return false; } + bool write_and_verify(const CommandState &, Responder &) { return false; } + bool verify(const CommandState &, Responder &) { return false; } + bool search_data_equal(const CommandState &, Responder &) { return false; } + bool search_data_high(const CommandState &, Responder &) { return false; } + bool search_data_low(const CommandState &, Responder &) { return false; } + bool read_buffer(const CommandState &state, Responder &responder) { + // Since I have no idea what earthly function READ BUFFER is meant to allow, + // the default implementation just returns an empty buffer of the requested size. + const auto specs = state.read_buffer_specs(); + responder.send_data(std::vector(specs.buffer_length), [] (const Target::CommandState &, Target::Responder &responder) { + responder.terminate_command(Target::Responder::Status::Good); + }); + + return true; + } + + /* Group 5 commands. */ + bool set_block_limits(const CommandState &, Responder &) { return false; } +}; + +/*! + A template for any SCSI target; provides the necessary bus glue to + receive and respond to commands. Specific targets should be implemented + as Executors. +*/ +template class Target: public Bus::Observer, public Responder { + public: + /*! + Instantiates a target attached to @c bus, + with SCSI ID @c scsi_id — a number in the range 0 to 7. + + Received commands will be handed to the Executor to perform. + */ + Target(Bus &bus, int scsi_id); + + inline Executor *operator->() { + return &executor_; + } + + private: + Executor executor_; + + // Bus::Observer. + void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) final; + + // Responder + void send_data(std::vector &&data, continuation next) final; + void receive_data(size_t length, continuation next) final; + void send_status(Status, continuation next) final; + void send_message(Message, continuation next) final; + void end_command() final; + + // Instance storage. + Bus &bus_; + const BusState scsi_id_mask_; + const size_t scsi_bus_device_id_; + + enum class Phase { + AwaitingSelection, + Command, + ReceivingData, + SendingData, + SendingStatus, + SendingMessage + } phase_ = Phase::AwaitingSelection; + BusState bus_state_ = DefaultBusState; + + void set_device_output(BusState state) { + expected_control_state_ = state & (Line::Control | Line::Input | Line::Message); + bus_.set_device_output(scsi_bus_device_id_, state); + } + BusState expected_control_state_ = DefaultBusState; + + void begin_command(uint8_t first_byte); + std::vector command_; + Status status_; + Message message_; + size_t command_pointer_ = 0; + bool dispatch_command(); + + std::vector data_; + size_t data_pointer_ = 0; + + continuation next_function_; +}; + +#import "TargetImplementation.hpp" + +} +} + +#endif /* SCSI_Target_hpp */ diff --git a/Storage/MassStorage/SCSI/TargetImplementation.hpp b/Storage/MassStorage/SCSI/TargetImplementation.hpp new file mode 100644 index 000000000..ff6ccb297 --- /dev/null +++ b/Storage/MassStorage/SCSI/TargetImplementation.hpp @@ -0,0 +1,280 @@ +// +// TargetImplementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 19/08/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +template Target::Target(Bus &bus, int scsi_id) : + bus_(bus), + scsi_id_mask_(BusState(1 << scsi_id)), + scsi_bus_device_id_(bus.add_device()) { + bus.add_observer(this); +} + +template void Target::scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) { + /* + "The target determines that it is selected when the SEL# signal + and its SCSI ID bit are active and the BSY# and I#/O signals + are false. It then asserts the signal within a selection abort + time." + */ + + // Wait for deskew, at the very least. + if(time_since_change < SCSI::DeskewDelay) return; + + // A reset always takes precedence over anything else ongoing. + if(new_state & Line::Reset) { + phase_ = Phase::AwaitingSelection; + bus_state_ = DefaultBusState; + set_device_output(bus_state_); + return; + } + + switch(phase_) { + /* + While awaiting selection the SCSI target is passively watching the bus waiting for its ID + to be set during a target selection. It will segue automatically from there to the command + phase regardless of its executor. + */ + case Phase::AwaitingSelection: + if( + (new_state & scsi_id_mask_) && + ((new_state & (Line::SelectTarget | Line::Busy | Line::Input)) == Line::SelectTarget) + ) { + phase_ = Phase::Command; + command_.resize(0); + command_pointer_ = 0; + bus_state_ |= Line::Busy; // Initiate the command phase: request a command byte. + set_device_output(bus_state_); + } + break; + + /* + In the command phase, the target will stream an appropriate number of bytes for the command + it is being offered, before giving the executor a chance to handle the command. If the target + supports this command, it becomes responsible for the appropriate next phase transition. If it + reports that it doesn't support that command, a suitable response is automatically dispatched. + */ + case Phase::Command: + // Wait for select to be disabled before beginning the control phase proper. + if((new_state & Line::SelectTarget)) return; + + bus_state_ |= Line::Control; + + switch(new_state & (Line::Request | Line::Acknowledge)) { + // If request and acknowledge are both enabled, grab a byte and cancel the request. + case Line::Request | Line::Acknowledge: + bus_state_ &= ~Line::Request; + + if(command_.empty()) { + begin_command(uint8_t(new_state)); + + // TODO: if(command_.empty()) signal_error_somehow(); + } else { + command_[command_pointer_] = uint8_t(new_state); + ++command_pointer_; + if(command_pointer_ == command_.size()) { + if(!dispatch_command()) { + // This is just a guess for now; I don't know how SCSI + // devices are supposed to respond if they don't support + // a command. + terminate_command(Responder::Status::TaskAborted); + } + } + } + break; + + // The reset of request has caused the initiator to reset acknowledge, so it is now + // safe to request the next byte. + case 0: + bus_state_ |= Line::Request; + break; + + default: break; + } + set_device_output(bus_state_); + break; + + case Phase::ReceivingData: + switch(new_state & (Line::Request | Line::Acknowledge)) { + case Line::Request | Line::Acknowledge: + bus_state_ &= ~Line::Request; + + data_[data_pointer_] = uint8_t(new_state); + ++data_pointer_; + break; + + case 0: + if(data_pointer_ == data_.size()) { + next_function_(CommandState(command_, data_), *this); + } else { + bus_state_ |= Line::Request; + } + break; + } + set_device_output(bus_state_); + break; + + case Phase::SendingData: + case Phase::SendingStatus: + case Phase::SendingMessage: + switch(new_state & (Line::Request | Line::Acknowledge)) { + case Line::Request | Line::Acknowledge: + bus_state_ &= ~(Line::Request | 0xff); + + ++data_pointer_; + break; + + case 0: + if( + (phase_ == Phase::SendingMessage && data_pointer_ == 1) || + (phase_ == Phase::SendingStatus && data_pointer_ == 1) || + (phase_ == Phase::SendingData && data_pointer_ == data_.size()) + ) { + next_function_(CommandState(command_, data_), *this); + } else { + bus_state_ |= Line::Request; + bus_state_ &= ~0xff; + + switch(phase_) { + case Phase::SendingData: bus_state_ |= data_[data_pointer_]; break; + case Phase::SendingStatus: bus_state_ |= BusState(status_); break; + default: + case Phase::SendingMessage: bus_state_ |= BusState(message_); break; + } + } + break; + } + set_device_output(bus_state_); + break; + } +} + +template void Target::begin_command(uint8_t first_byte) { + // The logic below is valid for SCSI-1. TODO: other SCSIs. + switch(first_byte >> 5) { + default: break; + case 0: command_.resize(6); break; // Group 0 commands: 6 bytes long. + case 1: command_.resize(10); break; // Group 1 commands: 10 bytes long. + case 5: command_.resize(12); break; // Group 5 commands: 12 bytes long. + } + + // Store the first byte if it was recognised. + if(!command_.empty()) { + command_[0] = first_byte; + command_pointer_ = 1; + } +} + +template bool Target::dispatch_command() { + + CommandState arguments(command_, data_); + +#define G0(x) x +#define G1(x) (0x20|x) +#define G5(x) (0xa0|x) + + LOG("---Command " << PADHEX(2) << int(command_[0]) << "---"); + + switch(command_[0]) { + default: return false; + + case G0(0x00): return executor_.test_unit_ready(arguments, *this); + case G0(0x01): return executor_.rezero_unit(arguments, *this); + case G0(0x03): return executor_.request_sense(arguments, *this); + case G0(0x04): return executor_.format_unit(arguments, *this); + case G0(0x08): return executor_.read(arguments, *this); + case G0(0x0a): return executor_.write(arguments, *this); + case G0(0x0b): return executor_.seek(arguments, *this); + case G0(0x12): return executor_.inquiry(arguments, *this); + case G0(0x15): return executor_.mode_select(arguments, *this); + case G0(0x16): return executor_.reserve_unit(arguments, *this); + case G0(0x17): return executor_.release_unit(arguments, *this); + case G0(0x1a): return executor_.mode_sense(arguments, *this); + case G0(0x1c): return executor_.read_diagnostic(arguments, *this); + case G0(0x1d): return executor_.write_diagnostic(arguments, *this); + + case G1(0x05): return executor_.read_capacity(arguments, *this); + case G1(0x08): return executor_.read(arguments, *this); + case G1(0x0a): return executor_.write(arguments, *this); + case G1(0x0e): return executor_.write_and_verify(arguments, *this); + case G1(0x0f): return executor_.verify(arguments, *this); + case G1(0x11): return executor_.search_data_equal(arguments, *this); + case G1(0x10): return executor_.search_data_high(arguments, *this); + case G1(0x12): return executor_.search_data_low(arguments, *this); + case G1(0x1c): return executor_.read_buffer(arguments, *this); + case G1(0x15): return executor_.mode_select(arguments, *this); + + + case G5(0x09): return executor_.set_block_limits(arguments, *this); + } + +#undef G0 +#undef G1 +#undef G5 + + return false; +} + +template void Target::send_data(std::vector &&data, continuation next) { + // Data out phase: control and message all reset, input set. + bus_state_ &= ~(Line::Control | Line::Input | Line::Message); + bus_state_ |= Line::Input; + + phase_ = Phase::SendingData; + next_function_ = next; + data_ = std::move(data); + + data_pointer_ = 0; + set_device_output(bus_state_); +} + +template void Target::receive_data(size_t length, continuation next) { + // Data out phase: control, input and message all reset. + bus_state_ &= ~(Line::Control | Line::Input | Line::Message); + + phase_ = Phase::ReceivingData; + next_function_ = next; + data_.resize(length); + + data_pointer_ = 0; + set_device_output(bus_state_); +} + +template void Target::send_status(Status status, continuation next) { + // Status phase: message reset, control and input set. + bus_state_ &= ~(Line::Control | Line::Input | Line::Message); + bus_state_ |= Line::Input | Line::Control; + + status_ = status; + phase_ = Phase::SendingStatus; + next_function_ = next; + + data_pointer_ = 0; + set_device_output(bus_state_); +} + +template void Target::send_message(Message message, continuation next) { + // Message in phase: message, control and input set. + bus_state_ |= Line::Message | Line::Control | Line::Input; + + message_ = message; + phase_ = Phase::SendingMessage; + next_function_ = next; + + data_pointer_ = 0; + set_device_output(bus_state_); +} + +template void Target::end_command() { + // TODO: was this a linked command? + + // Release all bus lines and return to awaiting selection. + phase_ = Phase::AwaitingSelection; + bus_state_ = DefaultBusState; + set_device_output(bus_state_); + + LOG("---Done---"); +} diff --git a/Storage/Tape/Formats/CSW.cpp b/Storage/Tape/Formats/CSW.cpp index ea3deb40b..82c08eb20 100644 --- a/Storage/Tape/Formats/CSW.cpp +++ b/Storage/Tape/Formats/CSW.cpp @@ -8,6 +8,8 @@ #include "CSW.hpp" +#include "../../FileHolder.hpp" + #include using namespace Storage::Tape; diff --git a/Storage/Tape/Formats/CSW.hpp b/Storage/Tape/Formats/CSW.hpp index c0ca59f84..3c9d38369 100644 --- a/Storage/Tape/Formats/CSW.hpp +++ b/Storage/Tape/Formats/CSW.hpp @@ -10,7 +10,6 @@ #define CSW_hpp #include "../Tape.hpp" -#include "../../FileHolder.hpp" #include #include diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 47b7bbd1d..b476237f0 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -128,7 +128,7 @@ class TapePlayer: public TimedEventLoop, public ClockingHint::Source { They can also provide a delegate to be notified upon any change in the input level. */ -class BinaryTapePlayer: public TapePlayer { +class BinaryTapePlayer : public TapePlayer { public: BinaryTapePlayer(int input_clock_rate); void set_motor_control(bool enabled); @@ -145,7 +145,7 @@ class BinaryTapePlayer: public TapePlayer { }; void set_delegate(Delegate *delegate); - ClockingHint::Preference preferred_clocking() override; + ClockingHint::Preference preferred_clocking() final; protected: Delegate *delegate_ = nullptr;