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:
commit
4fdf01a1a8
@ -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));
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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() {}
|
||||||
|
@ -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);
|
||||||
|
@ -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 */,
|
||||||
|
@ -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>
|
||||||
|
@ -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));
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
41
Storage/MassStorage/Formats/DAT.cpp
Normal file
41
Storage/MassStorage/Formats/DAT.cpp
Normal 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);
|
||||||
|
}
|
40
Storage/MassStorage/Formats/DAT.hpp
Normal file
40
Storage/MassStorage/Formats/DAT.hpp
Normal 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 */
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user