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