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