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