OXYGENHUB
◆ main openmsx / src / ide / SCSILS120.cc
20.2 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 supports a removable LS-120 disk, as the class
18
 *  name suggests. Refer to revision 6526 of SCSIHD.cc to see what was removed
19
 *  from the generic/parameterized code.
20
 */
21
22
#include "SCSILS120.hh"
23
24
#include "CommandException.hh"
25
#include "DeviceConfig.hh"
26
#include "FileContext.hh"
27
#include "FileException.hh"
28
#include "FileOperations.hh"
29
#include "FilePool.hh"
30
#include "LedStatus.hh"
31
#include "MSXCliComm.hh"
32
#include "MSXMotherBoard.hh"
33
#include "RecordedCommand.hh"
34
#include "TclObject.hh"
35
#include "serialize.hh"
36
37
#include "endian.hh"
38
#include "narrow.hh"
39
#include "one_of.hh"
40
41
#include <algorithm>
42
#include <array>
43
#include <cstring>
44
#include <memory>
45
#include <vector>
46
47
namespace openmsx {
48
49
// Medium type (value like LS-120)
50
static constexpr uint8_t MT_UNKNOWN   = 0x00;
51
static constexpr uint8_t MT_2DD_UN    = 0x10;
52
static constexpr uint8_t MT_2DD       = 0x11;
53
static constexpr uint8_t MT_2HD_UN    = 0x20;
54
static constexpr uint8_t MT_2HD_12_98 = 0x22;
55
static constexpr uint8_t MT_2HD_12    = 0x23;
56
static constexpr uint8_t MT_2HD_144   = 0x24;
57
static constexpr uint8_t MT_LS120     = 0x31;
58
static constexpr uint8_t MT_NO_DISK   = 0x70;
59
static constexpr uint8_t MT_DOOR_OPEN = 0x71;
60
static constexpr uint8_t MT_FMT_ERROR = 0x72;
61
62
static constexpr std::array<uint8_t, 36> inqData = {
63
	  0,   // bit5-0 device type code.
64
	  0,   // bit7 = 1 removable device
65
	  2,   // bit7,6 ISO version. bit5,4,3 ECMA version.
66
	       // bit2,1,0 ANSI Version (001=SCSI1, 010=SCSI2)
67
	  2,   // bit7 AENC. bit6 TrmIOP.
68
	       // bit3-0 Response Data Format. (0000=SCSI1, 0001=CCS, 0010=SCSI2)
69
	 51,   // additional length
70
	  0, 0,// reserved
71
	  0,   // bit7 RelAdr, bit6 WBus32, bit5 Wbus16, bit4 Sync, bit3 Linked,
72
	       // bit2 reserved bit1 CmdQue, bit0 SftRe
73
	'o', 'p', 'e', 'n', 'M', 'S', 'X', ' ',    // vendor ID (8bytes)
74
	'S', 'C', 'S', 'I', '2', ' ', 'L', 'S',    // product ID (16bytes)
75
	'-', '1', '2', '0', 'd', 'i', 's', 'k',
76
	'0', '1', '0', 'a'                         // product version (ASCII 4bytes)
77
};
78
79
// for FDSFORM.COM
80
static constexpr std::string_view fds120 = "IODATA  LS-120 COSM     0001";
81
82
static constexpr unsigned BUFFER_BLOCK_SIZE = SCSIDevice::BUFFER_SIZE /
83
                                              SectorAccessibleDisk::SECTOR_SIZE;
84
85
SCSILS120::SCSILS120(const DeviceConfig& targetConfig,
86
                     AlignedBuffer& buf, unsigned mode_)
87
	: motherBoard(targetConfig.getMotherBoard())
88
	, buffer(buf)
89
	, name("lsX")
90
	, mode(mode_)
91
	, scsiId(narrow_cast<uint8_t>(targetConfig.getAttributeValueAsInt("id", 0)))
92
{
93
	lsInUse = motherBoard.getSharedStuff<LSInUse>("lsInUse");
94
95
	unsigned id = 0;
96
	while ((*lsInUse)[id]) {
97
		++id;
98
		if (id == MAX_LS) {
99
			throw MSXException("Too many LSs");
100
		}
101
	}
102
	name[2] = char('a' + id);
103
	(*lsInUse)[id] = true;
104
	lsxCommand.emplace(
105
		motherBoard.getCommandController(),
106
		motherBoard.getStateChangeDistributor(),
107
		motherBoard.getScheduler(), *this);
108
109
	lun = 0; // TODO move to reset() ?
110
	message = 0;
111
	reset();
112
113
	motherBoard.registerMediaProvider(name, *this);
114
	motherBoard.getMSXCliComm().update(CliComm::UpdateType::HARDWARE, name, "add");
115
}
116
117
SCSILS120::~SCSILS120()
118
{
119
	motherBoard.unregisterMediaProvider(*this);
120
	motherBoard.getMSXCliComm().update(CliComm::UpdateType::HARDWARE, name, "remove");
121
122
	unsigned id = name[2] - 'a';
123
	assert((*lsInUse)[id]);
124
	(*lsInUse)[id] = false;
125
}
126
127
128
void SCSILS120::getMediaInfo(TclObject& result)
129
{
130
	result.addDictKeyValue("target", filename);
131
}
132
133
void SCSILS120::setMedia(const TclObject& info, EmuTime /*time*/)
134
{
135
	auto target = info.getOptionalDictValue(TclObject("target"));
136
	if (!target) return;
137
138
	if (auto trgt = target->getString(); trgt.empty()) {
139
		eject();
140
	} else {
141
		insert(trgt);
142
	}
143
}
144
145
void SCSILS120::reset()
146
{
147
	mediaChanged  = false;
148
	currentSector = 0;
149
	currentLength = 0;
150
	busReset();
151
}
152
153
void SCSILS120::busReset()
154
{
155
	keycode = 0;
156
	unitAttention = (mode & MODE_UNITATTENTION) != 0;
157
}
158
159
void SCSILS120::disconnect()
160
{
161
	motherBoard.getLedStatus().setLed(LedStatus::FDD, false);
162
}
163
164
// Check the initiator in the call origin.
165
bool SCSILS120::isSelected()
166
{
167
	lun = 0;
168
	return file.is_open();
169
}
170
171
bool SCSILS120::getReady()
172
{
173
	if (file.is_open()) return true;
174
	keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT;
175
	return false;
176
}
177
178
void SCSILS120::testUnitReady()
179
{
180
	if ((mode & MODE_NOVAXIS) == 0) {
181
		if (getReady() && mediaChanged && (mode & MODE_MEGASCSI)) {
182
			// Disk change is surely sent for the driver of MEGA-SCSI.
183
			keycode = SCSI::SENSE_POWER_ON;
184
		}
185
	}
186
	mediaChanged = false;
187
}
188
189
void SCSILS120::startStopUnit()
190
{
191
	switch (cdb[4]) {
192
	case 2: // Eject
193
		eject();
194
		break;
195
	case 3: // Insert  TODO: how can this happen?
196
		//if (!diskPresent(diskId)) {
197
		//	*disk = disk;
198
		//	updateExtendedDiskName(diskId, disk->fileName, disk->fileNameInZip);
199
		//	boardChangeDiskette(diskId, disk->fileName, disk->fileNameInZip);
200
		//}
201
		break;
202
	}
203
}
204
205
unsigned SCSILS120::inquiry()
206
{
207
	auto total      = getNbSectors();
208
	unsigned length = currentLength;
209
210
	bool fdsMode = (total > 0) && (total <= 2880);
211
212
	if (length == 0) return 0;
213
214
	buffer[0] = SCSI::DT_DirectAccess;
215
	buffer[1] = 0x80; // removable
216
	if (fdsMode) {
217
		std::ranges::copy(subspan<6>(inqData, 2), &buffer[2]);
218
		std::ranges::copy(fds120, &buffer[8]);
219
	} else {
220
		std::ranges::copy(subspan(inqData, 2), &buffer[2]);
221
	}
222
223
	if (!(mode & BIT_SCSI2)) {
224
		buffer[2] = 1;
225
		buffer[3] = 1;
226
		if (!fdsMode) buffer[20] = '1';
227
	} else {
228
		if (mode & BIT_SCSI3) {
229
			buffer[2] = 5;
230
			if (!fdsMode) buffer[20] = '3';
231
		}
232
	}
233
234
	if (mode & BIT_SCSI3) {
235
		length = std::min(length, 96u);
236
		buffer[4] = 91;
237
		if (length > 56) {
238
			memset(buffer + 56, 0, 40);
239
			buffer[58] = 0x03;
240
			buffer[60] = 0x01;
241
			buffer[61] = 0x80;
242
		}
243
	} else {
244
		length = std::min(length, 56u);
245
	}
246
247
	if (length > 36) {
248
		std::string fname(FileOperations::getFilename(filename));
249
		fname.resize(20, ' ');
250
		std::ranges::copy(fname, &buffer[36]);
251
	}
252
	return length;
253
}
254
255
unsigned SCSILS120::modeSense()
256
{
257
	uint8_t* pBuffer = buffer;
258
259
	if ((currentLength > 0) && (cdb[2] == 3)) {
260
		auto total       = getNbSectors();
261
		uint8_t media       = MT_UNKNOWN;
262
		uint8_t sectors     = 64;
263
		uint8_t blockLength = SECTOR_SIZE >> 8;
264
		uint8_t tracks      = 8;
265
		uint8_t size        = 4 + 24;
266
		uint8_t removable   = 0xa0;
267
268
		memset(pBuffer + 2, 0, 34);
269
270
		if (total == 0) {
271
			media = MT_NO_DISK;
272
		} else {
273
			if (total == 1440) {
274
				media       = MT_2DD;
275
				sectors     = 9;
276
				blockLength = 2048 >> 8; // FDS-120 value
277
				tracks      = 160;
278
			} else {
279
				if (total == 2880) {
280
					media       = MT_2HD_144;
281
					sectors     = 18;
282
					blockLength = 2048 >> 8;
283
					tracks      = 160;
284
				}
285
			}
286
		}
287
288
		// Mode Parameter Header 4bytes
289
		pBuffer[1] = media; // Medium Type
290
		pBuffer[3] = 8;     // block descriptor length
291
		pBuffer += 4;
292
293
		// Disable Block Descriptor check
294
		if (!(cdb[1] & 0x08)) {
295
			// Block Descriptor 8bytes
296
			pBuffer[1] = (total >> 16) & 0xff; // 1..3 Number of Blocks
297
			pBuffer[2] = (total >>  8) & 0xff;
298
			pBuffer[3] = (total >>  0) & 0xff;
299
			pBuffer[6] = blockLength & 0xff;   // 5..7 Block Length in Bytes
300
			pBuffer += 8;
301
			size   += 8;
302
		}
303
304
		// Format Device Page 24bytes
305
		pBuffer[ 0] = 3;           //     0 Page
306
		pBuffer[ 1] = 0x16;        //     1 Page Length
307
		pBuffer[ 3] = tracks;      //  2, 3 Tracks per Zone
308
		pBuffer[11] = sectors;     // 10,11 Sectors per Track
309
		pBuffer[12] = blockLength; // 12,13 Data Bytes per Physical Sector
310
		pBuffer[20] = removable;   // 20    bit7 Soft Sector bit5 Removable
311
312
		buffer[0] = size - 1;    // sense data length
313
314
		return std::min<unsigned>(currentLength, size);
315
	}
316
	keycode = SCSI::SENSE_INVALID_COMMAND_CODE;
317
	return 0;
318
}
319
320
unsigned SCSILS120::requestSense()
321
{
322
	unsigned length = currentLength;
323
	unsigned tmpKeycode = unitAttention ? SCSI::SENSE_POWER_ON : keycode;
324
	unitAttention = false;
325
326
	keycode = SCSI::SENSE_NO_SENSE;
327
328
	memset(buffer + 1, 0, 17);
329
	if (length == 0) {
330
		if (mode & BIT_SCSI2) {
331
			return 0;
332
		}
333
		buffer[ 0] = (tmpKeycode >>  8) & 0xff; // Sense code
334
		length = 4;
335
	} else {
336
		buffer[ 0] = 0x70;
337
		buffer[ 2] = (tmpKeycode >> 16) & 0xff; // Sense key
338
		buffer[ 7] = 10;                        // Additional sense length
339
		buffer[12] = (tmpKeycode >>  8) & 0xff; // Additional sense code
340
		buffer[13] = (tmpKeycode >>  0) & 0xff; // Additional sense code qualifier
341
		length = std::min(length, 18u);
342
	}
343
	return length;
344
}
345
346
bool SCSILS120::checkReadOnly()
347
{
348
	if (file.isReadOnly()) {
349
		keycode = SCSI::SENSE_WRITE_PROTECT;
350
		return true;
351
	}
352
	return false;
353
}
354
355
unsigned SCSILS120::readCapacity()
356
{
357
	auto block = unsigned(getNbSectors());
358
359
	if (block == 0) {
360
		// drive not ready
361
		keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT;
362
		return 0;
363
	}
364
365
	--block;
366
	Endian::writeB32(&buffer[0], block);
367
	Endian::writeB32(&buffer[4], SECTOR_SIZE); // TODO see SCSIHD
368
369
	return 8;
370
}
371
372
bool SCSILS120::checkAddress()
373
{
374
	auto total = unsigned(getNbSectors());
375
	if (total == 0) {
376
		// drive not ready
377
		keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT;
378
		return false;
379
	}
380
381
	if ((currentLength > 0) && (currentSector + currentLength <= total)) {
382
		return true;
383
	}
384
	keycode = SCSI::SENSE_ILLEGAL_BLOCK_ADDRESS;
385
	return false;
386
}
387
388
// Execute scsiDeviceCheckAddress previously.
389
unsigned SCSILS120::readSector(unsigned& blocks)
390
{
391
	motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
392
393
	unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE);
394
	unsigned counter = currentLength * SECTOR_SIZE;
395
396
	try {
397
		// TODO: somehow map this to SectorAccessibleDisk::readSector?
398
		file.seek(SECTOR_SIZE * currentSector);
399
		file.read(std::span{buffer.data(), SECTOR_SIZE * numSectors});
400
		currentSector += numSectors;
401
		currentLength -= numSectors;
402
		blocks = currentLength;
403
		return counter;
404
	} catch (FileException&) {
405
		blocks = 0;
406
		keycode = SCSI::SENSE_UNRECOVERED_READ_ERROR;
407
		return 0;
408
	}
409
}
410
411
unsigned SCSILS120::dataIn(unsigned& blocks)
412
{
413
	if (cdb[0] == SCSI::OP_READ10) {
414
		unsigned counter = readSector(blocks);
415
		if (counter) {
416
			return counter;
417
		}
418
	}
419
	// error
420
	blocks = 0;
421
	return 0;
422
}
423
424
// Execute scsiDeviceCheckAddress and scsiDeviceCheckReadOnly previously.
425
unsigned SCSILS120::writeSector(unsigned& blocks)
426
{
427
	motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
428
429
	unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE);
