openMSX
SCSIHD.cc
Go to the documentation of this file.
1 /* Ported from:
2 ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/ScsiDevice.c,v
3 ** Revision: 1.10
4 ** Date: 2007-05-21 21:38:29 +0200 (Mon, 21 May 2007)
5 **
6 ** More info: http://www.bluemsx.com
7 **
8 ** Copyright (C) 2003-2007 Daniel Vik, white cat
9 */
10 
11 /*
12  * Notes:
13  * It follows the SCSI1(CCS) standard or the SCSI2 standard.
14  * Only the direct access device is supported now.
15  * Message system might be imperfect.
16  *
17  * NOTE: this version only supports a non-removable harddisk, as the class
18  * name suggests. Refer to revision 6526 of this file to see what was removed
19  * from the generic/parameterised code.
20  */
21 
22 #include "SCSIHD.hh"
23 #include "FileOperations.hh"
24 #include "MSXException.hh"
25 #include "LedStatus.hh"
26 #include "MSXMotherBoard.hh"
27 #include "DeviceConfig.hh"
28 #include "endian.hh"
29 #include "serialize.hh"
30 #include <algorithm>
31 #include <cstring>
32 
33 using std::string;
34 
35 namespace openmsx {
36 
37 // Medium type (value like LS-120)
38 static const byte MT_UNKNOWN = 0x00;
39 static const byte MT_2DD_UN = 0x10;
40 static const byte MT_2DD = 0x11;
41 static const byte MT_2HD_UN = 0x20;
42 static const byte MT_2HD_12_98 = 0x22;
43 static const byte MT_2HD_12 = 0x23;
44 static const byte MT_2HD_144 = 0x24;
45 static const byte MT_LS120 = 0x31;
46 static const byte MT_NO_DISK = 0x70;
47 static const byte MT_DOOR_OPEN = 0x71;
48 static const byte MT_FMT_ERROR = 0x72;
49 
50 static const byte inqdata[36] = {
51  0, // bit5-0 device type code.
52  0, // bit7 = 1 removable device
53  2, // bit7,6 ISO version. bit5,4,3 ECMA version.
54  // bit2,1,0 ANSI Version (001=SCSI1, 010=SCSI2)
55  2, // bit7 AENC. bit6 TrmIOP.
56  // bit3-0 Response Data Format. (0000=SCSI1, 0001=CCS, 0010=SCSI2)
57  51, // addtional length
58  0, 0,// reserved
59  0, // bit7 RelAdr, bit6 WBus32, bit5 Wbus16, bit4 Sync, bit3 Linked,
60  // bit2 reseved bit1 CmdQue, bit0 SftRe
61  'o', 'p', 'e', 'n', 'M', 'S', 'X', ' ', // vendor ID (8bytes)
62  'S', 'C', 'S', 'I', '2', ' ', 'H', 'a', // product ID (16bytes)
63  'r', 'd', 'd', 'i', 's', 'k', ' ', ' ',
64  '0', '1', '0', 'a' // product version (ASCII 4bytes)
65 };
66 
67 static const unsigned BUFFER_BLOCK_SIZE = SCSIHD::BUFFER_SIZE /
69 
70 SCSIHD::SCSIHD(const DeviceConfig& targetconfig,
71  byte* const buf, unsigned mode_)
72  : HD(targetconfig)
73  , motherBoard(targetconfig.getMotherBoard())
74  , buffer(buf)
75  , mode(mode_)
76  , scsiId(targetconfig.getAttributeAsInt("id"))
77 {
78  reset();
79 }
80 
82 {
83  PRT_DEBUG("hdd close for hdd " << int(scsiId));
84 }
85 
86 void SCSIHD::reset()
87 {
88  currentSector = 0;
89  currentLength = 0;
90  busReset();
91 }
92 
93 void SCSIHD::busReset()
94 {
95  PRT_DEBUG("SCSI: bus reset on " << int(scsiId));
96  keycode = 0;
97  unitAttention = (mode & MODE_UNITATTENTION) != 0;
98 }
99 
100 void SCSIHD::disconnect()
101 {
102  motherBoard.getLedStatus().setLed(LedStatus::FDD, false);
103 }
104 
105 // Check the initiator in the call origin.
106 bool SCSIHD::isSelected()
107 {
108  lun = 0;
109  return true;
110 }
111 
112 unsigned SCSIHD::inquiry()
113 {
114  unsigned length = currentLength;
115 
116  if (length == 0) return 0;
117 
118  memcpy(buffer + 2, inqdata + 2, 34);
119 
120  buffer[0] = SCSI::DT_DirectAccess;
121  buffer[1] = 0; // removable
122 
123  if (!(mode & BIT_SCSI2)) {
124  buffer[2] = 1;
125  buffer[3] = 1;
126  buffer[20] = '1';
127  } else {
128  if (mode & BIT_SCSI3) {
129  buffer[2] = 5;
130  buffer[20] = '3';
131  }
132  }
133 
134  if (mode & BIT_SCSI3) {
135  length = std::min(length, 96u);
136  buffer[4] = 91;
137  if (length > 56) {
138  memset(buffer + 56, 0, 40);
139  buffer[58] = 0x03;
140  buffer[60] = 0x01;
141  buffer[61] = 0x80;
142  }
143  } else {
144  length = std::min(length, 56u);
145  }
146 
147  if (length > 36) {
148  string filename = FileOperations::getFilename(
149  getImageName().getOriginal()).str();
150  filename.resize(20, ' ');
151  memcpy(buffer + 36, filename.data(), 20);
152  }
153  return length;
154 }
155 
156 unsigned SCSIHD::modeSense()
157 {
158  byte* pBuffer = buffer;
159  if ((currentLength > 0) && (cdb[2] == 3)) {
160  // TODO check for too many sectors
161  unsigned total = unsigned(getNbSectors());
162  byte media = MT_UNKNOWN;
163  byte sectors = 64;
164  byte blockLength = SECTOR_SIZE >> 8;
165  byte tracks = 8;
166  byte size = 4 + 24;
167  byte removable = 0x80; // == not removable
168 
169  memset(pBuffer + 2, 0, 34);
170 
171  if (total == 0) {
172  media = MT_NO_DISK;
173  }
174 
175  // Mode Parameter Header 4bytes
176  pBuffer[1] = media; // Medium Type
177  pBuffer[3] = 8; // block descripter length
178  pBuffer += 4;
179 
180  // Disable Block Descriptor check
181  if (!(cdb[1] & 0x08)) {
182  // Block Descriptor 8bytes
183  pBuffer[1] = (total >> 16) & 0xff; // 1..3 Number of Blocks
184  pBuffer[2] = (total >> 8) & 0xff;
185  pBuffer[3] = (total >> 0) & 0xff;
186  pBuffer[6] = blockLength & 0xff; // 5..7 Block Length in Bytes
187  pBuffer += 8;
188  size += 8;
189  }
190 
191  // Format Device Page 24bytes
192  pBuffer[ 0] = 3; // 0 Page
193  pBuffer[ 1] = 0x16; // 1 Page Length
194  pBuffer[ 3] = tracks; // 2, 3 Tracks per Zone
195  pBuffer[11] = sectors; // 10,11 Sectors per Track
196  pBuffer[12] = blockLength; // 12,13 Data Bytes per Physical Sector
197  pBuffer[20] = removable; // 20 bit7 Soft Sector bit5 Removable
198 
199  buffer[0] = size - 1; // sense data length
200 
201  return std::min<unsigned>(currentLength, size);
202  }
203  keycode = SCSI::SENSE_INVALID_COMMAND_CODE;
204  return 0;
205 }
206 
207 unsigned SCSIHD::requestSense()
208 {
209  unsigned length = currentLength;
210  unsigned tmpKeycode = unitAttention ? SCSI::SENSE_POWER_ON : keycode;
211  unitAttention = false;
212 
213  PRT_DEBUG("Request Sense: keycode = " << tmpKeycode);
214  keycode = SCSI::SENSE_NO_SENSE;
215 
216  memset(buffer + 1, 0, 17);
217  if (length == 0) {
218  if (mode & BIT_SCSI2) {
219  return 0;
220  }
221  buffer[ 0] = (tmpKeycode >> 8) & 0xff; // Sense code
222  length = 4;
223  } else {
224  buffer[ 0] = 0x70;
225  buffer[ 2] = (tmpKeycode >> 16) & 0xff; // Sense key
226  buffer[ 7] = 10; // Additional sense length
227  buffer[12] = (tmpKeycode >> 8) & 0xff; // Additional sense code
228  buffer[13] = (tmpKeycode >> 0) & 0xff; // Additional sense code qualifier
229  length = std::min(length, 18u);
230  }
231  return length;
232 }
233 
234 bool SCSIHD::checkReadOnly()
235 {
236  if (isWriteProtected()) {
237  keycode = SCSI::SENSE_WRITE_PROTECT;
238  return true;
239  }
240  return false;
241 }
242 
243 unsigned SCSIHD::readCapacity()
244 {
245  // TODO check for overflow
246  unsigned block = unsigned(getNbSectors());
247 
248  if (block == 0) {
249  keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT;
250  PRT_DEBUG("hdd " << int(scsiId) << ": drive not ready");
251  return 0;
252  }
253 
254  PRT_DEBUG("total block: " << block);
255 
256  --block;
257  Endian::write_UA_B32(&buffer[0], block); // TODO use aligned version later
258  Endian::write_UA_B32(&buffer[4], SECTOR_SIZE); // TODO is this a 32 bit field or 2x16-bit fields where the first field happens to have the value 0?
259  return 8;
260 }
261 
262 bool SCSIHD::checkAddress()
263 {
264  unsigned total = unsigned(getNbSectors());
265  if (total == 0) {
266  keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT;
267  PRT_DEBUG("hdd " << int(scsiId) << ": drive not ready");
268  return false;
269  }
270 
271  if ((currentLength > 0) && (currentSector + currentLength <= total)) {
272  return true;
273  }
274  PRT_DEBUG("hdd " << int(scsiId) << ": IllegalBlockAddress");
275  keycode = SCSI::SENSE_ILLEGAL_BLOCK_ADDRESS;
276  return false;
277 }
278 
279 // Execute scsiDeviceCheckAddress previously.
280 unsigned SCSIHD::readSectors(unsigned& blocks)
281 {
282  motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
283 
284  unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE);
285  unsigned counter = currentLength * SECTOR_SIZE;
286 
287  PRT_DEBUG("hdd#" << int(scsiId) << " read sector: " << currentSector << ' ' << numSectors);
288  try {
289  for (unsigned i = 0; i < numSectors; ++i) {
290  readSector(currentSector, &buffer[i * SECTOR_SIZE]);
291  ++currentSector;
292  --currentLength;
293  }
294  blocks = currentLength;
295  return counter;
296  } catch (MSXException&) {
297  blocks = 0;
298  keycode = SCSI::SENSE_UNRECOVERED_READ_ERROR;
299  return 0;
300  }
301 }
302 
303 unsigned SCSIHD::dataIn(unsigned& blocks)
304 {
305  if (cdb[0] == SCSI::OP_READ10) {
306  unsigned counter = readSectors(blocks);
307  if (counter) {
308  return counter;
309  }
310  }
311  PRT_DEBUG("dataIn error " << cdb[0]);
312  blocks = 0;
313  return 0;
314 }
315 
316 // Execute scsiDeviceCheckAddress and scsiDeviceCheckReadOnly previously.
317 unsigned SCSIHD::writeSectors(unsigned& blocks)
318 {
319  motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
320 
321  unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE);
322 
323  PRT_DEBUG("hdd#" << int(scsiId) << " write sector: " << currentSector << ' ' << numSectors);
324  try {
325  for (unsigned i = 0; i < numSectors; ++i) {
326  writeSector(currentSector, &buffer[i * SECTOR_SIZE]);
327  ++currentSector;
328  --currentLength;
329  }
330 
331  unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
332  blocks = currentLength - tmp;
333  unsigned counter = tmp * SECTOR_SIZE;
334  return counter;
335  } catch (MSXException&) {
336  keycode = SCSI::SENSE_WRITE_FAULT;
337  blocks = 0;
338  return 0;
339  }
340 }
341 
342 unsigned SCSIHD::dataOut(unsigned& blocks)
343 {
344  if (cdb[0] == SCSI::OP_WRITE10) {
345  return writeSectors(blocks);
346  }
347  PRT_DEBUG("dataOut error " << int(cdb[0]));
348  blocks = 0;
349  return 0;
350 }
351 
352 // MBR erase only
353 void SCSIHD::formatUnit()
354 {
355  if (!checkReadOnly()) {
356  memset(buffer, 0, SECTOR_SIZE);
357  try {
358  writeSector(0, buffer);
359  unitAttention = true;
360  } catch (MSXException&) {
361  keycode = SCSI::SENSE_WRITE_FAULT;
362  }
363  }
364 }
365 
366 byte SCSIHD::getStatusCode()
367 {
368  byte result = keycode ? SCSI::ST_CHECK_CONDITION : SCSI::ST_GOOD;
369  PRT_DEBUG("SCSI status code: \n" << int(result));
370  return result;
371 }
372 
373 unsigned SCSIHD::executeCmd(const byte* cdb_, SCSI::Phase& phase, unsigned& blocks)
374 {
375  PRT_DEBUG("SCSI Command: " << int(cdb[0]));
376  memcpy(cdb, cdb_, sizeof(cdb));
377  message = 0;
378  phase = SCSI::STATUS;
379  blocks = 0;
380 
381  // check unit attention
382  if (unitAttention && (mode & MODE_UNITATTENTION) &&
383  (cdb[0] != SCSI::OP_INQUIRY) && (cdb[0] != SCSI::OP_REQUEST_SENSE)) {
384  unitAttention = false;
385  keycode = SCSI::SENSE_POWER_ON;
386  if (cdb[0] == SCSI::OP_TEST_UNIT_READY) {
387  // changed = false;
388  }
389  PRT_DEBUG("Unit Attention. This command is not executed.");
390  return 0;
391  }
392 
393  // check LUN
394  if (((cdb[1] & 0xe0) || lun) && (cdb[0] != SCSI::OP_REQUEST_SENSE) &&
395  !(cdb[0] == SCSI::OP_INQUIRY && !(mode & MODE_NOVAXIS))) {
396  keycode = SCSI::SENSE_INVALID_LUN;
397  PRT_DEBUG("check LUN error");
398  return 0;
399  }
400 
401  if (cdb[0] != SCSI::OP_REQUEST_SENSE) {
402  keycode = SCSI::SENSE_NO_SENSE;
403  }
404 
405  if (cdb[0] < SCSI::OP_GROUP1) {
406  currentSector = ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3];
407  currentLength = cdb[4];
408 
409  switch (cdb[0]) {
410  case SCSI::OP_TEST_UNIT_READY:
411  PRT_DEBUG("TestUnitReady");
412  return 0;
413 
414  case SCSI::OP_INQUIRY: {
415  PRT_DEBUG("Inquiry " << currentLength);
416  unsigned counter = inquiry();
417  if (counter) {
418  phase = SCSI::DATA_IN;
419  }
420  return counter;
421  }
422  case SCSI::OP_REQUEST_SENSE: {
423  PRT_DEBUG("RequestSense");
424  unsigned counter = requestSense();
425  if (counter) {
426  phase = SCSI::DATA_IN;
427  }
428  return counter;
429  }
430  case SCSI::OP_READ6:
431  PRT_DEBUG("Read6: " << currentSector << ' ' << currentLength);
432  if (currentLength == 0) {
433  currentLength = SECTOR_SIZE / 2;
434  }
435  if (checkAddress()) {
436  unsigned counter = readSectors(blocks);
437  if (counter) {
438  cdb[0] = SCSI::OP_READ10;
439  phase = SCSI::DATA_IN;
440  return counter;
441  }
442  }
443  return 0;
444 
445  case SCSI::OP_WRITE6:
446  PRT_DEBUG("Write6: " << currentSector << ' ' << currentLength);
447  if (currentLength == 0) {
448  currentLength = SECTOR_SIZE / 2;
449  }
450  if (checkAddress() && !checkReadOnly()) {
451  motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
452  unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
453  blocks = currentLength - tmp;
454  unsigned counter = tmp * SECTOR_SIZE;
455  cdb[0] = SCSI::OP_WRITE10;
456  phase = SCSI::DATA_OUT;
457  return counter;
458  }
459  return 0;
460 
461  case SCSI::OP_SEEK6:
462  PRT_DEBUG("Seek6: " << currentSector);
463  motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
464  currentLength = 1;
465  checkAddress();
466  return 0;
467 
468  case SCSI::OP_MODE_SENSE: {
469  PRT_DEBUG("ModeSense: " << currentLength);
470  unsigned counter = modeSense();
471  if (counter) {
472  phase = SCSI::DATA_IN;
473  }
474  return counter;
475  }
476  case SCSI::OP_FORMAT_UNIT:
477  PRT_DEBUG("FormatUnit");
478  formatUnit();
479  return 0;
480 
481  case SCSI::OP_START_STOP_UNIT:
482  PRT_DEBUG("StartStopUnit (Not supported for this device.)");
483  return 0;
484 
485  case SCSI::OP_REZERO_UNIT:
486  case SCSI::OP_REASSIGN_BLOCKS:
487  case SCSI::OP_RESERVE_UNIT:
488  case SCSI::OP_RELEASE_UNIT:
489  case SCSI::OP_SEND_DIAGNOSTIC:
490  PRT_DEBUG("SCSI_Group0 dummy");
491  return 0;
492  }
493  } else {
494  currentSector = Endian::read_UA_B32(&cdb[2]);
495  currentLength = Endian::read_UA_B16(&cdb[7]);
496 
497  switch (cdb[0]) {
498  case SCSI::OP_READ10:
499  PRT_DEBUG("Read10: " << currentSector << ' ' << currentLength);
500 
501  if (checkAddress()) {
502  unsigned counter = readSectors(blocks);
503  if (counter) {
504  phase = SCSI::DATA_IN;
505  return counter;
506  }
507  }
508  return 0;
509 
510  case SCSI::OP_WRITE10:
511  PRT_DEBUG("Write10: " << currentSector << ' ' << currentLength);
512 
513  if (checkAddress() && !checkReadOnly()) {
514  unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
515  blocks = currentLength - tmp;
516  unsigned counter = tmp * SECTOR_SIZE;
517  phase = SCSI::DATA_OUT;
518  return counter;
519  }
520  return 0;
521 
522  case SCSI::OP_READ_CAPACITY: {
523  PRT_DEBUG("ReadCapacity");
524  unsigned counter = readCapacity();
525  if (counter) {
526  phase = SCSI::DATA_IN;
527  }
528  return counter;
529  }
530  case SCSI::OP_SEEK10:
531  PRT_DEBUG("Seek10: " << currentSector);
532  motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
533  currentLength = 1;
534  checkAddress();
535  return 0;
536  }
537  }
538 
539  PRT_DEBUG("unsupported command " << cdb[0]);
540  keycode = SCSI::SENSE_INVALID_COMMAND_CODE;
541  return 0;
542 }
543 
544 unsigned SCSIHD::executingCmd(SCSI::Phase& phase, unsigned& blocks)
545 {
546  phase = SCSI::EXECUTE;
547  blocks = 0;
548  return 0; // Always for non-CD-ROM it seems
549 }
550 
551 byte SCSIHD::msgIn()
552 {
553  byte result = message;
554  message = 0;
555  //PRT_DEBUG("SCSIDevice " << int(scsiId) << " msgIn returning " << result);
556  return result;
557 }
558 
559 /*
560 scsiDeviceMsgOut()
561 Notes:
562  [out]
563  -1: Busfree demand. (Please process it in the call origin.)
564  bit2: Status phase demand. Error happend.
565  bit1: Make it to a busfree if ATN has not been released.
566  bit0: There is a message(MsgIn).
567 */
568 int SCSIHD::msgOut(byte value)
569 {
570  PRT_DEBUG("SCSI #" << int(scsiId) << " message out: " << int(value));
571  if (value & 0x80) {
572  lun = value & 7;
573  return 0;
574  }
575 
576  switch (value) {
577  case SCSI::MSG_INITIATOR_DETECT_ERROR:
578  keycode = SCSI::SENSE_INITIATOR_DETECTED_ERR;
579  return 6;
580 
581  case SCSI::MSG_BUS_DEVICE_RESET:
582  busReset();
583  // fall-through
584  case SCSI::MSG_ABORT:
585  return -1;
586 
587  case SCSI::MSG_REJECT:
588  case SCSI::MSG_PARITY_ERROR:
589  case SCSI::MSG_NO_OPERATION:
590  return 2;
591  }
592  message = SCSI::MSG_REJECT;
593  return ((value >= 0x04) && (value <= 0x11)) ? 3 : 1;
594 }
595 
596 
597 template<typename Archive>
598 void SCSIHD::serialize(Archive& ar, unsigned /*version*/)
599 {
600  // don't serialize SCSIDevice, SectorAccessibleDisk, DiskContainer
601  // base classes
602  ar.template serializeBase<HD>(*this);
603  ar.serialize("keycode", keycode);
604  ar.serialize("currentSector", currentSector);
605  ar.serialize("currentLength", currentLength);
606  ar.serialize("unitAttention", unitAttention);
607  ar.serialize("message", message);
608  ar.serialize("lun", lun);
609  ar.serialize_blob("cdb", cdb, sizeof(cdb));
610 }
613 
614 } // namespace openmsx