openMSX
AbstractIDEDevice.cc
Go to the documentation of this file.
2#include "MSXMotherBoard.hh"
3#include "LedStatus.hh"
4#include "Version.hh"
5#include "narrow.hh"
6#include "serialize.hh"
7#include "unreachable.hh"
8#include "xrange.hh"
9#include <cassert>
10#include <cstdio>
11
12namespace openmsx {
13
15 : motherBoard(motherBoard_)
16{
17 ranges::fill(buffer, 0);
18}
19
20byte AbstractIDEDevice::diagnostic() const
21{
22 // The Execute Device Diagnostic command is executed by both devices in
23 // parallel. Fortunately, returning 0x01 is valid in all cases:
24 // - for device 0 it means: device 0 passed, device 1 passed or not present
25 // - for device 1 it means: device 1 passed
26 return 0x01;
27}
28
29void AbstractIDEDevice::createSignature(bool preserveDevice)
30{
31 sectorCountReg = 0x01;
32 sectorNumReg = 0x01;
33 if (isPacketDevice()) {
34 cylinderLowReg = 0x14;
35 cylinderHighReg = 0xEB;
36 if (preserveDevice) {
37 devHeadReg &= 0x10;
38 } else {
39 // The current implementation of SunriseIDE will substitute the
40 // current device for DEV, so it will always act like DEV is
41 // preserved.
42 devHeadReg = 0x00;
43 }
44 } else {
45 cylinderLowReg = 0x00;
46 cylinderHighReg = 0x00;
47 devHeadReg = 0x00;
48 }
49}
50
51void AbstractIDEDevice::reset(EmuTime::param /*time*/)
52{
53 errorReg = diagnostic();
54 statusReg = DRDY | DSC;
55 featureReg = 0x00;
56 createSignature();
57 setTransferRead(false);
58 setTransferWrite(false);
59}
60
61byte AbstractIDEDevice::readReg(nibble reg, EmuTime::param /*time*/)
62{
63 switch (reg) {
64 case 1: // error register
65 return errorReg;
66
67 case 2: // sector count register
68 return sectorCountReg;
69
70 case 3: // sector number register / LBA low
71 return sectorNumReg;
72
73 case 4: // cylinder low register / LBA mid
74 return cylinderLowReg;
75
76 case 5: // cylinder high register / LBA high
77 return cylinderHighReg;
78
79 case 6: // device/head register
80 // DEV bit is handled by IDE interface
81 return devHeadReg;
82
83 case 7: // status register
84 return statusReg;
85
86 case 8:
87 case 9:
88 case 10:
89 case 11:
90 case 12:
91 case 13:
92 case 15:// not used
93 return 0x7F;
94
95 case 0: // data register, converted to readData by IDE interface
96 case 14:// alternate status reg, converted to read from normal
97 // status register by IDE interface
98 default:
100 }
101}
102
104 nibble reg, byte value, EmuTime::param /*time*/
105 )
106{
107 switch (reg) {
108 case 1: // feature register
109 featureReg = value;
110 break;
111
112 case 2: // sector count register
113 sectorCountReg = value;
114 break;
115
116 case 3: // sector number register / LBA low
117 sectorNumReg = value;
118 break;
119
120 case 4: // cylinder low register / LBA mid
121 cylinderLowReg = value;
122 break;
123
124 case 5: // cylinder high register / LBA high
125 cylinderHighReg = value;
126 break;
127
128 case 6: // device/head register
129 // DEV bit is handled by IDE interface
130 devHeadReg = value;
131 break;
132
133 case 7: // command register
134 statusReg &= ~(DRQ | ERR);
135 setTransferRead(false);
136 setTransferWrite(false);
137 executeCommand(value);
138 break;
139
140 case 8:
141 case 9:
142 case 10:
143 case 11:
144 case 12:
145 case 13:
146 case 15: // not used
147 case 14: // device control register, handled by IDE interface
148 // do nothing
149 break;
150
151 case 0: // data register, converted to readData by IDE interface
152 default:
154 }
155}
156
157word AbstractIDEDevice::readData(EmuTime::param /*time*/)
158{
159 if (!transferRead) {
160 // no read in progress
161 return 0x7F7F;
162 }
163 assert((transferIdx + 1) < sizeof(buffer));
164 auto result = word((buffer[transferIdx + 0] << 0) +
165 (buffer[transferIdx + 1] << 8));
166 transferIdx += 2;
167 bufferLeft -= 2;
168 if (bufferLeft == 0) {
169 if (transferCount == 0) {
170 // End of transfer.
171 setTransferRead(false);
172 statusReg &= ~DRQ;
173 readEnd();
174 } else {
175 // Buffer empty, but transfer not done yet.
176 readNextBlock();
177 }
178 }
179 return result;
180}
181
182void AbstractIDEDevice::readNextBlock()
183{
184 bufferLeft = readBlockStart(
185 buffer, std::min<unsigned>(sizeof(buffer), transferCount));
186 assert((bufferLeft & 1) == 0);
187 transferIdx = 0;
188 transferCount -= bufferLeft;
189}
190
191void AbstractIDEDevice::writeData(word value, EmuTime::param /*time*/)
192{
193 if (!transferWrite) {
194 // no write in progress
195 return;
196 }
197 assert((transferIdx + 1) < sizeof(buffer));
198 buffer[transferIdx + 0] = narrow_cast<byte>(value & 0xFF);
199 buffer[transferIdx + 1] = narrow_cast<byte>(value >> 8);
200 transferIdx += 2;
201 bufferLeft -= 2;
202 if (bufferLeft == 0) {
203 unsigned bytesInBuffer = transferIdx;
204 if (transferCount == 0) {
205 // End of transfer.
206 setTransferWrite(false);
207 statusReg &= ~DRQ;
208 } else {
209 // Buffer full, but transfer not done yet.
210 writeNextBlock();
211 }
212 // Packet commands can start a second transfer, so the command
213 // execution must happen after we close this transfer.
214 writeBlockComplete(buffer, bytesInBuffer);
215 }
216}
217
218void AbstractIDEDevice::writeNextBlock()
219{
220 transferIdx = 0;
221 bufferLeft = std::min<unsigned>(sizeof(buffer), transferCount);
222 transferCount -= bufferLeft;
223}
224
226{
227 errorReg = error;
228 if (error) {
229 statusReg |= ERR;
230 } else {
231 statusReg &= ~ERR;
232 }
233 statusReg &= ~DRQ;
234 setTransferWrite(false);
235 setTransferRead(false);
236}
237
239{
240 return sectorNumReg | (cylinderLowReg << 8) |
241 (cylinderHighReg << 16) | ((devHeadReg & 0x0F) << 24);
242}
243
245{
246 return (sectorCountReg == 0) ? 256 : sectorCountReg;
247}
248
250{
251 sectorCountReg = value;
252}
253
255{
256 return cylinderLowReg | (cylinderHighReg << 8);
257}
258
260{
261 cylinderLowReg = narrow_cast<byte>(count & 0xFF);
262 cylinderHighReg = narrow_cast<byte>(count >> 8);
263}
264
266{
267 sectorNumReg = (lba & 0x000000FF) >> 0;
268 cylinderLowReg = (lba & 0x0000FF00) >> 8;
269 cylinderHighReg = (lba & 0x00FF0000) >> 16;
270 devHeadReg = (lba & 0x0F000000) >> 24; // note: only 4 bits
271}
272
276
278{
279 switch (cmd) {
280 case 0x08: // Device Reset
281 if (isPacketDevice()) {
282 errorReg = diagnostic();
283 createSignature(true);
284 // TODO: Which is correct?
285 //statusReg = 0x00;
286 statusReg = DRDY | DSC;
287 } else {
288 // Command is only implemented for packet devices.
290 }
291 break;
292
293 case 0x90: // Execute Device Diagnostic
294 errorReg = diagnostic();
295 createSignature();
296 break;
297
298 case 0x91: // Initialize Device Parameters
299 // ignore command
300 break;
301
302 case 0xA1: // Identify Packet Device
303 if (isPacketDevice()) {
304 createIdentifyBlock(startShortReadTransfer(512));
305 } else {
307 }
308 break;
309
310 case 0xEC: // Identify Device
311 if (isPacketDevice()) {
313 } else {
314 createIdentifyBlock(startShortReadTransfer(512));
315 }
316 break;
317
318 case 0xEF: // Set Features
319 switch (getFeatureReg()) {
320 case 0x03: // Set Transfer Mode
321 break;
322 default:
323 fprintf(stderr, "Unhandled set feature subcommand: %02X\n",
324 getFeatureReg());
326 }
327 break;
328
329 default: // unsupported command
330 fprintf(stderr, "unsupported IDE command %02X\n", cmd);
332 }
333}
334
336{
337 assert(count <= sizeof(buffer));
338 assert((count & 1) == 0);
339
340 startReadTransfer();
341 transferCount = 0;
342 bufferLeft = count;
343 transferIdx = 0;
344 ranges::fill(std::span{buffer.data(), count}, 0);
345 return buffer;
346}
347
349{
350 assert((count & 1) == 0);
351 startReadTransfer();
352 transferCount = count;
353 readNextBlock();
354}
355
356void AbstractIDEDevice::startReadTransfer()
357{
358 statusReg |= DRQ;
359 setTransferRead(true);
360}
361
363{
364 setError(error | ABORT);
365 setTransferRead(false);
366}
367
369{
370 statusReg |= DRQ;
371 setTransferWrite(true);
372 transferCount = count;
373 writeNextBlock();
374}
375
377{
378 setError(error | ABORT);
379 setTransferWrite(false);
380}
381
382void AbstractIDEDevice::setTransferRead(bool status)
383{
384 if (status != transferRead) {
385 transferRead = status;
386 if (!transferWrite) {
387 // (this is a bit of a hack!)
388 motherBoard.getLedStatus().setLed(LedStatus::FDD, transferRead);
389 }
390 }
391}
392
393void AbstractIDEDevice::setTransferWrite(bool status)
394{
395 if (status != transferWrite) {
396 transferWrite = status;
397 if (!transferRead) {
398 // (this is a bit of a hack!)
399 motherBoard.getLedStatus().setLed(LedStatus::FDD, transferWrite);
400 }
401 }
402}
403
411static void writeIdentifyString(std::span<byte> dst, std::string s)
412{
413 assert((dst.size() % 2) == 0);
414 s.resize(dst.size(), ' ');
415 for (size_t i = 0; i < dst.size(); i += 2) {
416 // copy and swap
417 dst[i + 0] = s[i + 1];
418 dst[i + 1] = s[i + 0];
419 }
420}
421
422void AbstractIDEDevice::createIdentifyBlock(AlignedBuffer& buf)
423{
424 // According to the spec, the combination of model and serial should be
425 // unique. But I don't know any MSX software that cares about this.
426 writeIdentifyString(std::span{&buf[10 * 2], 2 * 10}, "s00000001"); // serial
427 writeIdentifyString(std::span{&buf[23 * 2], 2 * 4},
428 // Use openMSX version as firmware revision, because most of our
429 // IDE emulation code is in fact emulating the firmware.
431 : strCat('d', Version::REVISION));
432 writeIdentifyString(std::span{&buf[27 * 2], 2 * 20}, std::string(getDeviceName())); // model
433
435}
436
437
438template<typename Archive>
439void AbstractIDEDevice::serialize(Archive& ar, unsigned /*version*/)
440{
441 // no need to serialize IDEDevice base class
442 ar.serialize_blob("buffer", buffer);
443 ar.serialize("transferIdx", transferIdx,
444 "bufferLeft", bufferLeft,
445 "transferCount", transferCount,
446 "errorReg", errorReg,
447 "sectorCountReg", sectorCountReg,
448 "sectorNumReg", sectorNumReg,
449 "cylinderLowReg", cylinderLowReg,
450 "cylinderHighReg", cylinderHighReg,
451 "devHeadReg", devHeadReg,
452 "statusReg", statusReg,
453 "featureReg", featureReg);
454 bool transferIdentifyBlock = false; // remove on next version increment
455 // no need to break bw-compat now
456 ar.serialize("transferIdentifyBlock", transferIdentifyBlock,
457 "transferRead", transferRead,
458 "transferWrite", transferWrite);
459}
461
462} // namespace openmsx
void writeData(word value, EmuTime::param time) override
void startWriteTransfer(unsigned count)
Indicates the start of a write data transfer.
AlignedBuffer & startShortReadTransfer(unsigned count)
Indicates the start of a read data transfer where all data fits into the buffer at once.
virtual bool isPacketDevice()=0
Is this device a packet (ATAPI) device?
static constexpr byte DRDY
void startLongReadTransfer(unsigned count)
Indicates the start of a read data transfer which uses blocks.
void serialize(Archive &ar, unsigned version)
virtual void executeCommand(byte cmd)
Starts execution of an IDE command.
void writeReg(nibble reg, byte value, EmuTime::param time) override
static constexpr byte DRQ
static constexpr byte ERR
virtual void fillIdentifyBlock(AlignedBuffer &buffer)=0
Tells a subclass to fill the device specific parts of the identify block located in the buffer.
void setSectorNumber(unsigned lba)
Writes a 28-bit LBA sector number in the registers.
void setByteCount(unsigned count)
Writes the byte count of a packet transfer in the registers.
virtual std::string_view getDeviceName()=0
Gets the device name to insert as "model number" into the identify block.
static constexpr byte DSC
virtual unsigned readBlockStart(AlignedBuffer &buffer, unsigned count)=0
Called when a block of read data should be buffered by the controller: when the buffer is empty or at...
unsigned getNumSectors() const
Gets the number of sectors indicated by the sector count register.
void abortReadTransfer(byte error)
Aborts the read transfer in progress.
unsigned getByteCount() const
Reads the byte count limit of a packet transfer in the registers.
void setError(byte error)
Indicates an error: sets error register, error flag, aborts transfers.
byte readReg(nibble reg, EmuTime::param time) override
void abortWriteTransfer(byte error)
Aborts the write transfer in progress.
AbstractIDEDevice(MSXMotherBoard &motherBoard)
void reset(EmuTime::param time) override
unsigned getSectorNumber() const
Creates an LBA sector address from the contents of the sectorNumReg, cylinderLowReg,...
void setInterruptReason(byte value)
Writes the interrupt reason register.
virtual void writeBlockComplete(AlignedBuffer &buffer, unsigned count)=0
Called when a block of written data has been buffered by the controller: when the buffer is full or a...
word readData(EmuTime::param time) override
virtual void readEnd()
Called when a read transfer completes.
static constexpr byte ABORT
void setLed(Led led, bool status)
Definition LedStatus.cc:44
static const char *const VERSION
Definition Version.hh:13
static const bool RELEASE
Definition Version.hh:12
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint8_t nibble
4 bit integer
Definition openmsx.hh:23
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
std::string strCat()
Definition strCat.hh:703
#define UNREACHABLE