From 1249fb598b776256bc1a7578ae87404e96f64ec2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 29 Oct 2020 21:03:02 -0400 Subject: [PATCH] Factors Apple's RTC out of the Macintosh. --- Components/AppleClock/AppleClock.hpp | 192 ++++++++++++++++++ Machines/Apple/Macintosh/Macintosh.cpp | 8 +- Machines/Apple/Macintosh/RealTimeClock.hpp | 173 ---------------- .../Clock Signal.xcodeproj/project.pbxproj | 13 +- 4 files changed, 206 insertions(+), 180 deletions(-) create mode 100644 Components/AppleClock/AppleClock.hpp delete mode 100644 Machines/Apple/Macintosh/RealTimeClock.hpp diff --git a/Components/AppleClock/AppleClock.hpp b/Components/AppleClock/AppleClock.hpp new file mode 100644 index 000000000..6dbf1dc6d --- /dev/null +++ b/Components/AppleClock/AppleClock.hpp @@ -0,0 +1,192 @@ +// +// RealTimeClock.hpp +// Clock Signal +// +// Created by Thomas Harte on 07/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Apple_RealTimeClock_hpp +#define Apple_RealTimeClock_hpp + +namespace Apple { +namespace Clock { + +/*! + Models Apple's real-time clocks, as contained in the Macintosh and IIgs. + + Since tracking of time is pushed to this class, it is assumed + that whomever is translating real time into emulated time + will also signal interrupts — this is just the storage and time counting. +*/ +class ClockStorage { + public: + ClockStorage() { + // TODO: this should persist, if possible, rather than + // being default initialised. + constexpr uint8_t default_data[] = { + 0xa8, 0x00, 0x00, 0x00, + 0xcc, 0x0a, 0xcc, 0x0a, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x63, 0x00, + 0x03, 0x88, 0x00, 0x4c + }; + memcpy(data_, default_data, sizeof(data_)); + memset(&data_[sizeof(default_data)], 0xff, sizeof(data_) - sizeof(default_data)); + } + + /*! + Advances the clock by 1 second. + + The caller should also signal an interrupt. + */ + void update() { + for(int c = 0; c < 4; ++c) { + ++seconds_[c]; + if(seconds_[c]) break; + } + } + + protected: + static constexpr uint16_t NoResult = 0x100; + static constexpr uint16_t DidWrite = 0x101; + uint16_t perform(uint8_t command) { + /* + Documented commands: + + z0000001 Seconds register 0 (lowest order byte) + z0000101 Seconds register 1 + z0001001 Seconds register 2 + z0001101 Seconds register 3 + 00110001 Test register (write only) + 00110101 Write-protect register (write only) + z010aa01 RAM addresses 0x10 - 0x13 + z1aaaa01 RAM addresses 0x00 – 0x0f + + z0111abc, followed by 0defgh00 + RAM address abcdefgh + + z = 1 => a read; z = 0 => a write. + + The top bit of the write-protect register enables (0) or disables (1) + writes to other locations. + + All the documentation says about the test register is to set the top + two bits to 0 for normal operation. Abnormal operation is undefined. + */ + + if(!is_writing_) { + // Decode an address; use values >= 0x100 to represent clock time. + address_ = (command >> 2) & 0x1f; + if(address_ < 4) { + address_ |= 0x100; + } else if(address_ >= 0x10) { + address_ &= 0xf; + } else if(address_ >= 0x8 && address_ <= 0xb) { + address_ = 0x10 + (address_ & 0x3); + } + + // If this is a read, return a result; otherwise prepare to write. + if(command & 0x80) { + return (address_ & 0x100) ? seconds_[address_ & 0xff] : data_[address_]; + } + + is_writing_ = true; + return NoResult; + } else { + // First test: is this to the write-protect register? + if(address_ == 0xd) { + write_protect_ = command; + } + + // No other writing is permitted if the write protect + // register won't allow it. + if(!(write_protect_ & 0x80)) { + if(address_ & 0x100) { + seconds_[address_ & 0xff] = command; + } else { + data_[address_] = command; + } + } + + is_writing_ = false; + return DidWrite; + } + } + + + private: + uint8_t data_[256]; + uint8_t seconds_[4]; + uint8_t write_protect_; + bool is_writing_ = false; + int address_; +}; + +/*! + Provides the serial interface implemented by the Macintosh. +*/ +class SerialClock: public ClockStorage { + public: + /*! + Sets the current clock and data inputs to the clock. + */ + void set_input(bool clock, bool data) { + // The data line is valid when the clock transitions to level 0. + if(clock && !previous_clock_) { + // Shift into the command_ register, no matter what. + command_ = uint16_t((command_ << 1) | (data ? 1 : 0)); + result_ <<= 1; + + // Increment phase. + ++phase_; + + // If a whole byte has been collected, push it onwards. + if(!(phase_&7)) { + // Begin pessimistically. + const auto effect = perform(uint8_t(command_)); + + switch(effect) { + case ClockStorage::NoResult: + break; + default: + result_ = uint8_t(effect); + break; + case ClockStorage::DidWrite: + abort(); + break; + } + } + } + + previous_clock_ = clock; + } + + /*! + Reads the current data output level from the clock. + */ + bool get_data() { + return !!(result_ & 0x80); + } + + /*! + Announces that a serial command has been aborted. + */ + void abort() { + result_ = 0; + phase_ = 0; + command_ = 0; + } + + private: + int phase_ = 0; + uint16_t command_; + uint8_t result_ = 0; + + bool previous_clock_ = false; +}; + +} +} + +#endif /* Apple_RealTimeClock_hpp */ diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 9ac6791c8..22f366ad2 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -13,7 +13,6 @@ #include "DeferredAudio.hpp" #include "DriveSpeedAccumulator.hpp" #include "Keyboard.hpp" -#include "RealTimeClock.hpp" #include "Video.hpp" #include "../../MachineTypes.hpp" @@ -32,6 +31,7 @@ #include "../../../Components/5380/ncr5380.hpp" #include "../../../Components/6522/6522.hpp" #include "../../../Components/8530/z8530.hpp" +#include "../../../Components/AppleClock/AppleClock.hpp" #include "../../../Components/DiskII/IWM.hpp" #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" #include "../../../Processors/68000/68000.hpp" @@ -648,7 +648,7 @@ template class ConcreteMachin class VIAPortHandler: public MOS::MOS6522::PortHandler { public: - VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : + VIAPortHandler(ConcreteMachine &machine, Apple::Clock::SerialClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} using Port = MOS::MOS6522::Port; @@ -751,7 +751,7 @@ template class ConcreteMachin private: ConcreteMachine &machine_; - RealTimeClock &clock_; + Apple::Clock::SerialClock &clock_; Keyboard &keyboard_; DeferredAudio &audio_; IWMActor &iwm_; @@ -766,7 +766,7 @@ template class ConcreteMachin DeferredAudio audio_; Video video_; - RealTimeClock clock_; + Apple::Clock::SerialClock clock_; Keyboard keyboard_; MOS::MOS6522::MOS6522 via_; diff --git a/Machines/Apple/Macintosh/RealTimeClock.hpp b/Machines/Apple/Macintosh/RealTimeClock.hpp deleted file mode 100644 index 9be04bb30..000000000 --- a/Machines/Apple/Macintosh/RealTimeClock.hpp +++ /dev/null @@ -1,173 +0,0 @@ -// -// RealTimeClock.hpp -// Clock Signal -// -// Created by Thomas Harte on 07/05/2019. -// Copyright © 2019 Thomas Harte. All rights reserved. -// - -#ifndef RealTimeClock_hpp -#define RealTimeClock_hpp - -#include "../../Utility/MemoryFuzzer.hpp" - -namespace Apple { -namespace Macintosh { - -/*! - Models the storage component of Apple's real-time clock. - - Since tracking of time is pushed to this class, it is assumed - that whomever is translating real time into emulated time - will notify the VIA of a potential interrupt. -*/ -class RealTimeClock { - public: - RealTimeClock() { - // TODO: this should persist, if possible, rather than - // being default initialised. - const uint8_t default_data[] = { - 0xa8, 0x00, 0x00, 0x00, - 0xcc, 0x0a, 0xcc, 0x0a, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x63, 0x00, - 0x03, 0x88, 0x00, 0x4c - }; - memcpy(data_, default_data, sizeof(data_)); - } - - /*! - Advances the clock by 1 second. - - The caller should also notify the VIA. - */ - void update() { - for(int c = 0; c < 4; ++c) { - ++seconds_[c]; - if(seconds_[c]) break; - } - } - - /*! - Sets the current clock and data inputs to the clock. - */ - void set_input(bool clock, bool data) { - /* - Documented commands: - - z0000001 Seconds register 0 (lowest order byte) - z0000101 Seconds register 1 - z0001001 Seconds register 2 - z0001101 Seconds register 3 - 00110001 Test register (write only) - 00110101 Write-protect register (write only) - z010aa01 RAM addresses 0x10 - 0x13 - z1aaaa01 RAM addresses 0x00 – 0x0f - - z = 1 => a read; z = 0 => a write. - - The top bit of the write-protect register enables (0) or disables (1) - writes to other locations. - - All the documentation says about the test register is to set the top - two bits to 0 for normal operation. Abnormal operation is undefined. - - The data line is valid when the clock transitions to level 0. - */ - - if(clock && !previous_clock_) { - // Shift into the command_ register, no matter what. - command_ = uint16_t((command_ << 1) | (data ? 1 : 0)); - result_ <<= 1; - - // Increment phase. - ++phase_; - - // When phase hits 8, inspect the command. - // If it's a read, prepare a result. - if(phase_ == 8) { - if(command_ & 0x80) { - // A read. - const auto address = (command_ >> 2) & 0x1f; - - // Begin pessimistically. - result_ = 0xff; - - if(address < 4) { - result_ = seconds_[address]; - } else if(address >= 0x10) { - result_ = data_[address & 0xf]; - } else if(address >= 0x8 && address <= 0xb) { - result_ = data_[0x10 + (address & 0x3)]; - } - } - } - - // If phase hits 16 and this was a read command, - // just stop. If it was a write command, do the - // actual write. - if(phase_ == 16) { - if(!(command_ & 0x8000)) { - // A write. - - const auto address = (command_ >> 10) & 0x1f; - const uint8_t value = uint8_t(command_ & 0xff); - - // First test: is this to the write-protect register? - if(address == 0xd) { - write_protect_ = value; - } - - // No other writing is permitted if the write protect - // register won't allow it. - if(!(write_protect_ & 0x80)) { - if(address < 4) { - seconds_[address] = value; - } else if(address >= 0x10) { - data_[address & 0xf] = value; - } else if(address >= 0x8 && address <= 0xb) { - data_[0x10 + (address & 0x3)] = value; - } - } - } - - // A phase of 16 always ends the command, so reset here. - abort(); - } - } - - previous_clock_ = clock; - } - - /*! - Reads the current data output level from the clock. - */ - bool get_data() { - return !!(result_ & 0x80); - } - - /*! - Announces that a serial command has been aborted. - */ - void abort() { - result_ = 0; - phase_ = 0; - command_ = 0; - } - - private: - uint8_t data_[0x14]; - uint8_t seconds_[4]; - uint8_t write_protect_; - - int phase_ = 0; - uint16_t command_; - uint8_t result_ = 0; - - bool previous_clock_ = false; -}; - -} -} - -#endif /* RealTimeClock_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 8caf4dce9..9fc455db9 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1314,6 +1314,7 @@ 4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = ""; }; 4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = ""; }; 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = ""; }; + 4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = ""; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = ""; }; 4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = ""; }; 4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; @@ -1736,7 +1737,6 @@ 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = ""; }; 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = ""; }; 4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = ""; }; - 4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealTimeClock.hpp; sourceTree = ""; }; 4BD0FBC2233706A200148981 /* CSApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSApplication.m; sourceTree = ""; }; 4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = ""; }; 4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = ""; }; @@ -3014,6 +3014,14 @@ path = AmstradCPC; sourceTree = ""; }; + 4B8DF4EC254B840B00F3433C /* AppleClock */ = { + isa = PBXGroup; + children = ( + 4B8DF4ED254B840B00F3433C /* AppleClock.hpp */, + ); + path = AppleClock; + sourceTree = ""; + }; 4B8EF6051FE5AF830076CCDD /* Implementation */ = { isa = PBXGroup; children = ( @@ -3456,7 +3464,6 @@ children = ( 4B85322922778E4200F26553 /* Comparative68000.hpp */, 4B90467222C6FA31000E2074 /* TestRunner68000.hpp */, - 4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */, 4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */, 4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */, 4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */, @@ -3692,6 +3699,7 @@ 4BB244D222AABAF500BE20E5 /* 8530 */, 4B0E04F71FC9F2C800F43484 /* 9918 */, 4B92E267234AE35000CD6D1B /* 68901 */, + 4B8DF4EC254B840B00F3433C /* AppleClock */, 4B595FAA2086DFBA0083CAA8 /* AudioToggle */, 4B4A762D1DB1A35C007AAE2E /* AY38910 */, 4B302181208A550100773308 /* DiskII */, @@ -3770,7 +3778,6 @@ 4BB4BFAB22A33D710069048D /* DriveSpeedAccumulator.hpp */, 4BDB3D8522833321002D3CEE /* Keyboard.hpp */, 4BCE0059227CFFCA000CA200 /* Macintosh.hpp */, - 4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */, 4BCE005F227D39AB000CA200 /* Video.hpp */, ); path = Macintosh;