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