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)
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 
156  SerialDataInterface::DataBits dataBits;
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 
176  SerialDataInterface::StopBits stopBits;
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
236  syncTrans.removeSyncPoint();
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
256  syncRecv.removeSyncPoint();
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 
318 bool I8251::isRecvEnabled()
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 = {
355  { "5", SerialDataInterface::DATA_5 },
356  { "6", SerialDataInterface::DATA_6 },
357  { "7", SerialDataInterface::DATA_7 },
358  { "8", SerialDataInterface::DATA_8 }
359 };
360 SERIALIZE_ENUM(SerialDataInterface::DataBits, dataBitsInfo);
361 
362 static std::initializer_list<enum_string<SerialDataInterface::StopBits>> stopBitsInfo = {
363  { "INVALID", SerialDataInterface::STOP_INV },
364  { "1", SerialDataInterface::STOP_1 },
365  { "1.5", SerialDataInterface::STOP_15 },
366  { "2", SerialDataInterface::STOP_2 }
367 };
368 SERIALIZE_ENUM(SerialDataInterface::StopBits, stopBitsInfo);
369 
370 static std::initializer_list<enum_string<SerialDataInterface::ParityBit>> parityBitInfo = {
371  { "EVEN", SerialDataInterface::EVEN },
372  { "ODD", SerialDataInterface::ODD }
373 };
374 SERIALIZE_ENUM(SerialDataInterface::ParityBit, parityBitInfo);
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  "syncTrans", syncTrans);
392  } else {
393  Schedulable::restoreOld(ar, {&syncRecv, &syncTrans});
394  }
395  ar.serialize("clock", clock,
396  "charLength", charLength,
397  "recvDataBits", recvDataBits,
398  "recvStopBits", recvStopBits,
399  "recvParityBit", recvParityBit,
400  "recvParityEnabled", recvParityEnabled,
401  "recvBuf", recvBuf,
402  "recvReady", recvReady,
403  "sendByte", sendByte,
404  "sendBuffer", sendBuffer,
405  "status", status,
406  "command", command,
407  "mode", mode,
408  "sync1", sync1,
409  "sync2", sync2,
410  "cmdFaze", cmdFaze);
411 }
413 
414 } // namespace openmsx
bool getEnum() const noexcept
Definition: EnumSetting.hh:96
virtual void setParityBit(bool enable, ParityBit parity)=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
void serialize(Archive &ar, unsigned version)
Definition: I8251.cc:387
#define SERIALIZE_ENUM(TYPE, INFO)
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8251.cc:108
byte readIO(word port, EmuTime::param time)
Definition: I8251.cc:79
void reset(EmuTime::param time)
Definition: I8251.cc:57
void execTrans(EmuTime::param time)
Definition: I8251.cc:340
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
virtual void setStopBits(StopBits bits)=0
void execRecv(EmuTime::param time)
Definition: I8251.cc:333
void recvByte(byte value, EmuTime::param time) override
Definition: I8251.cc:300
#define UNREACHABLE
Definition: unreachable.hh:38