1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 01:31:42 +00:00

Merge pull request #868 from TomHarte/ElectronSCSI

Adds Electron hard disk support.
This commit is contained in:
Thomas Harte 2021-02-02 20:43:08 -05:00 committed by GitHub
commit 4fdf01a1a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 319 additions and 27 deletions

View File

@ -61,10 +61,6 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>(); auto target = std::make_unique<Target>();
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 // strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.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 == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) { if(dfs_catalogue || adfs_catalogue) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied. // 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->media.disks = media.disks;
target->has_dfs = bool(dfs_catalogue); 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. // Check whether a simple shift+break will do for loading this disk.
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; 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; TargetList targets;
if(!target->media.empty()) { if(!target->media.empty()) {
targets.push_back(std::move(target)); targets.push_back(std::move(target));

View File

@ -18,7 +18,8 @@ namespace Static {
namespace Acorn { namespace Acorn {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> { struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_adfs = false; bool has_acorn_adfs = false;
bool has_pres_adfs = false;
bool has_dfs = false; bool has_dfs = false;
bool has_ap6_rom = false; bool has_ap6_rom = false;
bool has_sideways_ram = 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) { Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) { if(needs_declare()) {
DeclareField(has_adfs); DeclareField(has_pres_adfs);
DeclareField(has_acorn_adfs);
DeclareField(has_dfs); DeclareField(has_dfs);
DeclareField(has_ap6_rom); DeclareField(has_ap6_rom);
DeclareField(has_sideways_ram); DeclareField(has_sideways_ram);

View File

@ -51,6 +51,7 @@
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks) // Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp" #include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes // Tapes
@ -105,6 +106,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64 Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD

View File

@ -17,6 +17,9 @@
#include "../../Configurable/StandardOptions.hpp" #include "../../Configurable/StandardOptions.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/6502/6502.hpp" #include "../../Processors/6502/6502.hpp"
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
#include "../Utility/Typer.hpp" #include "../Utility/Typer.hpp"
@ -31,7 +34,7 @@
namespace Electron { namespace Electron {
class ConcreteMachine: template <bool has_scsi_bus> class ConcreteMachine:
public Machine, public Machine,
public MachineTypes::TimedMachine, public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer, public MachineTypes::ScanProducer,
@ -42,10 +45,15 @@ class ConcreteMachine:
public CPU::MOS6502::BusHandler, public CPU::MOS6502::BusHandler,
public Tape::Delegate, public Tape::Delegate,
public Utility::TypeRecipient<CharacterMapper>, public Utility::TypeRecipient<CharacterMapper>,
public Activity::Source { public Activity::Source,
public SCSI::Bus::Observer,
public ClockingHint::Observer {
public: public:
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this), m6502_(*this),
scsi_bus_(4'000'000),
hard_drive_(scsi_bus_, 0),
scsi_device_(scsi_bus_.add_device()),
video_output_(ram_), video_output_(ram_),
sound_generator_(audio_queue_), sound_generator_(audio_queue_),
speaker_(sound_generator_) { speaker_(sound_generator_) {
@ -64,10 +72,15 @@ class ConcreteMachine:
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781}, {machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f} {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, 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); 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(); const size_t dfs_rom_position = required_roms.size();
if(target.has_dfs) { if(target.has_dfs) {
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5); 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 keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11;
* the DFS, if in use, occupies slot 1; * 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 * 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 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<Plus3>(); plus3_ = std::make_unique<Plus3>();
if(target.has_dfs) { if(target.has_dfs) {
set_rom(ROM::Slot0, *roms[dfs_rom_position], true); set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
} }
if(target.has_adfs) { if(target.has_pres_adfs) {
set_rom(ROM::Slot4, *roms[2], true); set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true);
set_rom(ROM::Slot5, *roms[3], 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) { if(target.should_shift_restart) {
shift_restart_counter_ = 1000000; shift_restart_counter_ = 1000000;
} }
if(has_scsi_bus) {
scsi_bus_.add_observer(this);
scsi_bus_.set_clocking_hint_observer(this);
}
} }
~ConcreteMachine() { ~ConcreteMachine() {
@ -193,7 +215,12 @@ class ConcreteMachine:
set_rom(slot, cartridge->get_segments().front().data, false); 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) { 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); plus3_->set_control_register(*value);
} else *value = 1; } 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; 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: default:
if(address >= 0xc000) { 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)); return Cycles(int(cycles));
} }
@ -447,6 +546,28 @@ class ConcreteMachine:
m6502_.run_for(cycles); 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 { void tape_did_change_interrupt_status(Tape *) final {
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
evaluate_interrupts(); evaluate_interrupts();
@ -503,10 +624,14 @@ class ConcreteMachine:
if(activity_observer_) { if(activity_observer_) {
activity_observer_->register_led(caps_led); activity_observer_->register_led(caps_led);
activity_observer_->set_led_status(caps_led, caps_led_state_); activity_observer_->set_led_status(caps_led, caps_led_state_);
}
if(plus3_) { if(plus3_) {
plus3_->set_activity_observer(observer); plus3_->set_activity_observer(observer);
} }
if(has_scsi_bus) {
scsi_bus_.set_activity_observer(observer);
} }
} }
@ -600,8 +725,13 @@ class ConcreteMachine:
} else { } else {
interrupt_status_ &= ~1; 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); m6502_.set_irq_line(interrupt_status_ & 1);
} }
}
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
@ -644,6 +774,25 @@ class ConcreteMachine:
bool is_holding_shift_ = false; bool is_holding_shift_ = false;
int shift_restart_counter_ = 0; int shift_restart_counter_ = 0;
// Hard drive.
SCSI::Bus scsi_bus_;
SCSI::Target::Target<SCSI::DirectAccessDevice> 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 // Outputs
VideoOutput video_output_; VideoOutput video_output_;
@ -666,7 +815,12 @@ using namespace Electron;
Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Acorn::Target; using Target = Analyser::Static::Acorn::Target;
const Target *const acorn_target = dynamic_cast<const Target *>(target); const Target *const acorn_target = dynamic_cast<const Target *>(target);
return new Electron::ConcreteMachine(*acorn_target, rom_fetcher);
if(acorn_target->media.mass_storage_devices.empty()) {
return new Electron::ConcreteMachine<false>(*acorn_target, rom_fetcher);
} else {
return new Electron::ConcreteMachine<true>(*acorn_target, rom_fetcher);
}
} }
Machine::~Machine() {} Machine::~Machine() {}

