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 ranges::copy(cdb_, cdb);
366 message = 0;
367 phase = SCSI::STATUS;
368 blocks = 0;
369
370 // check unit attention
371 if (unitAttention && (mode & MODE_UNITATTENTION) &&
373 unitAttention = false;
374 keycode = SCSI::SENSE_POWER_ON;
375 if (cdb[0] == SCSI::OP_TEST_UNIT_READY) {
376 // changed = false;
377 }
378 // Unit Attention. This command is not executed.
379 return 0;
380 }
381
382 // check LUN
383 if (((cdb[1] & 0xe0) || lun) && (cdb[0] != SCSI::OP_REQUEST_SENSE) &&
384 !(cdb[0] == SCSI::OP_INQUIRY && !(mode & MODE_NOVAXIS))) {
385 keycode = SCSI::SENSE_INVALID_LUN;
386 // check LUN error
387 return 0;
388 }
389
390 if (cdb[0] != SCSI::OP_REQUEST_SENSE) {
391 keycode = SCSI::SENSE_NO_SENSE;
392 }
393
394 if (cdb[0] < SCSI::OP_GROUP1) {
395 currentSector = ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3];
396 currentLength = cdb[4];
397
398 switch (cdb[0]) {
400 return 0;
401
402 case SCSI::OP_INQUIRY: {
403 unsigned counter = inquiry();
404 if (counter) {
405 phase = SCSI::DATA_IN;
406 }
407 return counter;
408 }
410 unsigned counter = requestSense();
411 if (counter) {
412 phase = SCSI::DATA_IN;
413 }
414 return counter;
415 }
416 case SCSI::OP_READ6:
417 if (currentLength == 0) {
418 currentLength = SECTOR_SIZE / 2;
419 }
420 if (checkAddress()) {
421 unsigned counter = readSectors(blocks);
422 if (counter) {
423 cdb[0] = SCSI::OP_READ10;
424 phase = SCSI::DATA_IN;
425 return counter;
426 }
427 }
428 return 0;
429
430 case SCSI::OP_WRITE6:
431 if (currentLength == 0) {
432 currentLength = SECTOR_SIZE / 2;
433 }
434 if (checkAddress() && !checkReadOnly()) {
436 unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
437 blocks = currentLength - tmp;
438 unsigned counter = tmp * SECTOR_SIZE;
439 cdb[0] = SCSI::OP_WRITE10;
440 phase = SCSI::DATA_OUT;
441 return counter;
442 }
443 return 0;
444
445 case SCSI::OP_SEEK6:
447 currentLength = 1;
448 (void)checkAddress();
449 return 0;
450
451 case SCSI::OP_MODE_SENSE: {
452 unsigned counter = modeSense();
453 if (counter) {
454 phase = SCSI::DATA_IN;
455 }
456 return counter;
457 }
459 formatUnit();
460 return 0;
461
463 // Not supported for this device
464 return 0;
465
471 // SCSI_Group0 dummy
472 return 0;
473 }
474 } else {
475 currentSector = Endian::read_UA_B32(&cdb[2]);
476 currentLength = Endian::read_UA_B16(&cdb[7]);
477
478 switch (cdb[0]) {
479 case SCSI::OP_READ10:
480 if (checkAddress()) {
481 unsigned counter = readSectors(blocks);
482 if (counter) {
483 phase = SCSI::DATA_IN;
484 return counter;
485 }
486 }
487 return 0;
488
489 case SCSI::OP_WRITE10:
490 if (checkAddress() && !checkReadOnly()) {
491 unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
492 blocks = currentLength - tmp;
493 unsigned counter = tmp * SECTOR_SIZE;
494 phase = SCSI::DATA_OUT;
495 return counter;
496 }
497 return 0;
498
500 unsigned counter = readCapacity();
501 if (counter) {
502 phase = SCSI::DATA_IN;
503 }
504 return counter;
505 }
506 case SCSI::OP_SEEK10:
508 currentLength = 1;
509 (void)checkAddress();
510 return 0;
511 }
512 }
513
514 // unsupported command
516 return 0;
517}
518
519unsigned SCSIHD::executingCmd(SCSI::Phase& phase, unsigned& blocks)
520{
521 phase = SCSI::EXECUTE;
522 blocks = 0;
523 return 0; // Always for non-CD-ROM it seems
524}
525
526uint8_t SCSIHD::msgIn()
527{
528 uint8_t result = message;
529 message = 0;
530 return result;
531}
532
533/*
534scsiDeviceMsgOut()
535Notes:
536 [out]
537 -1: Busfree demand. (Please process it in the call origin.)
538 bit2: Status phase demand. Error happened.
539 bit1: Make it to a busfree if ATN has not been released.
540 bit0: There is a message(MsgIn).
541*/
542int SCSIHD::msgOut(uint8_t value)
543{
544 if (value & 0x80) {
545 lun = value & 7;
546 return 0;
547 }
548
549 switch (value) {
552 return 6;
553
555 busReset();
556 [[fallthrough]];
557 case SCSI::MSG_ABORT:
558 return -1;
559
560 case SCSI::MSG_REJECT:
563 return 2;
564 }
565 message = SCSI::MSG_REJECT;
566 return ((value >= 0x04) && (value <= 0x11)) ? 3 : 1;
567}
568
569
570template<typename Archive>
571void SCSIHD::serialize(Archive& ar, unsigned /*version*/)
572{
573 // don't serialize SCSIDevice, SectorAccessibleDisk, DiskContainer
574 // base classes
575 ar.template serializeBase<HD>(*this);
576 ar.serialize("keycode", keycode,
577 "currentSector", currentSector,
578 "currentLength", currentLength,
579 "unitAttention", unitAttention,
580 "message", message,
581 "lun", lun);
582 ar.serialize_blob("cdb", cdb);
583}
586
587} // namespace openmsx
Definition: one_of.hh:7
const Filename & getImageName() const
Definition: HD.hh:33
MSXMotherBoard & getMotherBoard() const
Definition: HD.hh:44
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:571
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:225
ALWAYS_INLINE uint32_t read_UA_B32(const void *p)
Definition: endian.hh:238
void writeB32(void *p, uint32_t x)
Definition: endian.hh:147
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:341
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:267
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:250
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:471
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1021
constexpr auto xrange(T e)
Definition: xrange.hh:132