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