diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 76d655cfc..d5a34167b 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -61,10 +61,6 @@ static std::vector> Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { auto target = std::make_unique(); - target->confidence = 0.5; // TODO: a proper estimation - target->has_dfs = false; - target->has_adfs = false; - target->should_shift_restart = false; // strip out inappropriate cartridges target->media.cartridges = AcornCartridgesFrom(media.cartridges); @@ -111,9 +107,10 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); if(dfs_catalogue || adfs_catalogue) { // Accept the disk and determine whether DFS or ADFS ROMs are implied. + // Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO. target->media.disks = media.disks; target->has_dfs = bool(dfs_catalogue); - target->has_adfs = bool(adfs_catalogue); + target->has_pres_adfs = bool(adfs_catalogue); // Check whether a simple shift+break will do for loading this disk. Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; @@ -144,6 +141,28 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me } } + // Enable the Acorn ADFS if a mass-storage device is attached; + // unlike the Pres ADFS it retains SCSI logic. + if(!media.mass_storage_devices.empty()) { + target->has_pres_adfs = false; // To override a floppy selection, if one was made. + target->has_acorn_adfs = true; + + // Assume some sort of later-era Acorn work is likely to happen; + // so ensure *TYPE, etc are present. + target->has_ap6_rom = true; + target->has_sideways_ram = true; + + target->media.mass_storage_devices = media.mass_storage_devices; + + // Check for a boot option. + const auto sector = target->media.mass_storage_devices.front()->get_block(1); + if(sector[0xfd]) { + target->should_shift_restart = true; + } else { + target->loading_command = "*CAT\n"; + } + } + TargetList targets; if(!target->media.empty()) { targets.push_back(std::move(target)); diff --git a/Analyser/Static/Acorn/Target.hpp b/Analyser/Static/Acorn/Target.hpp index f415f4b30..17dad7393 100644 --- a/Analyser/Static/Acorn/Target.hpp +++ b/Analyser/Static/Acorn/Target.hpp @@ -18,7 +18,8 @@ namespace Static { namespace Acorn { struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl { - bool has_adfs = false; + bool has_acorn_adfs = false; + bool has_pres_adfs = false; bool has_dfs = false; bool has_ap6_rom = false; bool has_sideways_ram = false; @@ -27,7 +28,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl< Target() : Analyser::Static::Target(Machine::Electron) { if(needs_declare()) { - DeclareField(has_adfs); + DeclareField(has_pres_adfs); + DeclareField(has_acorn_adfs); DeclareField(has_dfs); DeclareField(has_ap6_rom); DeclareField(has_sideways_ram); diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 9384620e9..2b3cf18af 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -53,6 +53,7 @@ #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" // Mass Storage Devices (i.e. usually, hard disks) +#include "../../Storage/MassStorage/Formats/DAT.hpp" #include "../../Storage/MassStorage/Formats/HFV.hpp" // Tapes @@ -118,6 +119,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW Format("d64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // D64 + Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT Format("dmk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DMK Format("do", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DO Format("dsd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // DSD diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 15aae6c87..485bc162c 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -1089,7 +1089,7 @@ template class ConcreteMachine: return Utility::TypeRecipient::can_type(c); } - HalfCycles get_typer_delay() const final { + HalfCycles get_typer_delay(const std::string &) const final { return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0); } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 80bc3ccf7..076547ff1 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -17,6 +17,9 @@ #include "../../Configurable/StandardOptions.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Processors/6502/6502.hpp" + +#include "../../Storage/MassStorage/SCSI/SCSI.hpp" +#include "../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp" #include "../../Storage/Tape/Tape.hpp" #include "../Utility/Typer.hpp" @@ -31,7 +34,7 @@ namespace Electron { -class ConcreteMachine: +template class ConcreteMachine: public Machine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, @@ -42,10 +45,15 @@ class ConcreteMachine: public CPU::MOS6502::BusHandler, public Tape::Delegate, public Utility::TypeRecipient, - public Activity::Source { + public Activity::Source, + public SCSI::Bus::Observer, + public ClockingHint::Observer { public: ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this), + scsi_bus_(4'000'000), + hard_drive_(scsi_bus_, 0), + scsi_device_(scsi_bus_.add_device()), video_output_(ram_), sound_generator_(audio_queue_), speaker_(sound_generator_) { @@ -64,10 +72,15 @@ class ConcreteMachine: {machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781}, {machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f} }; - if(target.has_adfs) { + const size_t pres_adfs_rom_position = required_roms.size(); + if(target.has_pres_adfs) { required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993); required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e); } + const size_t acorn_adfs_rom_position = required_roms.size(); + if(target.has_acorn_adfs) { + required_roms.emplace_back(machine_name, "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6); + } const size_t dfs_rom_position = required_roms.size(); if(target.has_dfs) { required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5); @@ -91,19 +104,23 @@ class ConcreteMachine: * the keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11; * the DFS, if in use, occupies slot 1; - * the ADFS, if in use, occupies slots 4 and 5; + * the Pres ADFS, if in use, occupies slots 4 and 5; + * the Acorn ADFS, if in use, occupies slot 6; * the AP6, if in use, occupies slot 15; and * if sideways RAM was asked for, all otherwise unused slots are populated with sideways RAM. */ - if(target.has_dfs || target.has_adfs) { + if(target.has_dfs || target.has_acorn_adfs || target.has_pres_adfs) { plus3_ = std::make_unique(); if(target.has_dfs) { set_rom(ROM::Slot0, *roms[dfs_rom_position], true); } - if(target.has_adfs) { - set_rom(ROM::Slot4, *roms[2], true); - set_rom(ROM::Slot5, *roms[3], true); + if(target.has_pres_adfs) { + set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true); + set_rom(ROM::Slot5, *roms[pres_adfs_rom_position+1], true); + } + if(target.has_acorn_adfs) { + set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true); } } @@ -128,6 +145,11 @@ class ConcreteMachine: if(target.should_shift_restart) { shift_restart_counter_ = 1000000; } + + if(has_scsi_bus) { + scsi_bus_.add_observer(this); + scsi_bus_.set_clocking_hint_observer(this); + } } ~ConcreteMachine() { @@ -193,7 +215,12 @@ class ConcreteMachine: set_rom(slot, cartridge->get_segments().front().data, false); } - return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty(); + // TODO: allow this only at machine startup? + if(!media.mass_storage_devices.empty()) { + hard_drive_->set_storage(media.mass_storage_devices.front()); + } + + return !media.empty(); } forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { @@ -315,7 +342,73 @@ class ConcreteMachine: plus3_->set_control_register(*value); } else *value = 1; } + + if(has_scsi_bus && (address&0x00f0) == 0x0040) { + scsi_acknowledge_ = true; + if(!isReadOperation(operation)) { + scsi_data_ = *value; + push_scsi_output(); + } else { + *value = SCSI::data_lines(scsi_bus_.get_state()); + push_scsi_output(); + } + } break; + case 0xfc03: + if(has_scsi_bus && (address&0x00f0) == 0x0040) { + scsi_interrupt_state_ = false; + scsi_interrupt_mask_ = *value & 1; + evaluate_interrupts(); + } + break; + case 0xfc01: + if(has_scsi_bus && (address&0x00f0) == 0x0040 && isReadOperation(operation)) { + // Status byte is: + // + // b7: SCSI C/D + // b6: SCSI I/O + // b5: SCSI REQ + // b4: interrupt flag + // b3: 0 + // b2: 0 + // b1: SCSI BSY + // b0: SCSI MSG + const auto state = scsi_bus_.get_state(); + *value = + (state & SCSI::Line::Control ? 0x80 : 0x00) | + (state & SCSI::Line::Input ? 0x40 : 0x00) | + (state & SCSI::Line::Request ? 0x20 : 0x00) | + ((scsi_interrupt_state_ && scsi_interrupt_mask_) ? 0x10 : 0x00) | + (state & SCSI::Line::Busy ? 0x02 : 0x00) | + (state & SCSI::Line::Message ? 0x01 : 0x00); + + // Empirical guess: this is also the trigger to affect busy/request/acknowledge + // signalling. Maybe? + if(scsi_select_ && scsi_bus_.get_state() & SCSI::Line::Busy) { + scsi_select_ = false; + push_scsi_output(); + } + } + break; + case 0xfc02: + if(has_scsi_bus && (address&0x00f0) == 0x0040) { + scsi_select_ = true; + push_scsi_output(); + } + break; + + // SCSI locations: + // + // fc40: data, read and write + // fc41: status read + // fc42: select write + // fc43: interrupt latch + // + // + // Interrupt latch is: + // + // b0: enable or disable IRQ on REQ + // (and, possibly, writing to the latch acknowledges?) default: if(address >= 0xc000) { @@ -414,6 +507,12 @@ class ConcreteMachine: } } + if constexpr (has_scsi_bus) { + if(scsi_is_clocked_) { + scsi_bus_.run_for(Cycles(int(cycles))); + } + } + return Cycles(int(cycles)); } @@ -447,13 +546,44 @@ class ConcreteMachine: m6502_.run_for(cycles); } + void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double) final { + // Release acknowledge when request is released. + if(scsi_acknowledge_ && !(new_state & SCSI::Line::Request)) { + scsi_acknowledge_ = false; + push_scsi_output(); + } + + // Output occurs only while SCSI::Line::Input is inactive; therefore a change + // in that line affects what's on the bus. + if(((new_state^previous_bus_state_)&SCSI::Line::Input)) { + push_scsi_output(); + } + + scsi_interrupt_state_ |= (new_state^previous_bus_state_)&new_state & SCSI::Line::Request; + previous_bus_state_ = new_state; + evaluate_interrupts(); + } + + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference preference) final { + scsi_is_clocked_ = preference != ClockingHint::Preference::None; + } + void tape_did_change_interrupt_status(Tape *) final { interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); evaluate_interrupts(); } - HalfCycles get_typer_delay() const final { - return m6502_.get_is_resetting() ? Cycles(750'000) : Cycles(0); + HalfCycles get_typer_delay(const std::string &text) const final { + if(!m6502_.get_is_resetting()) { + return Cycles(0); + } + + // Add a longer delay for a command at reset that involves pressing a modifier; + // empirically this seems to be a requirement, in order to avoid a collision with + // the system's built-in modifier-at-startup test (e.g. to perform shift+break). + CharacterMapper test_mapper; + const uint16_t *const sequence = test_mapper.sequence_for_character(text[0]); + return is_modifier(Key(sequence[0])) ? Cycles(1'000'000) : Cycles(750'000); } HalfCycles get_typer_frequency() const final { @@ -494,10 +624,14 @@ class ConcreteMachine: if(activity_observer_) { activity_observer_->register_led(caps_led); activity_observer_->set_led_status(caps_led, caps_led_state_); + } - if(plus3_) { - plus3_->set_activity_observer(observer); - } + if(plus3_) { + plus3_->set_activity_observer(observer); + } + + if(has_scsi_bus) { + scsi_bus_.set_activity_observer(observer); } } @@ -591,7 +725,12 @@ class ConcreteMachine: } else { interrupt_status_ &= ~1; } - m6502_.set_irq_line(interrupt_status_ & 1); + + if constexpr (has_scsi_bus) { + m6502_.set_irq_line((scsi_interrupt_state_ && scsi_interrupt_mask_) | (interrupt_status_ & 1)); + } else { + m6502_.set_irq_line(interrupt_status_ & 1); + } } CPU::MOS6502::Processor m6502_; @@ -635,6 +774,25 @@ class ConcreteMachine: bool is_holding_shift_ = false; int shift_restart_counter_ = 0; + // Hard drive. + SCSI::Bus scsi_bus_; + SCSI::Target::Target hard_drive_; + SCSI::BusState previous_bus_state_ = SCSI::DefaultBusState; + const size_t scsi_device_ = 0; + uint8_t scsi_data_ = 0; + bool scsi_select_ = false; + bool scsi_acknowledge_ = false; + bool scsi_is_clocked_ = false; + bool scsi_interrupt_state_ = false; + bool scsi_interrupt_mask_ = false; + void push_scsi_output() { + scsi_bus_.set_device_output(scsi_device_, + (scsi_bus_.get_state()&SCSI::Line::Input ? 0 : scsi_data_) | + (scsi_select_ ? SCSI::Line::SelectTarget : 0) | + (scsi_acknowledge_ ? SCSI::Line::Acknowledge : 0) + ); + } + // Outputs VideoOutput video_output_; @@ -657,7 +815,12 @@ using namespace Electron; Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { using Target = Analyser::Static::Acorn::Target; const Target *const acorn_target = dynamic_cast(target); - return new Electron::ConcreteMachine(*acorn_target, rom_fetcher); + + if(acorn_target->media.mass_storage_devices.empty()) { + return new Electron::ConcreteMachine(*acorn_target, rom_fetcher); + } else { + return new Electron::ConcreteMachine(*acorn_target, rom_fetcher); + } } Machine::~Machine() {} diff --git a/Machines/Electron/Keyboard.cpp b/Machines/Electron/Keyboard.cpp index 887ac5aff..c554c2cbe 100644 --- a/Machines/Electron/Keyboard.cpp +++ b/Machines/Electron/Keyboard.cpp @@ -16,6 +16,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { default: break; BIND(BackTick, KeyCopy); + BIND(Backslash, KeyCopy); BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4); BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9); diff --git a/Machines/Electron/Keyboard.hpp b/Machines/Electron/Keyboard.hpp index d41f6500c..3583bd55d 100644 --- a/Machines/Electron/Keyboard.hpp +++ b/Machines/Electron/Keyboard.hpp @@ -31,11 +31,15 @@ enum Key: uint16_t { KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01, // Virtual keys. - KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0, + KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0, KeyBreak = 0xfffd, }; +constexpr bool is_modifier(Key key) { + return (key == KeyShift) || (key == KeyControl) || (key == KeyFunc); +} + struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final; }; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index f0fc67f07..5e1993463 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -413,7 +413,7 @@ template ram_top_) { - if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1]; + if(!isWriteOperation(operation)) *value = paged_rom_[address - ram_top_ - 1]; // 024D = 0 => fast; otherwise slow // E6C9 = read byte: return byte in A @@ -432,39 +432,39 @@ template = 0x3f4) { - if(isReadOperation(operation)) *value = jasmin_.read(address); + if(!isWriteOperation(operation)) *value = jasmin_.read(address); else jasmin_.write(address, *value); } break; case DiskInterface::Microdisc: switch(address) { case 0x0310: case 0x0311: case 0x0312: case 0x0313: - if(isReadOperation(operation)) *value = microdisc_.read(address); + if(!isWriteOperation(operation)) *value = microdisc_.read(address); else microdisc_.write(address, *value); break; case 0x314: case 0x315: case 0x316: case 0x317: - if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register(); + if(!isWriteOperation(operation)) *value = microdisc_.get_interrupt_request_register(); else microdisc_.set_control_register(*value); break; case 0x318: case 0x319: case 0x31a: case 0x31b: - if(isReadOperation(operation)) *value = microdisc_.get_data_request_register(); + if(!isWriteOperation(operation)) *value = microdisc_.get_data_request_register(); break; } break; case DiskInterface::Pravetz: if(address >= 0x0320) { - if(isReadOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)]; + if(!isWriteOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)]; else { switch(address) { case 0x380: case 0x381: case 0x382: case 0x383: @@ -476,13 +476,13 @@ template = 0x9800 && address <= 0xc000) update_video(); diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index 5ca3bd57d..d6bdc80f7 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -109,7 +109,7 @@ class TypeRecipient: public Typer::Delegate { /// Attaches a typer to this class that will type @c string using @c character_mapper as a source. void add_typer(const std::string &string) { if(!typer_) { - typer_ = std::make_unique(string, get_typer_delay(), get_typer_frequency(), character_mapper, this); + typer_ = std::make_unique(string, get_typer_delay(string), get_typer_frequency(), character_mapper, this); } else { typer_->append(string); } @@ -137,7 +137,7 @@ class TypeRecipient: public Typer::Delegate { typer_ = nullptr; } - virtual HalfCycles get_typer_delay() const { return HalfCycles(0); } + virtual HalfCycles get_typer_delay(const std::string &) const { return HalfCycles(0); } virtual HalfCycles get_typer_frequency() const { return HalfCycles(0); } std::unique_ptr typer_; diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp index 3841a58c5..96a10efbc 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/ZX8081/ZX8081.cpp @@ -387,7 +387,7 @@ template class ConcreteMachine: } // MARK: - Typer timing - HalfCycles get_typer_delay() const final { + HalfCycles get_typer_delay(const std::string &) const final { return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0); } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 9366d6d75..bb126fd0d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -912,6 +912,7 @@ 4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; }; 4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; }; 4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; }; + 4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; }; 4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; }; 4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; }; 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; @@ -1916,6 +1917,8 @@ 4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = ""; }; 4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = ""; }; 4BE8EB5525C0EA490040BC40 /* Sizes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sizes.hpp; sourceTree = ""; }; + 4BE8EB6425C750B50040BC40 /* DAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DAT.cpp; sourceTree = ""; }; + 4BE8EB6525C750B50040BC40 /* DAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DAT.hpp; sourceTree = ""; }; 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = ""; }; 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = ""; }; 4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = ""; }; @@ -2870,6 +2873,8 @@ children = ( 4B74CF7F2312FA9C00500CE8 /* HFV.hpp */, 4B74CF802312FA9C00500CE8 /* HFV.cpp */, + 4BE8EB6425C750B50040BC40 /* DAT.cpp */, + 4BE8EB6525C750B50040BC40 /* DAT.hpp */, ); path = Formats; sourceTree = ""; @@ -5239,6 +5244,7 @@ 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, 4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, + 4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */, 4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */, 4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */, 4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index c2e83fce3..0660c173c 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -567,6 +567,24 @@ CFBundleTypeRole Editor + LSTypeIsPackage + + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + + + CFBundleTypeExtensions + + dat + + CFBundleTypeName + Electron/BBC Hard Disk Image + CFBundleTypeOSTypes + + ???? + + CFBundleTypeRole + Editor LSHandlerRank Owner LSTypeIsPackage diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 67ea100cf..fa0ccf6e3 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -121,7 +121,7 @@ using Target = Analyser::Static::Acorn::Target; auto target = std::make_unique(); target->has_dfs = dfs; - target->has_adfs = adfs; + target->has_pres_adfs = adfs; target->has_ap6_rom = ap6; target->has_sideways_ram = sidewaysRAM; _targets.push_back(std::move(target)); diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 091cbde9a..761d931d4 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -1184,7 +1184,7 @@ void MainWindow::start_electron() { auto target = std::make_unique(); target->has_dfs = ui->electronDFSCheckBox->isChecked(); - target->has_adfs = ui->electronADFSCheckBox->isChecked(); + target->has_pres_adfs = ui->electronADFSCheckBox->isChecked(); target->has_ap6_rom = ui->electronAP6CheckBox->isChecked(); target->has_sideways_ram = ui->electronSidewaysRAMCheckBox->isChecked(); diff --git a/ROMImages/Electron/readme.txt b/ROMImages/Electron/readme.txt index 995145ee9..349144e2a 100644 --- a/ROMImages/Electron/readme.txt +++ b/ROMImages/Electron/readme.txt @@ -8,10 +8,9 @@ DFS-1770-2.20.rom — used only if the user opens a DFS disk image ADFS-E00_1.rom — used only if the user opens an ADFS disk image ADFS-E00_2.rom AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below. +adfs.rom - used only if the user opens a hard disk image Possibly to be desired in the future: -* adfs.rom -* ElectronExpansionRomPresAP2-v1.23.rom * os300.rom Commands that trigger a request for the AP6v133 ROM: diff --git a/Storage/MassStorage/Formats/DAT.cpp b/Storage/MassStorage/Formats/DAT.cpp new file mode 100644 index 000000000..ceb00c7ab --- /dev/null +++ b/Storage/MassStorage/Formats/DAT.cpp @@ -0,0 +1,41 @@ +// +// DAT.cpp +// Clock Signal +// +// Created by Thomas Harte on 31/01/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "DAT.hpp" + +using namespace Storage::MassStorage; + +DAT::DAT(const std::string &file_name) : file_(file_name) { + // Is the file a multiple of 256 bytes in size? + const auto file_size = file_.stats().st_size; + if(file_size & 255) throw std::exception(); + + // Does it contain the 'Hugo' signature? + file_.seek(0x201, SEEK_SET); + if(!file_.check_signature("Hugo")) { + throw std::exception(); + } +} + +size_t DAT::get_block_size() { + return 256; +} + +size_t DAT::get_number_of_blocks() { + return size_t(file_.stats().st_size) / 256; +} + +std::vector DAT::get_block(size_t address) { + file_.seek(long(address * 256), SEEK_SET); + return file_.read(256); +} + +void DAT::set_block(size_t address, const std::vector &contents) { + file_.seek(long(address * 256), SEEK_SET); + file_.write(contents); +} diff --git a/Storage/MassStorage/Formats/DAT.hpp b/Storage/MassStorage/Formats/DAT.hpp new file mode 100644 index 000000000..7ec0350e4 --- /dev/null +++ b/Storage/MassStorage/Formats/DAT.hpp @@ -0,0 +1,40 @@ +// +// DAT.hpp +// Clock Signal +// +// Created by Thomas Harte on 31/01/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef MassStorage_DAT_hpp +#define MassStorage_DAT_hpp + +#include "../MassStorageDevice.hpp" +#include "../../FileHolder.hpp" + +namespace Storage { +namespace MassStorage { + +/*! + Provides a @c MassStorageDevice containing an Acorn ADFS image, which is just a + sector dump of an ADFS volume. It will be validated for an ADFS catalogue and communicate + in 256-byte blocks. +*/ +class DAT: public MassStorageDevice { + public: + DAT(const std::string &file_name); + + private: + FileHolder file_; + + /* 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; +}; + +} +} + +#endif /* MassStorage_DAT_hpp */ diff --git a/Storage/MassStorage/SCSI/DirectAccessDevice.cpp b/Storage/MassStorage/SCSI/DirectAccessDevice.cpp index 2fc6039b7..f6944d2f0 100644 --- a/Storage/MassStorage/SCSI/DirectAccessDevice.cpp +++ b/Storage/MassStorage/SCSI/DirectAccessDevice.cpp @@ -38,6 +38,7 @@ bool DirectAccessDevice::write(const Target::CommandState &state, Target::Respon if(!device_) return false; const auto specs = state.read_write_specs(); + LOG("Write: " << specs.number_of_blocks << " to " << specs.address); 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(); diff --git a/Storage/MassStorage/SCSI/SCSI.hpp b/Storage/MassStorage/SCSI/SCSI.hpp index 538f1f30f..c896f7c3b 100644 --- a/Storage/MassStorage/SCSI/SCSI.hpp +++ b/Storage/MassStorage/SCSI/SCSI.hpp @@ -90,6 +90,13 @@ constexpr double DeskewDelay = ns(45.0); /// any two devices. constexpr double CableSkew = ns(10.0); +/*! + @returns The value of the data lines per @c state. +*/ +constexpr uint8_t data_lines(BusState state) { + return uint8_t(state & 0xff); +} + #undef ns #undef us diff --git a/Storage/MassStorage/SCSI/TargetImplementation.hpp b/Storage/MassStorage/SCSI/TargetImplementation.hpp index ff6ccb297..5a4596d1a 100644 --- a/Storage/MassStorage/SCSI/TargetImplementation.hpp +++ b/Storage/MassStorage/SCSI/TargetImplementation.hpp @@ -17,8 +17,8 @@ template void Target::scsi_bus_did_change(Bus *, B /* "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." + are false. It then asserts the signal within a selection + abort time." */ // Wait for deskew, at the very least.