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 <cassert>
25 #include <cstring>
26 #include <memory>
27 
28 namespace openmsx {
29 
30 constexpr unsigned MAX_DEV = 8;
31 
32 constexpr byte REG_OWN_ID = 0x00;
33 constexpr byte REG_CONTROL = 0x01;
34 constexpr byte REG_TIMEO = 0x02;
35 constexpr byte REG_TSECS = 0x03;
36 constexpr byte REG_THEADS = 0x04;
37 constexpr byte REG_TCYL_HI = 0x05;
38 constexpr byte REG_TCYL_LO = 0x06;
39 constexpr byte REG_ADDR_HI = 0x07;
40 constexpr byte REG_ADDR_2 = 0x08;
41 constexpr byte REG_ADDR_3 = 0x09;
42 constexpr byte REG_ADDR_LO = 0x0a;
43 constexpr byte REG_SECNO = 0x0b;
44 constexpr byte REG_HEADNO = 0x0c;
45 constexpr byte REG_CYLNO_HI = 0x0d;
46 constexpr byte REG_CYLNO_LO = 0x0e;
47 constexpr byte REG_TLUN = 0x0f;
48 constexpr byte REG_CMD_PHASE = 0x10;
49 constexpr byte REG_SYN = 0x11;
50 constexpr byte REG_TCH = 0x12;
51 constexpr byte REG_TCM = 0x13;
52 constexpr byte REG_TCL = 0x14;
53 constexpr byte REG_DST_ID = 0x15;
54 constexpr byte REG_SRC_ID = 0x16;
55 constexpr byte REG_SCSI_STATUS = 0x17; // (r)
56 constexpr byte REG_CMD = 0x18;
57 constexpr byte REG_DATA = 0x19;
58 constexpr byte REG_QUEUE_TAG = 0x1a;
59 constexpr byte REG_AUX_STATUS = 0x1f; // (r)
60 
61 constexpr byte REG_CDBSIZE = 0x00;
62 constexpr byte REG_CDB1 = 0x03;
63 constexpr byte REG_CDB2 = 0x04;
64 constexpr byte REG_CDB3 = 0x05;
65 constexpr byte REG_CDB4 = 0x06;
66 constexpr byte REG_CDB5 = 0x07;
67 constexpr byte REG_CDB6 = 0x08;
68 constexpr byte REG_CDB7 = 0x09;
69 constexpr byte REG_CDB8 = 0x0a;
70 constexpr byte REG_CDB9 = 0x0b;
71 constexpr byte REG_CDB10 = 0x0c;
72 constexpr byte REG_CDB11 = 0x0d;
73 constexpr byte REG_CDB12 = 0x0e;
74 
75 constexpr byte OWN_EAF = 0x08; // ENABLE ADVANCED FEATURES
76 
77 // SCSI STATUS
78 constexpr byte SS_RESET = 0x00; // reset
79 constexpr byte SS_RESET_ADV = 0x01; // reset w/adv. features
80 constexpr byte SS_XFER_END = 0x16; // select and transfer complete
81 constexpr byte SS_SEL_TIMEOUT = 0x42; // selection timeout
82 constexpr byte SS_DISCONNECT = 0x85;
83 
84 // AUX STATUS
85 constexpr byte AS_DBR = 0x01; // data buffer ready
86 constexpr byte AS_CIP = 0x10; // command in progress, chip is busy
87 constexpr byte AS_BSY = 0x20; // Level 2 command in progress
88 constexpr byte AS_LCI = 0x40; // last command ignored
89 constexpr byte AS_INT = 0x80;
90 
91 /* command phase
92 0x00 NO_SELECT
93 0x10 SELECTED
94 0x20 IDENTIFY_SENT
95 0x30 COMMAND_OUT
96 0x41 SAVE_DATA_RECEIVED
97 0x42 DISCONNECT_RECEIVED
98 0x43 LEGAL_DISCONNECT
99 0x44 RESELECTED
100 0x45 IDENTIFY_RECEIVED
101 0x46 DATA_TRANSFER_DONE
102 0x47 STATUS_STARTED
103 0x50 STATUS_RECEIVED
104 0x60 COMPLETE_RECEIVED
105 */
106 
108 {
109  devBusy = false;
110 
111  for (const auto* t : config.getXML()->getChildren("target")) {
112  unsigned id = t->getAttributeAsInt("id");
113  if (id >= MAX_DEV) {
114  throw MSXException("Invalid SCSI id: ", id,
115  " (should be 0..", MAX_DEV - 1, ')');
116  }
117  if (dev[id]) {
118  throw MSXException("Duplicate SCSI id: ", id);
119  }
120  DeviceConfig conf(config, *t);
121  const auto& type = t->getChild("type").getData();
122  if (type == "SCSIHD") {
123  dev[id] = std::make_unique<SCSIHD>(conf, buffer,
126  } else if (type == "SCSILS120") {
127  dev[id] = std::make_unique<SCSILS120>(conf, buffer,
130  } else {
131  throw MSXException("Unknown SCSI device: ", type);
132  }
133  }
134  // fill remaining targets with dummy SCSI devices to prevent crashes
135  for (auto& d : dev) {
136  if (!d) d = std::make_unique<DummySCSIDevice>();
137  }
138  reset(false);
139 
140  // avoid UMR on savestate
141  memset(buffer.data(), 0, SCSIDevice::BUFFER_SIZE);
142  counter = 0;
143  blockCounter = 0;
144  targetId = 0;
145 }
146 
147 void WD33C93::disconnect()
148 {
149  if (phase != SCSI::BUS_FREE) {
150  assert(targetId < MAX_DEV);
151  dev[targetId]->disconnect();
152  if (regs[REG_SCSI_STATUS] != SS_XFER_END) {
154  }
155  regs[REG_AUX_STATUS] = AS_INT;
156  phase = SCSI::BUS_FREE;
157  }
158  tc = 0;
159 }
160 
161 void WD33C93::execCmd(byte 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  memset(regs + 1, 0, 0x1a);
174  disconnect();
175  latch = 0; // TODO: is this correct? Some doc says: reset to zero by masterreset-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;
189  [[fallthrough]];
190  case 0x07: // Select Without ATN (Lv2)
191  targetId = regs[REG_DST_ID] & 7;
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  &regs[REG_CDB1], phase, blockCounter);
211 
212  switch (phase) {
213  case SCSI::STATUS:
214  devBusy = false;
215  regs[REG_TLUN] = dev[targetId]->getStatusCode();
216  dev[targetId]->msgIn();
218  disconnect();
219  break;
220 
221  case SCSI::EXECUTE:
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;
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 
247 void WD33C93::writeAdr(byte 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.
254 void WD33C93::writeCtrl(byte 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::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();
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  byte rv = regs[REG_AUX_STATUS];
316 
317  if (phase == SCSI::EXECUTE) {
318  counter = dev[targetId]->executingCmd(phase, blockCounter);
319 
320  switch (phase) {
321  case SCSI::STATUS: // TODO how can this ever be the case?
322  regs[REG_TLUN] = dev[targetId]->getStatusCode();
323  dev[targetId]->msgIn();
325  disconnect();
326  break;
327 
328  case SCSI::EXECUTE:
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  byte 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 {
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::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();
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 = (tc >> 16) & 0xff;
382  break;
383 
384  case REG_TCM:
385  rv = (tc >> 8) & 0xff;
386  break;
387 
388  case REG_TCL:
389  rv = (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 
409 byte WD33C93::peekCtrl() const
410 {
411  switch (latch) {
412  case REG_TCH:
413  return (tc >> 16) & 0xff;
414  case REG_TCM:
415  return (tc >> 8) & 0xff;
416  case REG_TCL:
417  return (tc >> 0) & 0xff;
418  default:
419  return regs[latch];
420  }
421 }
422 
423 void WD33C93::reset(bool scsireset)
424 {
425  // initialized register
426  memset(regs, 0, 0x1b);
427  memset(regs + 0x1b, 0xff, 4);
428  regs[REG_AUX_STATUS] = AS_INT;
429  myId = 0;
430  latch = 0;
431  tc = 0;
432  phase = SCSI::BUS_FREE;
433  bufIdx = 0;
434  if (scsireset) {
435  for (auto& d : dev) {
436  d->reset();
437  }
438  }
439 }
440 
441 
442 static constexpr std::initializer_list<enum_string<SCSI::Phase>> phaseInfo = {
443  { "UNDEFINED", SCSI::UNDEFINED },
444  { "BUS_FREE", SCSI::BUS_FREE },
445  { "ARBITRATION", SCSI::ARBITRATION },
446  { "SELECTION", SCSI::SELECTION },
447  { "RESELECTION", SCSI::RESELECTION },
448  { "COMMAND", SCSI::COMMAND },
449  { "EXECUTE", SCSI::EXECUTE },
450  { "DATA_IN", SCSI::DATA_IN },
451  { "DATA_OUT", SCSI::DATA_OUT },
452  { "STATUS", SCSI::STATUS },
453  { "MSG_OUT", SCSI::MSG_OUT },
454  { "MSG_IN", SCSI::MSG_IN }
455 };
456 SERIALIZE_ENUM(SCSI::Phase, phaseInfo);
457 
458 template<typename Archive>
459 void WD33C93::serialize(Archive& ar, unsigned /*version*/)
460 {
461  ar.serialize_blob("buffer", buffer.data(), buffer.size());
462  char tag[8] = { '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, *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, sizeof(regs));
475  ar.serialize("latch", latch,
476  "devBusy", devBusy);
477 }
479 
480 /* Here is some info on the parameters for SCSI devices:
481 static 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
TclObject t
const XMLElement * getXML() const
Definition: DeviceConfig.hh:47
static constexpr unsigned BUFFER_SIZE
Definition: SCSIDevice.hh:23
static constexpr unsigned MODE_NOVAXIS
Definition: SCSIDevice.hh:21
static constexpr unsigned MODE_SCSI1
Definition: SCSIDevice.hh:15
static constexpr unsigned MODE_UNITATTENTION
Definition: SCSIDevice.hh:18
void serialize(Archive &ar, unsigned version)
Definition: WD33C93.cc:459
byte peekCtrl() const
Definition: WD33C93.cc:409
void reset(bool scsireset)
Definition: WD33C93.cc:423
byte peekAuxStatus() const
Definition: WD33C93.cc:404
byte readCtrl()
Definition: WD33C93.cc:340
WD33C93(const DeviceConfig &config)
Definition: WD33C93.cc:107
byte readAuxStatus()
Definition: WD33C93.cc:313
void writeCtrl(byte value)
Definition: WD33C93.cc:254
void writeAdr(byte value)
Definition: WD33C93.cc:247
const Children & getChildren() const
Definition: XMLElement.hh:105
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
constexpr byte AS_INT
Definition: WD33C93.cc:89
constexpr byte REG_CYLNO_LO
Definition: WD33C93.cc:46
constexpr byte REG_SRC_ID
Definition: WD33C93.cc:54
constexpr byte REG_CDB8
Definition: WD33C93.cc:69
constexpr byte REG_DST_ID
Definition: WD33C93.cc:53
constexpr byte REG_ADDR_3
Definition: WD33C93.cc:41
constexpr byte REG_TCH
Definition: MB89352.cc:50
constexpr byte SS_SEL_TIMEOUT
Definition: WD33C93.cc:81
constexpr byte SS_RESET_ADV
Definition: WD33C93.cc:79
constexpr byte REG_TCL
Definition: MB89352.cc:52
constexpr byte REG_ADDR_LO
Definition: WD33C93.cc:42
constexpr byte REG_TSECS
Definition: WD33C93.cc:35
constexpr byte REG_DATA
Definition: WD33C93.cc:57
constexpr byte OWN_EAF
Definition: WD33C93.cc:75
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
constexpr byte REG_CDB12
Definition: WD33C93.cc:73
constexpr byte REG_CDB5
Definition: WD33C93.cc:66
constexpr byte REG_CMD_PHASE
Definition: WD33C93.cc:48
constexpr byte REG_TCM
Definition: MB89352.cc:51
constexpr byte SS_DISCONNECT
Definition: WD33C93.cc:82
constexpr unsigned MAX_DEV
Definition: WD33C93.cc:30
constexpr byte REG_CDB3
Definition: WD33C93.cc:64
constexpr byte REG_CDB10
Definition: WD33C93.cc:71
constexpr byte REG_THEADS
Definition: WD33C93.cc:36
constexpr byte REG_SECNO
Definition: WD33C93.cc:43
constexpr byte REG_TLUN
Definition: WD33C93.cc:47
constexpr byte REG_ADDR_HI
Definition: WD33C93.cc:39
constexpr byte REG_TCYL_HI
Definition: WD33C93.cc:37
constexpr byte REG_CONTROL
Definition: WD33C93.cc:33
constexpr byte REG_CDB11
Definition: WD33C93.cc:72
constexpr byte REG_AUX_STATUS
Definition: WD33C93.cc:59
constexpr byte REG_CDBSIZE
Definition: WD33C93.cc:61
constexpr byte AS_LCI
Definition: WD33C93.cc:88
constexpr byte REG_CDB1
Definition: WD33C93.cc:62
constexpr byte AS_DBR
Definition: WD33C93.cc:85
constexpr byte REG_CDB7
Definition: WD33C93.cc:68
constexpr byte REG_ADDR_2
Definition: WD33C93.cc:40
constexpr byte REG_HEADNO
Definition: WD33C93.cc:44
constexpr byte REG_CDB4
Definition: WD33C93.cc:65
constexpr byte AS_BSY
Definition: WD33C93.cc:87
constexpr byte REG_CDB2
Definition: WD33C93.cc:63
constexpr byte REG_SYN
Definition: WD33C93.cc:49
constexpr byte SS_RESET
Definition: WD33C93.cc:78
constexpr byte REG_CMD
Definition: WD33C93.cc:56
constexpr byte SS_XFER_END
Definition: WD33C93.cc:80
constexpr byte REG_CDB6
Definition: WD33C93.cc:67
constexpr byte REG_TIMEO
Definition: WD33C93.cc:34
constexpr byte REG_TCYL_LO
Definition: WD33C93.cc:38
constexpr byte REG_CYLNO_HI
Definition: WD33C93.cc:45
constexpr byte AS_CIP
Definition: WD33C93.cc:86
constexpr byte REG_OWN_ID
Definition: WD33C93.cc:32
constexpr byte REG_SCSI_STATUS
Definition: WD33C93.cc:55
constexpr byte REG_CDB9
Definition: WD33C93.cc:70
constexpr byte REG_QUEUE_TAG
Definition: WD33C93.cc:58
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983