430
431
	// TODO: somehow map this to SectorAccessibleDisk::writeSector?
432
	try {
433
		file.seek(SECTOR_SIZE * currentSector);
434
		file.write(std::span{buffer.data(), SECTOR_SIZE * numSectors});
435
		currentSector += numSectors;
436
		currentLength -= numSectors;
437
438
		unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
439
		blocks = currentLength - tmp;
440
		unsigned counter = tmp * SECTOR_SIZE;
441
		return counter;
442
	} catch (FileException&) {
443
		keycode = SCSI::SENSE_WRITE_FAULT;
444
		blocks = 0;
445
		return 0;
446
	}
447
}
448
449
unsigned SCSILS120::dataOut(unsigned& blocks)
450
{
451
	if (cdb[0] == SCSI::OP_WRITE10) {
452
		return writeSector(blocks);
453
	}
454
	// error
455
	blocks = 0;
456
	return 0;
457
}
458
459
//  MBR erase only
460
void SCSILS120::formatUnit()
461
{
462
	if (getReady() && !checkReadOnly()) {
463
		memset(buffer, 0, SECTOR_SIZE);
464
		try {
465
			file.seek(0);
466
			file.write(std::span{buffer.data(), SECTOR_SIZE});
467
			unitAttention = true;
468
			mediaChanged = true;
469
		} catch (FileException&) {
470
			keycode = SCSI::SENSE_WRITE_FAULT;
471
		}
472
	}
473
}
474
475
uint8_t SCSILS120::getStatusCode()
476
{
477
	return keycode ? SCSI::ST_CHECK_CONDITION : SCSI::ST_GOOD;
478
}
479
480
void SCSILS120::eject()
481
{
482
	file.close();
483
	filename.clear();
484
	mediaChanged = true;
485
	if (mode & MODE_UNITATTENTION) {
486
		unitAttention = true;
487
	}
488
	motherBoard.getMSXCliComm().update(CliComm::UpdateType::MEDIA, name, {});
489
}
490
491
void SCSILS120::insert(zstring_view fname)
492
{
493
	file = File(fname);
494
	filename = fname;
495
	mediaChanged = true;
496
	if (mode & MODE_UNITATTENTION) {
497
		unitAttention = true;
498
	}
499
	motherBoard.getMSXCliComm().update(CliComm::UpdateType::MEDIA, name, filename);
500
}
501
502
unsigned SCSILS120::executeCmd(std::span<const uint8_t, 12> cdb_, SCSI::Phase& phase, unsigned& blocks)
503
{
504
	using enum SCSI::Phase;
505
506
	copy_to_range(cdb_, cdb);
507
	message = 0;
508
	phase = STATUS;
509
	blocks = 0;
510
511
	// check unit attention
512
	if (unitAttention && (mode & MODE_UNITATTENTION) &&
513
	    (cdb[0] != one_of(SCSI::OP_INQUIRY, SCSI::OP_REQUEST_SENSE))) {
514
		unitAttention = false;
515
		keycode = SCSI::SENSE_POWER_ON;
516
		if (cdb[0] == SCSI::OP_TEST_UNIT_READY) {
517
			mediaChanged = false;
518
		}
519
		// Unit Attention. This command is not executed.
520
		return 0;
521
	}
522
523
	// check LUN
524
	if (((cdb[1] & 0xe0) || lun) && (cdb[0] != SCSI::OP_REQUEST_SENSE) &&
525
	    !(cdb[0] == SCSI::OP_INQUIRY && !(mode & MODE_NOVAXIS))) {
526
		keycode = SCSI::SENSE_INVALID_LUN;
527
		// check LUN error
528
		return 0;
529
	}
530
531
	if (cdb[0] != SCSI::OP_REQUEST_SENSE) {
532
		keycode = SCSI::SENSE_NO_SENSE;
533
	}
534
535
	if (cdb[0] < SCSI::OP_GROUP1) {
536
		currentSector = ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3];
537
		currentLength = cdb[4];
538
539
		switch (cdb[0]) {
540
		case SCSI::OP_TEST_UNIT_READY:
541
			testUnitReady();
542
			return 0;
543
544
		case SCSI::OP_INQUIRY: {
545
			unsigned counter = inquiry();
546
			if (counter) {
547
				phase = DATA_IN;
548
			}
549
			return counter;
550
		}
551
		case SCSI::OP_REQUEST_SENSE: {
552
			unsigned counter = requestSense();
553
			if (counter) {
554
				phase = DATA_IN;
555
			}
556
			return counter;
557
		}
558
		case SCSI::OP_READ6:
559
			if (currentLength == 0) {
560
				currentLength = SECTOR_SIZE >> 1;
561
			}
562
			if (checkAddress()) {
563
				unsigned counter = readSector(blocks);
564
				if (counter) {
565
					cdb[0] = SCSI::OP_READ10;
566
					phase = DATA_IN;
567
					return counter;
568
				}
569
			}
570
			return 0;
571
572
		case SCSI::OP_WRITE6:
573
			if (currentLength == 0) {
574
				currentLength = SECTOR_SIZE >> 1;
575
			}
576
			if (checkAddress() && !checkReadOnly()) {
577
				motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
578
				unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
579
				blocks = currentLength - tmp;
580
				unsigned counter = tmp * SECTOR_SIZE;
581
				cdb[0] = SCSI::OP_WRITE10;
582
				phase = DATA_OUT;
583
				return counter;
584
			}
585
			return 0;
586
587
		case SCSI::OP_SEEK6:
588
			motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
589
			currentLength = 1;
590
			(void)checkAddress();
591
			return 0;
592
593
		case SCSI::OP_MODE_SENSE: {
594
			unsigned counter = modeSense();
595
			if (counter) {
596
				phase = DATA_IN;
597
			}
598
			return counter;
599
		}
600
		case SCSI::OP_FORMAT_UNIT:
601
			formatUnit();
602
			return 0;
603
604
		case SCSI::OP_START_STOP_UNIT:
605
			startStopUnit();
606
			return 0;
607
608
		case SCSI::OP_REZERO_UNIT:
609
		case SCSI::OP_REASSIGN_BLOCKS:
610
		case SCSI::OP_RESERVE_UNIT:
611
		case SCSI::OP_RELEASE_UNIT:
612
		case SCSI::OP_SEND_DIAGNOSTIC:
613
			// SCSI_Group0 dummy
614
			return 0;
615
		}
616
	} else {
617
		currentSector = Endian::read_UA_B32(&cdb[2]);
618
		currentLength = Endian::read_UA_B16(&cdb[7]);
619
620
		switch (cdb[0]) {
621
		case SCSI::OP_READ10:
622
			if (checkAddress()) {
623
				unsigned counter = readSector(blocks);
624
				if (counter) {
625
					phase = DATA_IN;
626
					return counter;
627
				}
628
			}
629
			return 0;
630
631
		case SCSI::OP_WRITE10:
632
			if (checkAddress() && !checkReadOnly()) {
633
				unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE);
634
				blocks = currentLength - tmp;
635
				unsigned counter = tmp * SECTOR_SIZE;
636
				phase = DATA_OUT;
637
				return counter;
638
			}
639
			return 0;
640
641
		case SCSI::OP_READ_CAPACITY: {
642
			unsigned counter = readCapacity();
643
			if (counter) {
644
				phase = DATA_IN;
645
			}
646
			return counter;
647
		}
648
		case SCSI::OP_SEEK10:
649
			motherBoard.getLedStatus().setLed(LedStatus::FDD, true);
650
			currentLength = 1;
651
			(void)checkAddress();
652
			return 0;
653
		}
