OXYGENHUB
◆ main openmsx / src / ide / SCSIHD.cc
13.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
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
24
#include "DeviceConfig.hh"
25
#include "FileOperations.hh"
26
#include "LedStatus.hh"
27
#include "MSXException.hh"
28
#include "MSXMotherBoard.hh"
29
#include "serialize.hh"
30
31
#include "endian.hh"
32
#include "narrow.hh"
33
#include "one_of.hh"
34
#include "xrange.hh"
35
36
#include <algorithm>
37
#include <cstring>
38
39
namespace openmsx {
40
41
// Medium type (value like LS-120)
42
static constexpr uint8_t MT_UNKNOWN   = 0x00;
43
static constexpr uint8_t MT_2DD_UN    = 0x10;
44
static constexpr uint8_t MT_2DD       = 0x11;
45
static constexpr uint8_t MT_2HD_UN    = 0x20;
46
static constexpr uint8_t MT_2HD_12_98 = 0x22;
47
static constexpr uint8_t MT_2HD_12    = 0x23;
48
static constexpr uint8_t MT_2HD_144   = 0x24;
49
static constexpr uint8_t MT_LS120     = 0x31;
50
static constexpr uint8_t MT_NO_DISK   = 0x70;
51
static constexpr uint8_t MT_DOOR_OPEN = 0x71;
52
static constexpr uint8_t MT_FMT_ERROR = 0x72;
53
54
static constexpr std::array<uint8_t, 36> inqData = {
55
	  0,   // bit5-0 device type code.
56
	  0,   // bit7 = 1 removable device
57
	  2,   // bit7,6 ISO version. bit5,4,3 ECMA version.
58
	       // bit2,1,0 ANSI Version (001=SCSI1, 010=SCSI2)
59
	  2,   // bit7 AENC. bit6 TrmIOP.
60
	       // bit3-0 Response Data Format. (0000=SCSI1, 0001=CCS, 0010=SCSI2)
61
	 51,   // additional length
62
	  0, 0,// reserved
63
	  0,   // bit7 RelAdr, bit6 WBus32, bit5 Wbus16, bit4 Sync, bit3 Linked,
64
	       // bit2 reserved bit1 CmdQue, bit0 SftRe
65
	'o', 'p', 'e', 'n', 'M', 'S', 'X', ' ',    // vendor ID (8bytes)
66
	'S', 'C', 'S', 'I', '2', ' ', 'H', 'a',    // product ID (16bytes)
67
	'r', 'd', 'd', 'i', 's', 'k', ' ', ' ',
68
	'0', '1', '0', 'a'                         // product version (ASCII 4bytes)
69
};
70
71
static constexpr unsigned BUFFER_BLOCK_SIZE = SCSIHD::BUFFER_SIZE /
72
                                              SectorAccessibleDisk::SECTOR_SIZE;
73
74
SCSIHD::SCSIHD(const DeviceConfig& targetConfig,
75
               AlignedBuffer& buf, unsigned mode_)
76
	: HD(targetConfig)
77
	, buffer(buf)
78
	, mode(mode_)
79
	, scsiId(narrow_cast<uint8_t>(targetConfig.getAttributeValueAsInt("id", 0)))
80
{
81
	reset();
82
}
83
84
void SCSIHD::reset()
85
{
86
	currentSector = 0;
87
	currentLength = 0;
88
	busReset();
89
}
90
91
void SCSIHD::busReset()
92
{
93
	keycode = 0;
94
	unitAttention = (mode & MODE_UNITATTENTION) != 0;
95
}
96
97
void SCSIHD::disconnect()
98
{
99
	getMotherBoard().getLedStatus().setLed(LedStatus::FDD, false);
100
}
101
102
// Check the initiator in the call origin.
103
bool SCSIHD::isSelected()
104
{
105
	lun = 0;
106
	return true;
107
}
108
109
unsigned 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
	std::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
		std::ranges::copy(imageName, &buffer[36]);
148
	}
149
	return length;
150
}
151
152
unsigned 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
	}
199
	keycode = SCSI::SENSE_INVALID_COMMAND_CODE;
200
	return 0;
