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