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