201
}
202
203
unsigned 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
229
bool SCSIHD::checkReadOnly()
230
{
231
	if (isWriteProtected()) {
232
		keycode = SCSI::SENSE_WRITE_PROTECT;
233
		return true;
234
	}
235
	return false;
236
}
237
238
unsigned SCSIHD::readCapacity()
239
{
240
	// TODO check for overflow
241
	auto block = unsigned(getNbSectors());
242
243
	if (block == 0) {
244
		// drive not ready
245
		keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT;
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
255
bool SCSIHD::checkAddress()
256
{
257
	auto total = unsigned(getNbSectors());
258
	if (total == 0) {
259
		// drive not ready
260
		keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT;
261
		return false;
262
	}
263
264
	if ((currentLength > 0) && (currentSector + currentLength <= total)) {
265
		return true;
266
	}
267
	keycode = SCSI::SENSE_ILLEGAL_BLOCK_ADDRESS;
268
	return false;
269
}
270
271
// Execute scsiDeviceCheckAddress previously.
272
unsigned SCSIHD::readSectors(unsigned& blocks)
273
{
274
	getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true);
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;
290
		keycode = SCSI::SENSE_UNRECOVERED_READ_ERROR;
291
		return 0;
292
	}
293
}
294
295
unsigned 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.
307
unsigned SCSIHD::writeSectors(unsigned& blocks)
308
{
309
	getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true);
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
332
unsigned 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
343
void 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
357
uint8_t SCSIHD::getStatusCode()
358
{
359
	return keycode ? SCSI::ST_CHECK_CONDITION : SCSI::ST_GOOD;
360
}
361
362
unsigned SCSIHD::executeCmd(std::span<const uint8_t, 12> cdb_, SCSI::Phase& phase, unsigned& blocks)
363
{
364
	using enum SCSI::Phase;
365
366
	copy_to_range(cdb_, cdb);
367
	message = 0;
368
	phase = STATUS;
369
	blocks = 0;
370
371
	// check unit attention
372
	if (unitAttention && (mode & MODE_UNITATTENTION) &&
373
	    (cdb[0] != one_of(SCSI::OP_INQUIRY, SCSI::OP_REQUEST_SENSE))) {
374
		unitAttention = false;
375
		keycode = SCSI::SENSE_POWER_ON;
376
		if (cdb[0] == SCSI::OP_TEST_UNIT_READY) {
377
			// changed = false;
378
		}
379
		// Unit Attention. This command is not executed.
380
		return 0;
381
	}
382
383
	// check LUN
384
	if (((cdb[1] & 0xe0) || lun) && (cdb[0] != SCSI::OP_REQUEST_SENSE) &&
385
	    !(cdb[0] == SCSI::OP_INQUIRY && !(mode & MODE_NOVAXIS))) {
386
		keycode = SCSI::SENSE_INVALID_LUN;
387
		// check LUN error
388
		return 0;
389
	}
390
391
	if (cdb[0] != SCSI::OP_REQUEST_SENSE) {
392
		keycode = SCSI::SENSE_NO_SENSE;
393
	}
394
395
	if (cdb[0] < SCSI::OP_GROUP1) {
396
		currentSector = ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3];
397
		currentLength = cdb[4];
398
399
		switch (cdb[0]) {
400
		case SCSI::OP_TEST_UNIT_READY:
401
			return 0;
402
403
		case SCSI::OP_INQUIRY: {
404
			unsigned counter = inquiry();
405
			if (counter) {
406
				phase = DATA_IN;
407
			}
408
			return counter;
409
		}
410
		case SCSI::OP_REQUEST_SENSE: {
411
			unsigned counter = requestSense();
412
			if (counter) {
413
				phase = DATA_IN;
414
			}
415
			return counter;
416
		}
417
		case SCSI::OP_READ6:
418
			if (currentLength == 0) {
419
				currentLength = SECTOR_SIZE / 2;
420
			}
421
			if (checkAddress()) {
422
				unsigned counter = readSectors(blocks);
423
				if (counter) {
424
					cdb[0] = SCSI::OP_READ10;
425
					phase = DATA_IN;
426
					return counter;
427
				}
428
			}
429
			return 0;
430
431
		case SCSI::OP_WRITE6:
432
			if (currentLength == 0) {
433
				currentLength = SECTOR_SIZE / 2;
434
			}
435
			if (checkAddress() && !checkReadOnly()) {
436
				getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true);
437
				unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
438
				blocks = currentLength - tmp;
439
				unsigned counter = tmp * SECTOR_SIZE;
440
				cdb[0] = SCSI::OP_WRITE10;
441
				phase = DATA_OUT;
442
				return counter;
443
			}
444
			return 0;
445
446
		case SCSI::OP_SEEK6:
447
			getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true);
448
			currentLength = 1;
449
			(void)checkAddress();
450
			return 0;
451
452
		case SCSI::OP_MODE_SENSE: {
453
			unsigned counter = modeSense();
454
			if (counter) {
455
				phase = DATA_IN;
456
			}
457
			return counter;
458
		}
