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