openMSX
I8251.cc
Go to the documentation of this file.
1 #include "I8251.hh"
2 #include "serialize.hh"
3 #include "unreachable.hh"
4 #include <cassert>
5 
6 using std::string;
7 
8 namespace openmsx {
9 
10 static const byte STAT_TXRDY = 0x01;
11 static const byte STAT_RXRDY = 0x02;
12 static const byte STAT_TXEMPTY = 0x04;
13 static const byte STAT_PE = 0x08;
14 static const byte STAT_OE = 0x10;
15 static const byte STAT_FE = 0x20;
16 static const byte STAT_SYNBRK = 0x40;
17 static const byte STAT_DSR = 0x80;
18 
19 static const byte MODE_BAUDRATE = 0x03;
20 static const byte MODE_SYNCHRONOUS = 0x00;
21 static const byte MODE_RATE1 = 0x01;
22 static const byte MODE_RATE16 = 0x02;
23 static const byte MODE_RATE64 = 0x03;
24 static const byte MODE_WORDLENGTH = 0x0C;
25 static const byte MODE_5BIT = 0x00;
26 static const byte MODE_6BIT = 0x04;
27 static const byte MODE_7BIT = 0x08;
28 static const byte MODE_8BIT = 0x0C;
29 static const byte MODE_PARITYEN = 0x10;
30 static const byte MODE_PARITODD = 0x00;
31 static const byte MODE_PARITEVEN = 0x20;
32 static const byte MODE_STOP_BITS = 0xC0;
33 static const byte MODE_STOP_INV = 0x00;
34 static const byte MODE_STOP_1 = 0x40;
35 static const byte MODE_STOP_15 = 0x80;
36 static const byte MODE_STOP_2 = 0xC0;
37 static const byte MODE_SINGLESYNC = 0x80;
38 
39 static const byte CMD_TXEN = 0x01;
40 static const byte CMD_DTR = 0x02;
41 static const byte CMD_RXE = 0x04;
42 static const byte CMD_SBRK = 0x08;
43 static const byte CMD_RSTERR = 0x10;
44 static const byte CMD_RTS = 0x20;
45 static const byte CMD_RESET = 0x40;
46 static const byte CMD_HUNT = 0x80;
47 
48 
49 I8251::I8251(Scheduler& scheduler, I8251Interface& interf_, EmuTime::param time)
50  : syncRecv (scheduler)
51  , syncTrans(scheduler)
52  , interf(interf_), clock(scheduler)
53 {
54  reset(time);
55 }
56 
57 void I8251::reset(EmuTime::param time)
58 {
59  // initialize these to avoid UMR on savestate
60  // TODO investigate correct initial state after reset
61  charLength = 0;
62  recvDataBits = SerialDataInterface::DATA_8;
63  recvStopBits = SerialDataInterface::STOP_1;
64  recvParityBit = SerialDataInterface::EVEN;
65  recvParityEnabled = false;
66  recvBuf = 0;
67  recvReady = false;
68  sendByte = 0;
69  sendBuffer = 0;
70  mode = 0;
71  sync1 = sync2 = 0;
72 
73  status = STAT_TXRDY | STAT_TXEMPTY;
74  command = 0xFF; // make sure all bits change
75  writeCommand(0, time);
76  cmdFaze = FAZE_MODE;
77 }
78 
79 byte I8251::readIO(word port, EmuTime::param time)
80 {
81  byte result;
82  switch (port & 1) {
83  case 0:
84  result = readTrans(time);
85  break;
86  case 1:
87  result = readStatus(time);
88  break;
89  default:
90  UNREACHABLE; return 0;
91  }
92  return result;
93 }
94 
95 byte I8251::peekIO(word port, EmuTime::param /*time*/) const
96 {
97  switch (port & 1) {
98  case 0:
99  return recvBuf;
100  case 1:
101  return status; // TODO peekStatus()
102  default:
103  UNREACHABLE; return 0;
104  }
105 }
106 
107 
108 void I8251::writeIO(word port, byte value, EmuTime::param time)
109 {
110  switch (port & 1) {
111  case 0:
112  writeTrans(value, time);
113  break;
114  case 1:
115  switch (cmdFaze) {
116  case FAZE_MODE:
117  setMode(value);
118  if ((mode & MODE_BAUDRATE) == MODE_SYNCHRONOUS) {
119  cmdFaze = FAZE_SYNC1;
120  } else {
121  cmdFaze = FAZE_CMD;
122  }
123  break;
124  case FAZE_SYNC1:
125  sync1 = value;
126  if (mode & MODE_SINGLESYNC) {
127  cmdFaze = FAZE_CMD;
128  } else {
129  cmdFaze = FAZE_SYNC2;
130  }
131  break;
132  case FAZE_SYNC2:
133  sync2 = value;
134  cmdFaze = FAZE_CMD;
135  break;
136  case FAZE_CMD:
137  if (value & CMD_RESET) {
138  cmdFaze = FAZE_MODE;
139  } else {
140  writeCommand(value, time);
141  }
142  break;
143  default:
144  UNREACHABLE;
145  }
146  break;
147  default:
148  UNREACHABLE;
149  }
150 }
151 
152 void I8251::setMode(byte newMode)
153 {
154  mode = newMode;
155 
157  switch (mode & MODE_WORDLENGTH) {
158  case MODE_5BIT:
159  dataBits = SerialDataInterface::DATA_5;
160  break;
161  case MODE_6BIT:
162  dataBits = SerialDataInterface::DATA_6;
163  break;
164  case MODE_7BIT:
165  dataBits = SerialDataInterface::DATA_7;
166  break;
167  case MODE_8BIT:
168  dataBits = SerialDataInterface::DATA_8;
169  break;
170  default:
171  UNREACHABLE;
172  dataBits = SerialDataInterface::DATA_8;
173  }
174  interf.setDataBits(dataBits);
175 
177  switch(mode & MODE_STOP_BITS) {
178  case MODE_STOP_INV:
180  break;
181  case MODE_STOP_1:
182  stopBits = SerialDataInterface::STOP_1;
183  break;
184  case MODE_STOP_15:
185  stopBits = SerialDataInterface::STOP_15;
186  break;
187  case MODE_STOP_2:
188  stopBits = SerialDataInterface::STOP_2;
189  break;
190  default:
191  UNREACHABLE;
192  stopBits = SerialDataInterface::STOP_2;
193  }
194  interf.setStopBits(stopBits);
195 
196  bool parityEnable = (mode & MODE_PARITYEN) != 0;
197  SerialDataInterface::ParityBit parity = (mode & MODE_PARITEVEN) ?
199  interf.setParityBit(parityEnable, parity);
200 
201  unsigned baudrate;
202  switch (mode & MODE_BAUDRATE) {
203  case MODE_SYNCHRONOUS:
204  baudrate = 1;
205  break;
206  case MODE_RATE1:
207  baudrate = 1;
208  break;
209  case MODE_RATE16:
210  baudrate = 16;
211  break;
212  case MODE_RATE64:
213  baudrate = 64;
214  break;
215  default:
216  UNREACHABLE;
217  baudrate = 1;
218  }
219 
220  charLength = (((2 * (1 + unsigned(dataBits) + (parityEnable ? 1 : 0))) +
221  unsigned(stopBits)) * baudrate) / 2;
222 }
223 
224 void I8251::writeCommand(byte value, EmuTime::param time)
225 {
226  byte oldCommand = command;
227  command = value;
228 
229  // CMD_RESET, CMD_TXEN, CMD_RXE handled in other routines
230 
231  interf.setRTS((command & CMD_RTS) != 0, time);
232  interf.setDTR((command & CMD_DTR) != 0, time);
233 
234  if (!(command & CMD_TXEN)) {
235  // disable transmitter
237  status |= STAT_TXRDY | STAT_TXEMPTY;
238  }
239  if (command & CMD_RSTERR) {
240  status &= ~(STAT_PE | STAT_OE | STAT_FE);
241  }
242  if (command & CMD_SBRK) {
243  // TODO
244  }
245  if (command & CMD_HUNT) {
246  // TODO
247  }
248 
249  if ((command ^ oldCommand) & CMD_RXE) {
250  if (command & CMD_RXE) {
251  // enable receiver
252  status &= ~(STAT_PE | STAT_OE | STAT_FE); // TODO
253  recvReady = true;
254  } else {
255  // disable receiver
257  status &= ~(STAT_PE | STAT_OE | STAT_FE); // TODO
258  status &= ~STAT_RXRDY;
259  }
260  interf.signal(time);
261  }
262 }
263 
264 byte I8251::readStatus(EmuTime::param time)
265 {
266  byte result = status;
267  if (interf.getDSR(time)) {
268  result |= STAT_DSR;
269  }
270  return result;
271 }
272 
273 byte I8251::readTrans(EmuTime::param time)
274 {
275  status &= ~STAT_RXRDY;
276  interf.setRxRDY(false, time);
277  return recvBuf;
278 }
279 
280 void I8251::writeTrans(byte value, EmuTime::param time)
281 {
282  if (!(command & CMD_TXEN)) {
283  return;
284  }
285  if (status & STAT_TXEMPTY) {
286  // not sending
287  send(value, time);
288  } else {
289  sendBuffer = value;
290  status &= ~STAT_TXRDY;
291  }
292 }
293 
294 void I8251::setParityBit(bool enable, ParityBit parity)
295 {
296  recvParityEnabled = enable;
297  recvParityBit = parity;
298 }
299 
300 void I8251::recvByte(byte value, EmuTime::param time)
301 {
302  // TODO STAT_PE / STAT_FE / STAT_SYNBRK
303  assert(recvReady && (command & CMD_RXE));
304  if (status & STAT_RXRDY) {
305  status |= STAT_OE;
306  } else {
307  recvBuf = value;
308  status |= STAT_RXRDY;
309  interf.setRxRDY(true, time);
310  }
311  recvReady = false;
312  if (clock.isPeriodic()) {
313  EmuTime next = time + (clock.getTotalDuration() * charLength);
314  syncRecv.setSyncPoint(next);
315  }
316 }
317 
319 {
320  return (command & CMD_RXE) != 0;
321 }
322 
323 void I8251::send(byte value, EmuTime::param time)
324 {
325  status &= ~STAT_TXEMPTY;
326  sendByte = value;
327  if (clock.isPeriodic()) {
328  EmuTime next = time + (clock.getTotalDuration() * charLength);
329  syncTrans.setSyncPoint(next);
330  }
331 }
332 
333 void I8251::execRecv(EmuTime::param time)
334 {
335  assert(command & CMD_RXE);
336  recvReady = true;
337  interf.signal(time);
338 }
339 
340 void I8251::execTrans(EmuTime::param time)
341 {
342  assert(!(status & STAT_TXEMPTY) && (command & CMD_TXEN));
343 
344  interf.recvByte(sendByte, time);
345  if (status & STAT_TXRDY) {
346  status |= STAT_TXEMPTY;
347  } else {
348  status |= STAT_TXRDY;
349  send(sendBuffer, time);
350  }
351 }
352 
353 
354 static std::initializer_list<enum_string<SerialDataInterface::DataBits>> dataBitsInfo = {
359 };
361 
362 static std::initializer_list<enum_string<SerialDataInterface::StopBits>> stopBitsInfo = {
363  { "INVALID", SerialDataInterface::STOP_INV },
365  { "1.5", SerialDataInterface::STOP_15 },
367 };
369 
370 static std::initializer_list<enum_string<SerialDataInterface::ParityBit>> parityBitInfo = {
371  { "EVEN", SerialDataInterface::EVEN },
372  { "ODD", SerialDataInterface::ODD }
373 };
375 
376 static std::initializer_list<enum_string<I8251::CmdFaze>> cmdFazeInfo = {
377  { "MODE", I8251::FAZE_MODE },
378  { "SYNC1", I8251::FAZE_SYNC1 },
379  { "SYNC2", I8251::FAZE_SYNC2 },
380  { "CMD", I8251::FAZE_CMD }
381 };
382 SERIALIZE_ENUM(I8251::CmdFaze, cmdFazeInfo);
383 
384 // version 1: initial version
385 // version 2: removed 'userData' from Schedulable
386 template<typename Archive>
387 void I8251::serialize(Archive& ar, unsigned version)
388 {
389  if (ar.versionAtLeast(version, 2)) {
390  ar.serialize("syncRecv", syncRecv);
391  ar.serialize("syncTrans", syncTrans);
392  } else {
394  }
395  ar.serialize("clock", clock);
396  ar.serialize("charLength", charLength);
397  ar.serialize("recvDataBits", recvDataBits);
398  ar.serialize("recvStopBits", recvStopBits);
399  ar.serialize("recvParityBit", recvParityBit);
400  ar.serialize("recvParityEnabled", recvParityEnabled);
401  ar.serialize("recvBuf", recvBuf);
402  ar.serialize("recvReady", recvReady);
403  ar.serialize("sendByte", sendByte);
404  ar.serialize("sendBuffer", sendBuffer);
405  ar.serialize("status", status);
406  ar.serialize("command", command);
407  ar.serialize("mode", mode);
408  ar.serialize("sync1", sync1);
409  ar.serialize("sync2", sync2);
410  ar.serialize("cmdFaze", cmdFaze);
411 }
413 
414 } // namespace openmsx
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
virtual void setParityBit(bool enable, ParityBit parity)=0
virtual void signal(EmuTime::param time)=0
bool isRecvEnabled()
Definition: I8251.cc:318
byte peekIO(word port, EmuTime::param time) const
Definition: I8251.cc:95
virtual void setDataBits(DataBits bits)=0
void setParityBit(bool enable, ParityBit parity) override
Definition: I8251.cc:294
I8251(Scheduler &scheduler, I8251Interface &interf, EmuTime::param time)
Definition: I8251.cc:49
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
EmuDuration::param getTotalDuration() const
Definition: ClockPin.cc:67
virtual void recvByte(byte value, EmuTime::param time)=0
void serialize(Archive &ar, unsigned version)
Definition: I8251.cc:387
virtual void setDTR(bool status, EmuTime::param time)=0
static void restoreOld(Archive &ar, std::vector< Schedulable *> schedulables)
Definition: Schedulable.hh:77
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8251.cc:108
virtual void setRTS(bool status, EmuTime::param time)=0
bool isPeriodic() const
Definition: ClockPin.hh:34
byte readIO(word port, EmuTime::param time)
Definition: I8251.cc:79
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
openmsx::I8251::SyncTrans syncTrans
void reset(EmuTime::param time)
Definition: I8251.cc:57
void execTrans(EmuTime::param time)
Definition: I8251.cc:340
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
virtual void setStopBits(StopBits bits)=0
openmsx::I8251::SyncRecv syncRecv
void execRecv(EmuTime::param time)
Definition: I8251.cc:333
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
void recvByte(byte value, EmuTime::param time) override
Definition: I8251.cc:300
virtual void setRxRDY(bool status, EmuTime::param time)=0
virtual bool getDSR(EmuTime::param time)=0
#define UNREACHABLE
Definition: unreachable.hh:38