459
		case SCSI::OP_FORMAT_UNIT:
460
			formatUnit();
461
			return 0;
462
463
		case SCSI::OP_START_STOP_UNIT:
464
			// Not supported for this device
465
			return 0;
466
467
		case SCSI::OP_REZERO_UNIT:
468
		case SCSI::OP_REASSIGN_BLOCKS:
469
		case SCSI::OP_RESERVE_UNIT:
470
		case SCSI::OP_RELEASE_UNIT:
471
		case SCSI::OP_SEND_DIAGNOSTIC:
472
			// SCSI_Group0 dummy
473
			return 0;
474
		}
475
	} else {
476
		currentSector = Endian::read_UA_B32(&cdb[2]);
477
		currentLength = Endian::read_UA_B16(&cdb[7]);
478
479
		switch (cdb[0]) {
480
		case SCSI::OP_READ10:
481
			if (checkAddress()) {
482
				unsigned counter = readSectors(blocks);
483
				if (counter) {
484
					phase = DATA_IN;
485
					return counter;
486
				}
487
			}
488
			return 0;
489
490
		case SCSI::OP_WRITE10:
491
			if (checkAddress() && !checkReadOnly()) {
492
				unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
493
				blocks = currentLength - tmp;
494
				unsigned counter = tmp * SECTOR_SIZE;
495
				phase = DATA_OUT;
496
				return counter;
497
			}
498
			return 0;
499
500
		case SCSI::OP_READ_CAPACITY: {
501
			unsigned counter = readCapacity();
502
			if (counter) {
503
				phase = DATA_IN;
504
			}
505
			return counter;
506
		}
507
		case SCSI::OP_SEEK10:
508
			getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true);
509
			currentLength = 1;
510
			(void)checkAddress();
511
			return 0;
512
		}
513
	}
514
515
	// unsupported command
516
	keycode = SCSI::SENSE_INVALID_COMMAND_CODE;
517
	return 0;
518
}
519
520
unsigned SCSIHD::executingCmd(SCSI::Phase& phase, unsigned& blocks)
521
{
522
	phase = SCSI::Phase::EXECUTE;
523
	blocks = 0;
524
	return 0; // Always for non-CD-ROM it seems
525
}
526
527
uint8_t SCSIHD::msgIn()
528
{
529
	uint8_t result = message;
530
	message = 0;
531
	return result;
532
}
533
534
/*
535
scsiDeviceMsgOut()
536
Notes:
537
    [out]
538
	  -1: Busfree demand. (Please process it in the call origin.)
539
	bit2: Status phase demand. Error happened.
540
	bit1: Make it to a busfree if ATN has not been released.
541
	bit0: There is a message(MsgIn).
542
*/
543
int SCSIHD::msgOut(uint8_t value)
544
{
545
	if (value & 0x80) {
546
		lun = value & 7;
547
		return 0;
548
	}
549
550
	switch (value) {
551
	case SCSI::MSG_INITIATOR_DETECT_ERROR:
552
		keycode = SCSI::SENSE_INITIATOR_DETECTED_ERR;
553
		return 6;
554
555
	case SCSI::MSG_BUS_DEVICE_RESET:
556
		busReset();
557
		[[fallthrough]];
558
	case SCSI::MSG_ABORT:
559
		return -1;
560
561
	case SCSI::MSG_REJECT:
562
	case SCSI::MSG_PARITY_ERROR:
563
	case SCSI::MSG_NO_OPERATION:
564
		return 2;
565
	}
566
	message = SCSI::MSG_REJECT;
567
	return ((value >= 0x04) && (value <= 0x11)) ? 3 : 1;
568
}
569
570
571
template<typename Archive>
572
void SCSIHD::serialize(Archive& ar, unsigned /*version*/)
573
{
574
	// don't serialize SCSIDevice, SectorAccessibleDisk, DiskContainer
575
	// base classes
576
	ar.template serializeBase<HD>(*this);
577
	ar.serialize("keycode",       keycode,
578
	             "currentSector", currentSector,
579
	             "currentLength", currentLength,
580
	             "unitAttention", unitAttention,
581
	             "message",       message,
582
	             "lun",           lun);
583
	ar.serialize_blob("cdb", cdb);
584
}
585
INSTANTIATE_SERIALIZE_METHODS(SCSIHD);
586
REGISTER_POLYMORPHIC_INITIALIZER(SCSIDevice, SCSIHD, "SCSIHD");
587
588
} // namespace openmsx