openMSX
TurboRFDC.cc
Go to the documentation of this file.
1/*
2 * Based on code from NLMSX written by Frits Hilderink
3 *
4 * Despite the name, the class implements MSX connection to TC8566AF FDC for
5 * any MSX that uses it. To make reuse possible, it implements two sets of
6 * I/O configurations, one is used on turboR, the other on other machines with
7 * this FDC.
8 * There's also a mapper mechanism implemented. It's only used on the turboR,
9 * but all other machines only have a 16kB diskROM, so it's practically
10 * ineffective there. The mapper has 0x7FF0 as switch address. (Thanks to
11 * NYYRIKKI for this info.)
12 */
13
14#include "TurboRFDC.hh"
15#include "MSXCPU.hh"
16#include "CacheLine.hh"
17#include "Rom.hh"
18#include "MSXException.hh"
19#include "narrow.hh"
20#include "serialize.hh"
21
22namespace openmsx {
23
24[[nodiscard]] static TurboRFDC::Type parseType(const DeviceConfig& config)
25{
26 auto ioRegs = config.getChildData("io_regs", {});
27 if (ioRegs == "7FF2") {
29 } else if (ioRegs == "7FF8") {
31 } else if (ioRegs.empty()) {
32 // for backwards compatibility
34 } else {
35 throw MSXException(
36 "Invalid 'io_regs' specification: expected one of "
37 "'7FF2' or '7FF8', but got: ", ioRegs);
38 }
39}
40
42 : MSXFDC(config)
43 , controller(getScheduler(), drives, getCliComm(), getCurrentTime())
44 , romBlockDebug(*this, std::span{&bank, 1}, 0x4000, 0x4000, 14)
45 , memory(subspan<0x4000>(*rom))
46 , blockMask(narrow_cast<byte>((rom->size() / 0x4000) - 1))
47 , type(parseType(config))
48{
49 auto size = rom->size();
50 if ((size % 0x4000) != 0) {
51 throw MSXException("TurboRFDC rom size must be a multiple of 16kB");
52 }
53 if (size == 0) {
54 throw MSXException("TurboRFDC rom size too small");
55 }
56 if ((size / 0x4000) > 256) {
57 throw MSXException("TurboRFDC rom size too large");
58 }
60}
61
62void TurboRFDC::reset(EmuTime::param time)
63{
64 setBank(0);
65 controller.reset(time);
66}
67
68byte TurboRFDC::readMem(word address, EmuTime::param time_)
69{
70 EmuTime time = time_;
71 if (0x3FF0 <= (address & 0x3FFF)) {
72 // Reading or writing to this region takes 1 extra clock
73 // cycle. But only in R800 mode. Verified on a real turboR
74 // machine, it happens for all 16 positions in this region
75 // and both for reading and writing.
76 time = getCPU().waitCyclesR800(time, 1);
77 if (type != Type::R7FF8) { // turboR or BOTH
78 switch (address & 0xF) {
79 case 0x1: {
80 byte result = 0x33;
81 if (controller.diskChanged(0)) result &= ~0x10;
82 if (controller.diskChanged(1)) result &= ~0x20;
83 return result;
84 }
85 case 0x4: return controller.readStatus(time);
86 case 0x5: return controller.readDataPort(time);
87 }
88 }
89 if (type != Type::R7FF2) { // non-turboR or BOTH
90 switch (address & 0xF) {
91 case 0xA: return controller.readStatus(time);
92 case 0xB: return controller.readDataPort(time);
93 }
94
95 }
96 }
97 // all other stuff is handled by peekMem()
98 return TurboRFDC::peekMem(address, time);
99}
100
101
102byte TurboRFDC::peekMem(word address, EmuTime::param time) const
103{
104 if (0x3FF0 <= (address & 0x3FFF)) {
105 // note: this implementation requires that the handled
106 // addresses for the MSX2 and TURBOR variants don't overlap
107 if (type != Type::R7FF8) { // turboR or BOTH
108 switch (address & 0xF) {
109 case 0x0: return bank;
110 case 0x1: {
111 // bit 0 FD2HD1 High Density detect drive 1
112 // bit 1 FD2HD2 High Density detect drive 2
113 // bit 4 FDCHG1 Disk Change detect on drive 1
114 // bit 5 FDCHG2 Disk Change detect on drive 2
115 // active low
116 byte result = 0x33;
117 if (controller.peekDiskChanged(0)) result &= ~0x10;
118 if (controller.peekDiskChanged(1)) result &= ~0x20;
119 return result;
120 }
121 case 0x4: return controller.peekStatus();
122 case 0x5: return controller.peekDataPort(time);
123 }
124 }
125 if (type != Type::R7FF2) { // non-turboR or BOTH
126 switch (address & 0xF) {
127 case 0xA: return controller.peekStatus();
128 case 0xB: return controller.peekDataPort(time);
129 }
130 }
131 switch (address & 0xF) {
132 // TODO Any idea what these 4 are? I've confirmed that on a
133 // real FS-A1GT I get these values, though the ROM dumps
134 // contain 0xFF in these locations. When looking at the ROM
135 // content via the 'RomPanasonic' mapper in slot 3-3, you can
136 // see that the ROM dumps are correct (these 4 values are not
137 // part of the ROM).
138 // This MRC post indicates that also on non-turbor machines
139 // you see these 4 values (bluemsx' post of 10-03-2013, 10:15):
140 // http://www.msx.org/forum/msx-talk/development/finally-have-feature-request-openmsx?page=3
141 case 0xC: return 0xFC;
142 case 0xD: return 0xFC;
143 case 0xE: return 0xFF;
144 case 0xF: return 0x3F;
145 default: return 0xFF; // other regs in this region
146 }
147 } else if ((0x4000 <= address) && (address < 0x8000)) {
148 return memory[address & 0x3FFF];
149 } else {
150 return 0xFF;
151 }
152}
153
154const byte* TurboRFDC::getReadCacheLine(word start) const
155{
156 if ((start & 0x3FF0) == (0x3FF0 & CacheLine::HIGH)) {
157 return nullptr;
158 } else if ((0x4000 <= start) && (start < 0x8000)) {
159 return &memory[start & 0x3FFF];
160 } else {
161 return unmappedRead.data();
162 }
163}
164
165void TurboRFDC::writeMem(word address, byte value, EmuTime::param time_)
166{
167 EmuTime time = time_;
168 if (0x3FF0 <= (address & 0x3FFF)) {
169 // See comment in readMem().
170 time = getCPU().waitCyclesR800(time, 1);
171 }
172 if (address == 0x7FF0) {
173 setBank(value);
174 } else {
175 if (type != Type::R7FF8) { // turboR or BOTH
176 switch (address & 0x3FFF) {
177 case 0x3FF2:
178 controller.writeControlReg0(value, time);
179 break;
180 case 0x3ff3:
181 controller.writeControlReg1(value, time);
182 break;
183 case 0x3FF5:
184 controller.writeDataPort(value, time);
185 break;
186 }
187 }
188 if (type != Type::R7FF2) { // non-turboR or BOTH
189 switch (address & 0x3FFF) {
190 case 0x3FF8:
191 controller.writeControlReg0(value, time);
192 break;
193 case 0x3FF9:
194 controller.writeControlReg1(value, time);
195 break;
196 case 0x3FFB:
197 controller.writeDataPort(value, time);
198 break;
199 }
200 }
201 }
202}
203
204void TurboRFDC::setBank(byte value)
205{
206 invalidateDeviceRCache(0x4000, 0x4000);
207 bank = value & blockMask;
208 memory = subspan<0x4000>(*rom, 0x4000 * bank);
209}
210
212{
213 if ((address == (0x7FF0 & CacheLine::HIGH)) ||
214 ((address & 0x3FF0) == (0x3FF0 & CacheLine::HIGH))) {
215 return nullptr;
216 } else {
217 return unmappedWrite.data();
218 }
219}
220
221
222template<typename Archive>
223void TurboRFDC::serialize(Archive& ar, unsigned /*version*/)
224{
225 ar.template serializeBase<MSXFDC>(*this);
226 ar.serialize("TC8566AF", controller,
227 "bank", bank);
228 if constexpr (Archive::IS_LOADER) {
229 setBank(bank);
230 }
231}
234
235} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
EmuTime waitCyclesR800(EmuTime::param time, unsigned cycles)
Definition MSXCPU.cc:337
void invalidateDeviceRCache()
Definition MSXDevice.hh:215
static std::array< byte, 0x10000 > unmappedRead
Definition MSXDevice.hh:306
MSXCPU & getCPU() const
Definition MSXDevice.cc:129
static std::array< byte, 0x10000 > unmappedWrite
Definition MSXDevice.hh:307
EmuTime::param getCurrentTime() const
Definition MSXDevice.cc:125
std::optional< Rom > rom
Definition MSXFDC.hh:33
uint8_t peekStatus() const
Definition TC8566AF.cc:116
void writeControlReg0(uint8_t value, EmuTime::param time)
Definition TC8566AF.cc:318
void writeDataPort(uint8_t value, EmuTime::param time)
Definition TC8566AF.cc:338
void writeControlReg1(uint8_t value, EmuTime::param time)
Definition TC8566AF.cc:329
uint8_t peekDataPort(EmuTime::param time) const
Definition TC8566AF.cc:139
void reset(EmuTime::param time)
Definition TC8566AF.cc:77
uint8_t readDataPort(EmuTime::param time)
Definition TC8566AF.cc:151
bool peekDiskChanged(unsigned driveNum) const
Definition TC8566AF.cc:889
uint8_t readStatus(EmuTime::param time)
Definition TC8566AF.cc:123
bool diskChanged(unsigned driveNum)
Definition TC8566AF.cc:883
void reset(EmuTime::param time) override
This method is called on reset.
Definition TurboRFDC.cc:62
byte * getWriteCacheLine(word address) override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
Definition TurboRFDC.cc:211
void serialize(Archive &ar, unsigned version)
Definition TurboRFDC.cc:223
byte peekMem(word address, EmuTime::param time) const override
Read a byte from a given memory location.
Definition TurboRFDC.cc:102
TurboRFDC(const DeviceConfig &config)
Definition TurboRFDC.cc:41
void writeMem(word address, byte value, EmuTime::param time) override
Write a given byte to a given location at a certain time to this device.
Definition TurboRFDC.cc:165
const byte * getReadCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
Definition TurboRFDC.cc:154
byte readMem(word address, EmuTime::param time) override
Read a byte from a location at a certain time from this device.
Definition TurboRFDC.cc:68
constexpr unsigned HIGH
Definition CacheLine.hh:10
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint8_t byte
8 bit unsigned integer
Definition openmsx.hh:26
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
STL namespace.
constexpr To narrow_cast(From &&from) noexcept
Definition narrow.hh:21
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:481
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)