654
	}
655
656
	// unsupported command
657
	keycode = SCSI::SENSE_INVALID_COMMAND_CODE;
658
	return 0;
659
}
660
661
unsigned SCSILS120::executingCmd(SCSI::Phase& phase, unsigned& blocks)
662
{
663
	phase = SCSI::Phase::EXECUTE;
664
	blocks = 0;
665
	return 0; // we're very fast
666
}
667
668
uint8_t SCSILS120::msgIn()
669
{
670
	uint8_t result = message;
671
	message = 0;
672
	return result;
673
}
674
675
/*
676
scsiDeviceMsgOut()
677
Notes:
678
    [out]
679
	  -1: Busfree demand. (Please process it in the call origin.)
680
	bit2: Status phase demand. Error happened.
681
	bit1: Make it to a busfree if ATN has not been released.
682
	bit0: There is a message(MsgIn).
683
*/
684
int SCSILS120::msgOut(uint8_t value)
685
{
686
	if (value & 0x80) {
687
		lun = value & 7;
688
		return 0;
689
	}
690
691
	switch (value) {
692
	case SCSI::MSG_INITIATOR_DETECT_ERROR:
693
		keycode = SCSI::SENSE_INITIATOR_DETECTED_ERR;
694
		return 6;
695
696
	case SCSI::MSG_BUS_DEVICE_RESET:
697
		busReset();
698
		[[fallthrough]];
699
	case SCSI::MSG_ABORT:
700
		return -1;
701
702
	case SCSI::MSG_REJECT:
703
	case SCSI::MSG_PARITY_ERROR:
704
	case SCSI::MSG_NO_OPERATION:
705
		return 2;
706
	}
707
	message = SCSI::MSG_REJECT;
708
	return ((value >= 0x04) && (value <= 0x11)) ? 3 : 1;
709
}
710
711
size_t SCSILS120::getNbSectorsImpl()
712
{
713
	return file.is_open() ? (file.getSize() / SECTOR_SIZE) : 0;
714
}
715
716
bool SCSILS120::isWriteProtectedImpl() const
717
{
718
	return false;
719
}
720
721
Sha1Sum SCSILS120::getSha1SumImpl(FilePool& filePool)
722
{
723
	if (hasPatches()) {
724
		return SectorAccessibleDisk::getSha1SumImpl(filePool);
725
	}
726
	return filePool.getSha1Sum(file, filename);
727
}
728
729
void SCSILS120::readSectorsImpl(
730
	std::span<SectorBuffer> buffers, size_t startSector)
