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