View File

@ -16,6 +16,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
default: break; default: break;
BIND(BackTick, KeyCopy); BIND(BackTick, KeyCopy);
BIND(Backslash, KeyCopy);
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4); 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); BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);

View File

@ -850,6 +850,7 @@
4BE211DE253E4E4800435408 /* 65C02_no_Rockwell_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BE211DD253E4E4800435408 /* 65C02_no_Rockwell_test.bin */; }; 4BE211DE253E4E4800435408 /* 65C02_no_Rockwell_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BE211DD253E4E4800435408 /* 65C02_no_Rockwell_test.bin */; };
4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; }; 4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; };
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.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 */; }; 4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; };
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; }; 4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
@ -1788,6 +1789,8 @@
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = "<group>"; }; 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = "<group>"; };
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; }; 4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; }; 4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
4BE8EB6425C750B50040BC40 /* DAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DAT.cpp; sourceTree = "<group>"; };
4BE8EB6525C750B50040BC40 /* DAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DAT.hpp; sourceTree = "<group>"; };
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = "<group>"; }; 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = "<group>"; };
4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; }; 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; };
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; }; 4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
@ -2733,6 +2736,8 @@
children = ( children = (
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */, 4B74CF7F2312FA9C00500CE8 /* HFV.hpp */,
4B74CF802312FA9C00500CE8 /* HFV.cpp */, 4B74CF802312FA9C00500CE8 /* HFV.cpp */,
4BE8EB6425C750B50040BC40 /* DAT.cpp */,
4BE8EB6525C750B50040BC40 /* DAT.hpp */,
); );
path = Formats; path = Formats;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4770,6 +4775,7 @@
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */, 4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */, 4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */, 4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */, 4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,

View File

@ -552,6 +552,26 @@
<key>NSDocumentClass</key> <key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> <string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict> </dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dat</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC Hard Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array> </array>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>

View File

@ -99,7 +99,7 @@
using Target = Analyser::Static::Acorn::Target; using Target = Analyser::Static::Acorn::Target;
auto target = std::make_unique<Target>(); auto target = std::make_unique<Target>();
target->has_dfs = dfs; target->has_dfs = dfs;
target->has_adfs = adfs; target->has_pres_adfs = adfs;
target->has_ap6_rom = ap6; target->has_ap6_rom = ap6;
target->has_sideways_ram = sidewaysRAM; target->has_sideways_ram = sidewaysRAM;
_targets.push_back(std::move(target)); _targets.push_back(std::move(target));

View File

@ -1163,7 +1163,7 @@ void MainWindow::start_electron() {
auto target = std::make_unique<Target>(); auto target = std::make_unique<Target>();
target->has_dfs = ui->electronDFSCheckBox->isChecked(); 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_ap6_rom = ui->electronAP6CheckBox->isChecked();
target->has_sideways_ram = ui->electronSidewaysRAMCheckBox->isChecked(); target->has_sideways_ram = ui->electronSidewaysRAMCheckBox->isChecked();

View File

@ -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_1.rom — used only if the user opens an ADFS disk image
ADFS-E00_2.rom 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. 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: Possibly to be desired in the future:
* adfs.rom
* ElectronExpansionRomPresAP2-v1.23.rom
* os300.rom * os300.rom
Commands that trigger a request for the AP6v133 ROM: Commands that trigger a request for the AP6v133 ROM:

View File

@ -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<uint8_t> 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<uint8_t> &contents) {
file_.seek(long(address * 256), SEEK_SET);
file_.write(contents);
}

View File

@ -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<uint8_t> get_block(size_t address) final;
void set_block(size_t address, const std::vector<uint8_t> &) final;
};
}
}
#endif /* MassStorage_DAT_hpp */

View File

@ -38,6 +38,7 @@ bool DirectAccessDevice::write(const Target::CommandState &state, Target::Respon
if(!device_) return false; if(!device_) return false;
const auto specs = state.read_write_specs(); 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) { 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 received_data = state.received_data();

View File

@ -90,6 +90,13 @@ constexpr double DeskewDelay = ns(45.0);
/// any two devices. /// any two devices.
constexpr double CableSkew = ns(10.0); 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 ns
#undef us #undef us

View File

@ -17,8 +17,8 @@ template <typename Executor> void Target<Executor>::scsi_bus_did_change(Bus *, B
/* /*
"The target determines that it is selected when the SEL# signal "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 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 are false. It then asserts the signal within a selection
time." abort time."
*/ */
// Wait for deskew, at the very least. // Wait for deskew, at the very least.