731
{
732
	file.seek(startSector * sizeof(SectorBuffer));
733
	file.read(buffers);
734
}
735
736
void SCSILS120::writeSectorImpl(size_t sector, const SectorBuffer& buf)
737
{
738
	file.seek(sizeof(buf) * sector);
739
	file.write(buf.raw);
740
}
741
742
SectorAccessibleDisk* SCSILS120::getSectorAccessibleDisk()
743
{
744
	return this;
745
}
746
747
std::string_view SCSILS120::getContainerName() const
748
{
749
	return name;
750
}
751
752
bool SCSILS120::diskChanged()
753
{
754
	return mediaChanged; // TODO not reset on read
755
}
756
757
int SCSILS120::insertDisk(const std::string& fname)
758
{
759
	try {
760
		insert(fname);
761
		return 0;
762
	} catch (MSXException&) {
763
		return -1;
764
	}
765
}
766
767
768
// class LSXCommand
769
770
LSXCommand::LSXCommand(CommandController& commandController_,
771
                       StateChangeDistributor& stateChangeDistributor_,
772
                       Scheduler& scheduler_, SCSILS120& ls_)
773
	: RecordedCommand(commandController_, stateChangeDistributor_,
774
	                  scheduler_, ls_.name)
775
	, ls(ls_)
