OXYGENHUB
◆ main openmsx / src / ide / WD33C93.cc
13.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/* Ported from:
2
** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/wd33c93.c,v
3
** Revision: 1.12
4
** Date: 2007/03/25 17:05:07
5
**
6
** Based on the WD33C93 emulation in MESS (www.mess.org).
7
**
8
** More info: http://www.bluemsx.com
9
**
10
** Copyright (C) 2003-2006 Daniel Vik, Tomas Karlsson, white cat
11
*/
12
13
#include "WD33C93.hh"
14
15
#include "DummySCSIDevice.hh"
16
#include "SCSI.hh"
17
#include "SCSIDevice.hh"
18
#include "SCSIHD.hh"
19
#include "SCSILS120.hh"
20
21
#include "DeviceConfig.hh"
22
#include "MSXException.hh"
23
#include "XMLElement.hh"
24
#include "serialize.hh"
25
26
#include "enumerate.hh"
27
#include "narrow.hh"
28
29
#include <algorithm>
30
#include <array>
31
#include <cassert>
32
#include <memory>
33
34
namespace openmsx {
35
36
static constexpr unsigned MAX_DEV = 8;
37
38
static constexpr uint8_t REG_OWN_ID      = 0x00;
39
static constexpr uint8_t REG_CONTROL     = 0x01;
40
static constexpr uint8_t REG_TIMEO       = 0x02;
41
static constexpr uint8_t REG_TSECS       = 0x03;
42
static constexpr uint8_t REG_THEADS      = 0x04;
43
static constexpr uint8_t REG_TCYL_HI     = 0x05;
44
static constexpr uint8_t REG_TCYL_LO     = 0x06;
45
static constexpr uint8_t REG_ADDR_HI     = 0x07;
46
static constexpr uint8_t REG_ADDR_2      = 0x08;
47
static constexpr uint8_t REG_ADDR_3      = 0x09;
48
static constexpr uint8_t REG_ADDR_LO     = 0x0a;
49
static constexpr uint8_t REG_SECNO       = 0x0b;
50
static constexpr uint8_t REG_HEADNO      = 0x0c;
51
static constexpr uint8_t REG_CYLNO_HI    = 0x0d;
52
static constexpr uint8_t REG_CYLNO_LO    = 0x0e;
53
static constexpr uint8_t REG_TLUN        = 0x0f;
54
static constexpr uint8_t REG_CMD_PHASE   = 0x10;
55
static constexpr uint8_t REG_SYN         = 0x11;
56
static constexpr uint8_t REG_TCH         = 0x12;
57
static constexpr uint8_t REG_TCM         = 0x13;
58
static constexpr uint8_t REG_TCL         = 0x14;
59
static constexpr uint8_t REG_DST_ID      = 0x15;
60
static constexpr uint8_t REG_SRC_ID      = 0x16;
61
static constexpr uint8_t REG_SCSI_STATUS = 0x17; // (r)
62
static constexpr uint8_t REG_CMD         = 0x18;
63
static constexpr uint8_t REG_DATA        = 0x19;
64
static constexpr uint8_t REG_QUEUE_TAG   = 0x1a;
65
static constexpr uint8_t REG_AUX_STATUS  = 0x1f; // (r)
66
67
static constexpr uint8_t REG_CDBSIZE     = 0x00;
68
static constexpr uint8_t REG_CDB1        = 0x03;
69
static constexpr uint8_t REG_CDB2        = 0x04;
70
static constexpr uint8_t REG_CDB3        = 0x05;
71
static constexpr uint8_t REG_CDB4        = 0x06;
72
static constexpr uint8_t REG_CDB5        = 0x07;
73
static constexpr uint8_t REG_CDB6        = 0x08;
74
static constexpr uint8_t REG_CDB7        = 0x09;
75
static constexpr uint8_t REG_CDB8        = 0x0a;
76
static constexpr uint8_t REG_CDB9        = 0x0b;
77
static constexpr uint8_t REG_CDB10       = 0x0c;
78
static constexpr uint8_t REG_CDB11       = 0x0d;
79
static constexpr uint8_t REG_CDB12       = 0x0e;
80
81
static constexpr uint8_t OWN_EAF         = 0x08; // ENABLE ADVANCED FEATURES
82
83
// SCSI STATUS
84
static constexpr uint8_t SS_RESET        = 0x00; // reset
85
static constexpr uint8_t SS_RESET_ADV    = 0x01; // reset w/adv. features
86
static constexpr uint8_t SS_XFER_END     = 0x16; // select and transfer complete
87
static constexpr uint8_t SS_SEL_TIMEOUT  = 0x42; // selection timeout
88
static constexpr uint8_t SS_DISCONNECT   = 0x85;
89
90
// AUX STATUS
91
static constexpr uint8_t AS_DBR          = 0x01; // data buffer ready
92
static constexpr uint8_t AS_CIP          = 0x10; // command in progress, chip is busy
93
static constexpr uint8_t AS_BSY          = 0x20; // Level 2 command in progress
94
static constexpr uint8_t AS_LCI          = 0x40; // last command ignored
95
static constexpr uint8_t AS_INT          = 0x80;
96
97
/* command phase
98
0x00    NO_SELECT
99
0x10    SELECTED
100
0x20    IDENTIFY_SENT
101
0x30    COMMAND_OUT
102
0x41    SAVE_DATA_RECEIVED
103
0x42    DISCONNECT_RECEIVED
104
0x43    LEGAL_DISCONNECT
105
0x44    RESELECTED
106
0x45    IDENTIFY_RECEIVED
107
0x46    DATA_TRANSFER_DONE
108
0x47    STATUS_STARTED
109
0x50    STATUS_RECEIVED
110
0x60    COMPLETE_RECEIVED
111
*/
112
113
WD33C93::WD33C93(DeviceConfig& config)
114
{
115
	for (auto* t : config.getXML()->getChildren("target")) {
116
		unsigned id = t->getAttributeValueAsInt("id", 0);
117
		if (id >= MAX_DEV) {
118
			throw MSXException("Invalid SCSI id: ", id,
119
			                   " (should be 0..", MAX_DEV - 1, ')');
120
		}
121
		if (dev[id]) {
122
			throw MSXException("Duplicate SCSI id: ", id);
123
		}
124
		DeviceConfig conf(config, *t);
125
		auto type = t->getChildData("type");
126
		if (type == "SCSIHD") {
127
			dev[id] = std::make_unique<SCSIHD>(conf, buffer,
128
			        SCSIDevice::MODE_SCSI1 | SCSIDevice::MODE_UNITATTENTION |
129
			        SCSIDevice::MODE_NOVAXIS);
130
		} else if (type == "SCSILS120") {
131
			dev[id] = std::make_unique<SCSILS120>(conf, buffer,
132
			        SCSIDevice::MODE_SCSI1 | SCSIDevice::MODE_UNITATTENTION |
133
			        SCSIDevice::MODE_NOVAXIS);
134
		} else {
135
			throw MSXException("Unknown SCSI device: ", type);
136
		}
137
	}
138
	// fill remaining targets with dummy SCSI devices to prevent crashes
139
	for (auto& d : dev) {
140
		if (!d) d = std::make_unique<DummySCSIDevice>();
141
	}
142
	reset(false);
143
144
	// avoid UMR on savestate
145
	std::ranges::fill(buffer, 0);
146
}
147
148
void WD33C93::disconnect()
149
{
150
	if (phase != SCSI::Phase::BUS_FREE) {
151
		assert(targetId < MAX_DEV);
152
		dev[targetId]->disconnect();
153
		if (regs[REG_SCSI_STATUS] != SS_XFER_END) {
154
			regs[REG_SCSI_STATUS] = SS_DISCONNECT;
155
		}
156
		regs[REG_AUX_STATUS] = AS_INT;
157
		phase = SCSI::Phase::BUS_FREE;
158
	}
159
	tc = 0;
160
}
161
162
void WD33C93::execCmd(uint8_t value)
163
{
164
	if (regs[REG_AUX_STATUS] & AS_CIP) {
165
		// CIP error
166
		return;
167
	}
168
	//regs[REG_AUX_STATUS] |= AS_CIP;
169
	regs[REG_CMD] = value;
170
171
	bool atn = false;
172
	switch (value) {
173
	case 0x00: // Reset controller (software reset)
174
		std::ranges::fill(subspan<0x1a>(regs, 1), 0);
175
		disconnect();
176
		latch = 0; // TODO: is this correct? Some doc says: reset to zero by master-reset-signal but not by reset command.
177
		regs[REG_SCSI_STATUS] =
178
			(regs[REG_OWN_ID] & OWN_EAF) ? SS_RESET_ADV : SS_RESET;
179
		break;
180
181
	case 0x02: // Assert ATN
182
		break;
183
184
	case 0x04: // Disconnect
185
		disconnect();
186
		break;
187
188
	case 0x06: // Select with ATN (Lv2)
189
		//atn = true; // never read
190
		[[fallthrough]];
191
	case 0x07: // Select Without ATN (Lv2)
192
		targetId = regs[REG_DST_ID] & 7;
193
		regs[REG_SCSI_STATUS] = SS_SEL_TIMEOUT;
194
		tc = 0;
195
		regs[REG_AUX_STATUS] = AS_INT;
196
		break;
197
198
	case 0x08: // Select with ATN and transfer (Lv2)
199
		atn = true;
200
		[[fallthrough]];
201
	case 0x09: // Select without ATN and Transfer (Lv2)
202
		targetId = regs[REG_DST_ID] & 7;
203
204
		if (!devBusy && targetId < MAX_DEV && /* targetId != myId  && */
205
		    dev[targetId]->isSelected()) {
206
			if (atn) {
207
				dev[targetId]->msgOut(regs[REG_TLUN] | 0x80);
208
			}
209
			devBusy = true;
210
			counter = dev[targetId]->executeCmd(
211
				subspan<12>(regs, REG_CDB1), phase, blockCounter);
212
213
			switch (phase) {
214
			case SCSI::Phase::STATUS:
215
				devBusy = false;
216
				regs[REG_TLUN] = dev[targetId]->getStatusCode();
217
				dev[targetId]->msgIn();
218
				regs[REG_SCSI_STATUS] = SS_XFER_END;
219
				disconnect();
220
				break;
221
222
			case SCSI::Phase::EXECUTE:
223
				regs[REG_AUX_STATUS] = AS_CIP | AS_BSY;
224
				bufIdx = 0;
225
				break;
226
227
			default:
228
				devBusy = false;
229
				regs[REG_AUX_STATUS] = AS_CIP | AS_BSY | AS_DBR;
230
				bufIdx = 0;
231
			}
232
			//regs[REG_SRC_ID] |= regs[REG_DST_ID] & 7;
233
		} else {
234
			// timeout
235
			tc = 0;
236
			regs[REG_SCSI_STATUS] = SS_SEL_TIMEOUT;
237
			regs[REG_AUX_STATUS]  = AS_INT;
238
		}
239
		break;
240
241
	case 0x18: // Translate Address (Lv2)
242
	default:
243
		// unsupported command
244
		break;
245
	}
246
}
247
248
void WD33C93::writeAdr(uint8_t value)
249
{
250
	latch = value & 0x1f;
251
}
252
253
// Latch incremented by one each time a register is accessed,
254
// except for the address-, aux.status-, data- and command registers.
255
void WD33C93::writeCtrl(uint8_t value)
256
{
257
	switch (latch) {
258
	case REG_OWN_ID:
259
		regs[REG_OWN_ID] = value;
260
		myId = value & 7;
261
		break;
262
263
	case REG_TCH:
264
		tc = (tc & 0x00ffff) + (value << 16);
265
		break;
266
267
	case REG_TCM:
268
		tc = (tc & 0xff00ff) + (value <<  8);
269
		break;
270
271
	case REG_TCL:
272
		tc = (tc & 0xffff00) + (value <<  0);
273
		break;
274
275
	case REG_CMD_PHASE:
276
		regs[REG_CMD_PHASE] = value;
277
		break;
278
279
	case REG_CMD:
280
		execCmd(value);
281
		return; // no latch-inc for address-, aux.status-, data- and command regs.
282
283
	case REG_DATA:
284
		regs[REG_DATA] = value;
285
		if (phase == SCSI::Phase::DATA_OUT) {
286
			buffer[bufIdx++] = value;
287
			--tc;
288
			if (--counter == 0) {
289
				counter = dev[targetId]->dataOut(blockCounter);
290
				if (counter) {
291
					bufIdx = 0;
292
					return;
293
				}
294
				regs[REG_TLUN] = dev[targetId]->getStatusCode();
295
				dev[targetId]->msgIn();
296
				regs[REG_SCSI_STATUS] = SS_XFER_END;
297
				disconnect();
298
			}
299
		}
300
		return; // no latch-inc for address-, aux.status-, data- and command regs.
301
302
	case REG_AUX_STATUS:
303
		return; // no latch-inc for address-, aux.status-, data- and command regs.
304
305
	default:
306
		if (latch <= REG_SRC_ID) {
307
			regs[latch] = value;
308
		}
309
		break;
310
	}
311
	latch = (latch + 1) & 0x1f;
312
}
313
314
uint8_t WD33C93::readAuxStatus()
315
{
316
	uint8_t rv = regs[REG_AUX_STATUS];
317
318
	if (phase == SCSI::Phase::EXECUTE) {
319
		counter = dev[targetId]->executingCmd(phase, blockCounter);
320
321
		switch (phase) {
322
		case SCSI::Phase::STATUS: // TODO how can this ever be the case?
323
			regs[REG_TLUN] = dev[targetId]->getStatusCode();
324
			dev[targetId]->msgIn();
325
			regs[REG_SCSI_STATUS] = SS_XFER_END;
326
			disconnect();
327
			break;
328
329
		case SCSI::Phase::EXECUTE:
330
			break;
331
332
		default:
333
			regs[REG_AUX_STATUS] |= AS_DBR;
334
		}
335
	}
336
	return rv;
337
}
338
339
// Latch incremented by one each time a register is accessed,
340
// except for the address-, aux.status-, data- and command registers.
341
uint8_t WD33C93::readCtrl()
342
{
343
	uint8_t rv;
344
	switch (latch) {
345
	case REG_SCSI_STATUS:
346
		rv = regs[REG_SCSI_STATUS];
347
		if (rv != SS_XFER_END) {
348
			regs[REG_AUX_STATUS] &= ~AS_INT;
349
		} else {
350
			regs[REG_SCSI_STATUS] = SS_DISCONNECT;
351
			regs[REG_AUX_STATUS]  = AS_INT;
352
		}
353
		break;
354
355
	case REG_CMD:
356
		return regs[REG_CMD]; // no latch-inc for address-, aux.status-, data- and command regs.
357
358
	case REG_DATA:
359
		if (phase == SCSI::Phase::DATA_IN) {
360
			rv = buffer[bufIdx++];
361
			regs[REG_DATA] = rv;
362
			--tc;
363
			if (--counter == 0) {
364
				if (blockCounter > 0) {
365
					counter = dev[targetId]->dataIn(blockCounter);
366
					if (counter) {
367
						bufIdx = 0;
368
						return rv;
369
					}
370
				}
371
				regs[REG_TLUN] = dev[targetId]->getStatusCode();
372
				dev[targetId]->msgIn();
373
				regs[REG_SCSI_STATUS] = SS_XFER_END;
374
				disconnect();
375
			}
376
		} else {
377
			rv = regs[REG_DATA];
378
		}
379
		return rv; // no latch-inc for address-, aux.status-, data- and command regs.
380
381
	case REG_TCH:
382
		rv = narrow_cast<uint8_t>((tc >> 16) & 0xff);
383
		break;
384
385
	case REG_TCM:
386
		rv = narrow_cast<uint8_t>((tc >>  8) & 0xff);
387
		break;
388
389
	case REG_TCL:
390
		rv = narrow_cast<uint8_t>((tc >>  0) & 0xff);
391
		break;
392
393
	case REG_AUX_STATUS:
394
		return readAuxStatus(); // no latch-inc for address-, aux.status-, data- and command regs.
395
396
	default:
397
		rv = regs[latch];
398
		break;
399
	}
400
401
	latch = (latch + 1) & 0x1f;
402
	return rv;
403
}
404
405
uint8_t WD33C93::peekAuxStatus() const
406
{
407
	return regs[REG_AUX_STATUS];
408
}
409
410
uint8_t WD33C93::peekCtrl() const
411
{
412
	switch (latch) {
413
	case REG_TCH:
414
		return narrow_cast<uint8_t>((tc >> 16) & 0xff);
415
	case REG_TCM:
416
		return narrow_cast<uint8_t>((tc >>  8) & 0xff);
417
	case REG_TCL:
418
		return narrow_cast<uint8_t>((tc >>  0) & 0xff);
419
	default:
420
		return regs[latch];
421
	}
422
}
423
424
void WD33C93::reset(bool scsiReset)
425
{
426
	// initialized register
427
	std::ranges::fill(subspan<0x1b>(regs, 0x00), 0x00);
428
	std::ranges::fill(subspan<0x04>(regs, 0x1b), 0xff);
429
	regs[REG_AUX_STATUS] = AS_INT;
430
	myId  = 0;
431
	latch = 0;
432
	tc    = 0;
433
	phase = SCSI::Phase::BUS_FREE;
434
	bufIdx  = 0;
435
	if (scsiReset) {
436
		for (const auto& d : dev) {
437
			d->reset();
438
		}
439
	}
440
}
441
442
443
static constexpr auto phaseInfo = std::to_array<enum_string<SCSI::Phase>>({
444
	{ "UNDEFINED",   SCSI::Phase::UNDEFINED   },
445
	{ "BUS_FREE",    SCSI::Phase::BUS_FREE    },
446
	{ "ARBITRATION", SCSI::Phase::ARBITRATION },
447
	{ "SELECTION",   SCSI::Phase::SELECTION   },
448
	{ "RESELECTION", SCSI::Phase::RESELECTION },
449
	{ "COMMAND",     SCSI::Phase::COMMAND     },
450
	{ "EXECUTE",     SCSI::Phase::EXECUTE     },
451
	{ "DATA_IN",     SCSI::Phase::DATA_IN     },
452
	{ "DATA_OUT",    SCSI::Phase::DATA_OUT    },
453
	{ "STATUS",      SCSI::Phase::STATUS      },
454
	{ "MSG_OUT",     SCSI::Phase::MSG_OUT     },
455
	{ "MSG_IN",      SCSI::Phase::MSG_IN      },
456
});
457
SERIALIZE_ENUM(SCSI::Phase, phaseInfo);
458
459
template<typename Archive>
460
void WD33C93::serialize(Archive& ar, unsigned /*version*/)
461
{
462
	ar.serialize_blob("buffer", buffer);
463
	std::array<char, 8> tag = {'d', 'e', 'v', 'i', 'c', 'e', 'X', 0};
464
	for (auto [i, d] : enumerate(dev)) {
465
		tag[6] = char('0' + i);
466
		ar.serializePolymorphic(tag.data(), *d);
467
	}
468
	ar.serialize("bufIdx",       bufIdx,
469
	             "counter",      counter,
470
	             "blockCounter", blockCounter,
471
	             "tc",           tc,
472
	             "phase",        phase,
473
	             "myId",         myId,
474
	             "targetId",     targetId);
475
	ar.serialize_blob("registers", regs);
476
	ar.serialize("latch",   latch,
477
	             "devBusy", devBusy);
478
}
479
INSTANTIATE_SERIALIZE_METHODS(WD33C93);
480
481
/* Here is some info on the parameters for SCSI devices:
482
static SCSIDevice* wd33c93ScsiDevCreate(WD33C93* wd33c93, int id)
483
{
484
#if 1
485
	// CD_UPDATE: Use dynamic parameters instead of hard coded ones
486
	int diskId, mode, type;
487
488
	diskId = diskGetHdDriveId(hdId, id);
489
	if (diskIsCdrom(diskId)) {
490
		mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_REMOVABLE | MODE_NOVAXIS;
491
		type = SDT_CDROM;
492
	} else {
493
		mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_FDS120 | MODE_REMOVABLE | MODE_NOVAXIS;
494
		type = SDT_DirectAccess;
495
	}
496
	return scsiDeviceCreate(id, diskId, buffer, nullptr, type, mode,
497
	                       (CdromXferCompCb)wd33c93XferCb, wd33c93);
498
#else
499
	SCSIDEVICE* dev;
500
	int mode;
501
	int type;
502
503
	if (id != 2) {
504
		mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_FDS120 | MODE_REMOVABLE | MODE_NOVAXIS;
505
		type = SDT_DirectAccess;
506
	} else {
507
		mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_REMOVABLE | MODE_NOVAXIS;
508
		type = SDT_CDROM;
509
	}
510
	dev = scsiDeviceCreate(id, diskGetHdDriveId(hdId, id),
511
	        buffer, nullptr, type, mode, (CdromXferCompCb)wd33c93XferCb, wd33c93);
512
	return dev;
513
#endif
514
}
515
*/
516
517
} // namespace openmsx