776
{
777
}
778
779
void LSXCommand::execute(std::span<const TclObject> tokens, TclObject& result,
780
                         EmuTime /*time*/)
781
{
782
	if (tokens.size() == 1) {
783
		const auto& file = ls.file;
784
		result.addListElement(tmpStrCat(ls.name, ':'), ls.filename);
785
		if (!file.is_open()) result.addListElement("empty");
786
	} else if ((tokens.size() == 2) && (tokens[1] == one_of("eject", "-eject"))) {
787
		ls.eject();
788
		// TODO check for locked tray
789
		if (tokens[1] == "-eject") {
790
			result = "Warning: use of '-eject' is deprecated, "
791
			         "instead use the 'eject' subcommand";
792
		}
793
	} else if ((tokens.size() == 2) ||
794
	           ((tokens.size() == 3) && (tokens[1] == "insert"))) {
795
		int fileToken = 1;
796
		if (tokens[1] == "insert") {
797
			if (tokens.size() > 2) {
798
				fileToken = 2;
799
			} else {
800
				throw CommandException(
801
					"Missing argument to insert subcommand");
802
			}
803
		}
804
		try {
805
			ls.insert(userFileContext().resolve(tokens[fileToken].getString()));
806
		} catch (FileException& e) {
807
			throw CommandException("Can't change disk image: ",
808
			                       e.getMessage());
809
		}
810
	} else {
811
		throw CommandException("Too many or wrong arguments.");
812
	}
813
}
814
815
std::string LSXCommand::help(std::span<const TclObject> /*tokens*/) const
816
{
817
	return strCat(
818
		ls.name, "                   : display the disk image for this LS-120 drive\n",
819
		ls.name, " eject             : eject the disk image from this LS-120 drive\n",
820
		ls.name, " insert <filename> : change the disk image for this LS-120 drive\n",
821
		ls.name, " <filename>        : change the disk image for this LS-120 drive\n");
822
}
823
824
void LSXCommand::tabCompletion(std::vector<std::string>& tokens) const
825
{
826
	using namespace std::literals;
827
	static constexpr std::array extra = {"eject"sv, "insert"sv};
828
	completeFileName(tokens, userFileContext(), extra);
829
}
830
831
832
template<typename Archive>
833
void SCSILS120::serialize(Archive& ar, unsigned /*version*/)
834
{
835
	ar.serialize("filename", filename);
836
	if constexpr (Archive::IS_LOADER) {
837
		// re-insert disk before restoring 'mediaChanged'
838
		if (filename.empty()) {
839
			eject();
840
		} else {
841
			insert(filename);
842
		}
843
	}
844
845
	ar.serialize("keycode",       keycode,
846
	             "currentSector", currentSector,
847
	             "currentLength", currentLength,
848
	             "unitAttention", unitAttention,
849
	             "mediaChanged",  mediaChanged,
850
	             "message",       message,
851
	             "lun",           lun);
852
	ar.serialize_blob("cdb", cdb);
853
}
854
INSTANTIATE_SERIALIZE_METHODS(SCSILS120);
855
REGISTER_POLYMORPHIC_INITIALIZER(SCSIDevice, SCSILS120, "SCSILS120");
856
857
} // namespace openmsx