openMSX
MSXRS232.cc
Go to the documentation of this file.
1 #include "MSXRS232.hh"
2 #include "RS232Device.hh"
3 #include "CacheLine.hh"
4 #include "Ram.hh"
5 #include "Rom.hh"
6 #include "BooleanSetting.hh"
7 #include "MSXException.hh"
8 #include "serialize.hh"
9 #include "one_of.hh"
10 #include "outer.hh"
11 #include "unreachable.hh"
12 #include <cassert>
13 #include <memory>
14 
15 namespace openmsx {
16 
17 const unsigned RAM_OFFSET = 0x2000;
18 const unsigned RAM_SIZE = 0x800;
19 
21  : MSXDevice(config)
22  , RS232Connector(MSXDevice::getPluggingController(), "msx-rs232")
23  , i8254(getScheduler(), &cntr0, &cntr1, nullptr, getCurrentTime())
24  , i8251(getScheduler(), interf, getCurrentTime())
25  , rom(config.findChild("rom")
26  ? std::make_unique<Rom>(
27  MSXDevice::getName() + " ROM", "rom", config)
28  : nullptr) // when the ROM is already mapped, you don't want to specify it again here
29  , ram(config.getChildDataAsBool("ram", false)
30  ? std::make_unique<Ram>(
31  config, MSXDevice::getName() + " RAM",
32  "RS232 RAM", RAM_SIZE)
33  : nullptr)
34  , rxrdyIRQ(getMotherBoard(), MSXDevice::getName() + ".IRQrxrdy")
35  , rxrdyIRQlatch(false)
36  , rxrdyIRQenabled(false)
37  , hasMemoryBasedIo(config.getChildDataAsBool("memorybasedio", false))
38  , ioAccessEnabled(!hasMemoryBasedIo)
39  , switchSetting(config.getChildDataAsBool("toshiba_rs232c_switch",
40  false) ? std::make_unique<BooleanSetting>(getCommandController(),
41  "toshiba_rs232c_switch", "status of the RS-232C enable switch",
42  true) : nullptr)
43 {
44  if (rom && (rom->getSize() != one_of(0x2000u, 0x4000u))) {
45  throw MSXException("RS232C only supports 8kB or 16kB ROMs.");
46  }
47 
48  EmuDuration total(1.0 / 1.8432e6); // 1.8432MHz
49  EmuDuration hi (1.0 / 3.6864e6); // half clock period
50  EmuTime::param time = getCurrentTime();
51  i8254.getClockPin(0).setPeriodicState(total, hi, time);
52  i8254.getClockPin(1).setPeriodicState(total, hi, time);
53  i8254.getClockPin(2).setPeriodicState(total, hi, time);
54 
55  powerUp(time);
56 }
57 
58 MSXRS232::~MSXRS232() = default;
59 
60 void MSXRS232::powerUp(EmuTime::param time)
61 {
62  if (ram) ram->clear();
63  reset(time);
64 }
65 
66 void MSXRS232::reset(EmuTime::param /*time*/)
67 {
68  rxrdyIRQlatch = false;
69  rxrdyIRQenabled = false;
70  rxrdyIRQ.reset();
71 
72  ioAccessEnabled = !hasMemoryBasedIo;
73 
74  if (ram) ram->clear();
75 }
76 
77 byte MSXRS232::readMem(word address, EmuTime::param time)
78 {
79  if (hasMemoryBasedIo && (0xBFF8 <= address) && (address <= 0xBFFF)) {
80  return readIOImpl(address & 0x07, time);
81  }
82  word addr = address & 0x3FFF;
83  if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
84  return (*ram)[addr - RAM_OFFSET];
85  } else if (rom && (0x4000 <= address) && (address < 0x8000)) {
86  return (*rom)[addr & (rom->getSize() - 1)];
87  } else {
88  return 0xFF;
89  }
90 }
91 
92 const byte* MSXRS232::getReadCacheLine(word start) const
93 {
94  if (hasMemoryBasedIo && (start == (0xBFF8 & CacheLine::HIGH))) {
95  return nullptr;
96  }
97  word addr = start & 0x3FFF;
98  if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
99  return &(*ram)[addr - RAM_OFFSET];
100  } else if (rom && (0x4000 <= start) && (start < 0x8000)) {
101  return &(*rom)[addr & (rom->getSize() - 1)];
102  } else {
103  return unmappedRead;
104  }
105 }
106 
107 void MSXRS232::writeMem(word address, byte value, EmuTime::param time)
108 {
109 
110  if (hasMemoryBasedIo && (0xBFF8 <= address) && (address <= 0xBFFF)) {
111  // when the interface has memory based I/O, the I/O port
112  // based I/O is disabled, but it can be enabled by writing
113  // bit 4 to 0xBFFA. It is disabled again at reset.
114  // Source: Sony HB-G900P and Sony HB-G900AP service manuals.
115  // We assume here you can also disable it by writing 0 to it.
116  if (address == 0xBFFA) {
117  ioAccessEnabled = (value & (1 << 4))!=0;
118  }
119  return writeIOImpl(address & 0x07, value, time);
120  }
121  word addr = address & 0x3FFF;
122  if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
123  (*ram)[addr - RAM_OFFSET] = value;
124  }
125 }
126 
128 {
129  if (hasMemoryBasedIo && (start == (0xBFF8 & CacheLine::HIGH))) {
130  return nullptr;
131  }
132  word addr = start & 0x3FFF;
133  if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
134  return &(*ram)[addr - RAM_OFFSET];
135  } else {
136  return unmappedWrite;
137  }
138 }
139 
141 {
142  // OK, because this device doesn't call any 'fillDeviceXXXCache()'functions.
143  return true;
144 }
145 
146 byte MSXRS232::readIO(word port, EmuTime::param time)
147 {
148  if (ioAccessEnabled) {
149  return readIOImpl(port & 0x07, time);
150  }
151  return 0xFF;
152 }
153 
154 byte MSXRS232::readIOImpl(word port, EmuTime::param time)
155 {
156  byte result;
157  switch (port) {
158  case 0: // UART data register
159  case 1: // UART status register
160  result = i8251.readIO(port, time);
161  break;
162  case 2: // Status sense port
163  result = readStatus(time);
164  break;
165  case 3: // no function
166  result = 0xFF;
167  break;
168  case 4: // counter 0 data port
169  case 5: // counter 1 data port
170  case 6: // counter 2 data port
171  case 7: // timer command register
172  result = i8254.readIO(port - 4, time);
173  break;
174  default:
175  UNREACHABLE; return 0;
176  }
177  return result;
178 }
179 
180 byte MSXRS232::peekIO(word port, EmuTime::param time) const
181 {
182  if (hasMemoryBasedIo && !ioAccessEnabled) return 0xFF;
183  byte result;
184  port &= 0x07;
185  switch (port) {
186  case 0: // UART data register
187  case 1: // UART status register
188  result = i8251.peekIO(port, time);
189  break;
190  case 2: // Status sense port
191  result = 0; // TODO not implemented
192  break;
193  case 3: // no function
194  result = 0xFF;
195  break;
196  case 4: // counter 0 data port
197  case 5: // counter 1 data port
198  case 6: // counter 2 data port
199  case 7: // timer command register
200  result = i8254.peekIO(port - 4, time);
201  break;
202  default:
203  UNREACHABLE; return 0;
204  }
205  return result;
206 }
207 
208 void MSXRS232::writeIO(word port, byte value, EmuTime::param time)
209 {
210  if (ioAccessEnabled) writeIOImpl(port & 0x07, value, time);
211 }
212 
213 void MSXRS232::writeIOImpl(word port, byte value, EmuTime::param time)
214 {
215  switch (port) {
216  case 0: // UART data register
217  case 1: // UART command register
218  i8251.writeIO(port, value, time);
219  break;
220  case 2: // interrupt mask register
221  setIRQMask(value);
222  break;
223  case 3: // no function
224  break;
225  case 4: // counter 0 data port
226  case 5: // counter 1 data port
227  case 6: // counter 2 data port
228  case 7: // timer command register
229  i8254.writeIO(port - 4, value, time);
230  break;
231  }
232 }
233 
234 byte MSXRS232::readStatus(EmuTime::param time)
235 {
236 
237  // Info from http://nocash.emubase.de/portar.htm
238  //
239  // Bit Name Expl.
240  // 0 CD Carrier Detect (0=Active, 1=Not active)
241  // 1 RI Ring Indicator (0=Active, 1=Not active) (N/C in MSX)
242  // 6 Timer Output from i8253 Counter 2
243  // 7 CTS Clear to Send (0=Active, 1=Not active)
244  //
245  // On Toshiba HX-22, see
246  // http://www.msx.org/forum/msx-talk/hardware/toshiba-hx-22?page=3
247  // RetroTechie's post of 20-09-2012, 08:08
248  // ... The "RS-232 interrupt disable" bit can be read back via bit 3
249  // on this I/O port, if CN1 is open. If CN1 is closed, it always
250  // reads back as "0". ...
251 
252  byte result = 0; // TODO check unused bits
253 
254  // TODO bit 0: carrier detect
255 
256  if (!rxrdyIRQenabled && switchSetting && switchSetting->getBoolean()) {
257  result |= 0x08;
258  }
259 
260  if (!interf.getCTS(time)) {
261  result |= 0x80;
262  }
263  if (i8254.getOutputPin(2).getState(time)) {
264  result |= 0x40;
265  }
266  return result;
267 }
268 
269 void MSXRS232::setIRQMask(byte value)
270 {
271  enableRxRDYIRQ(!(value & 1));
272 }
273 
274 void MSXRS232::setRxRDYIRQ(bool status)
275 {
276  if (rxrdyIRQlatch != status) {
277  rxrdyIRQlatch = status;
278  if (rxrdyIRQenabled) {
279  if (rxrdyIRQlatch) {
280  rxrdyIRQ.set();
281  } else {
282  rxrdyIRQ.reset();
283  }
284  }
285  }
286 }
287 
288 void MSXRS232::enableRxRDYIRQ(bool enabled)
289 {
290  if (rxrdyIRQenabled != enabled) {
291  rxrdyIRQenabled = enabled;
292  if (!rxrdyIRQenabled && rxrdyIRQlatch) {
293  rxrdyIRQ.reset();
294  }
295  }
296 }
297 
298 
299 // I8251Interface (pass calls from I8251 to outConnector)
300 
301 void MSXRS232::I8251Interf::setRxRDY(bool status, EmuTime::param /*time*/)
302 {
303  auto& rs232 = OUTER(MSXRS232, interf);
304  rs232.setRxRDYIRQ(status);
305 }
306 
307 void MSXRS232::I8251Interf::setDTR(bool status, EmuTime::param time)
308 {
309  auto& rs232 = OUTER(MSXRS232, interf);
310  rs232.getPluggedRS232Dev().setDTR(status, time);
311 }
312 
313 void MSXRS232::I8251Interf::setRTS(bool status, EmuTime::param time)
314 {
315  auto& rs232 = OUTER(MSXRS232, interf);
316  rs232.getPluggedRS232Dev().setRTS(status, time);
317 }
318 
319 bool MSXRS232::I8251Interf::getDSR(EmuTime::param time)
320 {
321  auto& rs232 = OUTER(MSXRS232, interf);
322  return rs232.getPluggedRS232Dev().getDSR(time);
323 }
324 
325 bool MSXRS232::I8251Interf::getCTS(EmuTime::param time)
326 {
327  auto& rs232 = OUTER(MSXRS232, interf);
328  return rs232.getPluggedRS232Dev().getCTS(time);
329 }
330 
331 void MSXRS232::I8251Interf::setDataBits(DataBits bits)
332 {
333  auto& rs232 = OUTER(MSXRS232, interf);
334  rs232.getPluggedRS232Dev().setDataBits(bits);
335 }
336 
337 void MSXRS232::I8251Interf::setStopBits(StopBits bits)
338 {
339  auto& rs232 = OUTER(MSXRS232, interf);
340  rs232.getPluggedRS232Dev().setStopBits(bits);
341 }
342 
343 void MSXRS232::I8251Interf::setParityBit(bool enable, ParityBit parity)
344 {
345  auto& rs232 = OUTER(MSXRS232, interf);
346  rs232.getPluggedRS232Dev().setParityBit(enable, parity);
347 }
348 
349 void MSXRS232::I8251Interf::recvByte(byte value, EmuTime::param time)
350 {
351  auto& rs232 = OUTER(MSXRS232, interf);
352  rs232.getPluggedRS232Dev().recvByte(value, time);
353 }
354 
355 void MSXRS232::I8251Interf::signal(EmuTime::param time)
356 {
357  auto& rs232 = OUTER(MSXRS232, interf);
358  rs232.getPluggedRS232Dev().signal(time); // for input
359 }
360 
361 
362 // Counter 0 output
363 
364 void MSXRS232::Counter0::signal(ClockPin& pin, EmuTime::param time)
365 {
366  auto& rs232 = OUTER(MSXRS232, cntr0);
367  ClockPin& clk = rs232.i8251.getClockPin();
368  if (pin.isPeriodic()) {
369  clk.setPeriodicState(pin.getTotalDuration(),
370  pin.getHighDuration(), time);
371  } else {
372  clk.setState(pin.getState(time), time);
373  }
374 }
375 
376 void MSXRS232::Counter0::signalPosEdge(ClockPin& /*pin*/, EmuTime::param /*time*/)
377 {
378  UNREACHABLE;
379 }
380 
381 
382 // Counter 1 output // TODO split rx tx
383 
384 void MSXRS232::Counter1::signal(ClockPin& pin, EmuTime::param time)
385 {
386  auto& rs232 = OUTER(MSXRS232, cntr1);
387  ClockPin& clk = rs232.i8251.getClockPin();
388  if (pin.isPeriodic()) {
389  clk.setPeriodicState(pin.getTotalDuration(),
390  pin.getHighDuration(), time);
391  } else {
392  clk.setState(pin.getState(time), time);
393  }
394 }
395 
396 void MSXRS232::Counter1::signalPosEdge(ClockPin& /*pin*/, EmuTime::param /*time*/)
397 {
398  UNREACHABLE;
399 }
400 
401 
402 // RS232Connector input
403 
405 {
406  return i8251.isRecvReady();
407 }
408 
410 {
411  return i8251.isRecvEnabled();
412 }
413 
415 {
416  i8251.setDataBits(bits);
417 }
418 
420 {
421  i8251.setStopBits(bits);
422 }
423 
424 void MSXRS232::setParityBit(bool enable, ParityBit parity)
425 {
426  i8251.setParityBit(enable, parity);
427 }
428 
429 void MSXRS232::recvByte(byte value, EmuTime::param time)
430 {
431  i8251.recvByte(value, time);
432 }
433 
434 // version 1: initial version
435 // version 2: added ioAccessEnabled
436 // TODO: serialize switch status?
437 template<typename Archive>
438 void MSXRS232::serialize(Archive& ar, unsigned version)
439 {
440  ar.template serializeBase<MSXDevice>(*this);
441  ar.template serializeBase<RS232Connector>(*this);
442 
443  ar.serialize("I8254", i8254,
444  "I8251", i8251);
445  if (ram) ar.serialize("ram", *ram);
446  ar.serialize("rxrdyIRQ", rxrdyIRQ,
447  "rxrdyIRQlatch", rxrdyIRQlatch,
448  "rxrdyIRQenabled", rxrdyIRQenabled);
449  if (ar.versionAtLeast(version, 2)) {
450  ar.serialize("ioAccessEnabled", ioAccessEnabled);
451  } else {
452  assert(ar.isLoader());
453  ioAccessEnabled = !hasMemoryBasedIo; // we can't know the
454  // actual value, but this is probably
455  // safest
456  }
457 
458  // don't serialize cntr0, cntr1, interf
459 }
461 REGISTER_MSXDEVICE(MSXRS232, "RS232");
462 
463 } // namespace openmsx
openmsx::I8251::setParityBit
void setParityBit(bool enable, ParityBit parity) override
Definition: I8251.cc:294
openmsx::MSXDevice
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition: MSXDevice.hh:31
one_of.hh
openmsx::ClockPin::setPeriodicState
void setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time)
Definition: ClockPin.cc:36
openmsx::I8251::peekIO
byte peekIO(word port, EmuTime::param time) const
Definition: I8251.cc:95
openmsx::MSXRS232::writeMem
void writeMem(word address, byte value, EmuTime::param time) override
Write a given byte to a given location at a certain time to this device.
Definition: MSXRS232.cc:107
openmsx::MSXRS232::setDataBits
void setDataBits(DataBits bits) override
Definition: MSXRS232.cc:414
openmsx::I8254::getOutputPin
ClockPin & getOutputPin(unsigned cntr)
Definition: I8254.cc:174
openmsx::MSXRS232::~MSXRS232
~MSXRS232() override
openmsx::MSXRS232::writeIO
void writeIO(word port, byte value, EmuTime::param time) override
Write a byte to a given IO port at a certain time to this device.
Definition: MSXRS232.cc:208
serialize.hh
openmsx::I8251::setDataBits
void setDataBits(DataBits bits) override
Definition: I8251.hh:44
Rom.hh
openmsx::EmuDuration
Definition: EmuDuration.hh:18
openmsx::I8251::setStopBits
void setStopBits(StopBits bits) override
Definition: I8251.hh:45
openmsx::MSXRS232::powerUp
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
Definition: MSXRS232.cc:60
openmsx::DeviceConfig
Definition: DeviceConfig.hh:19
openmsx::YM2413NukeYKT::rom
constexpr Rom rom
Definition: YM2413NukeYKT.cc:71
openmsx::MSXDevice::unmappedWrite
static byte unmappedWrite[0x10000]
Definition: MSXDevice.hh:301
openmsx::MSXRS232::MSXRS232
MSXRS232(const DeviceConfig &config)
Definition: MSXRS232.cc:20
openmsx::I8251::readIO
byte readIO(word port, EmuTime::param time)
Definition: I8251.cc:79
MSXException.hh
openmsx::I8254::peekIO
byte peekIO(word port, EmuTime::param time) const
Definition: I8254.cc:107
openmsx::MSXException
Definition: MSXException.hh:9
openmsx::MSXRS232::readMem
byte readMem(word address, EmuTime::param time) override
Read a byte from a location at a certain time from this device.
Definition: MSXRS232.cc:77
openmsx::MSXRS232::getWriteCacheLine
byte * getWriteCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
Definition: MSXRS232.cc:127
openmsx::CacheLine::HIGH
constexpr unsigned HIGH
Definition: CacheLine.hh:10
openmsx::MSXRS232::recvByte
void recvByte(byte value, EmuTime::param time) override
Definition: MSXRS232.cc:429
BooleanSetting.hh
openmsx::MSXRS232::acceptsData
bool acceptsData() override
Definition: MSXRS232.cc:409
openmsx::RAM_SIZE
const unsigned RAM_SIZE
Definition: MSXRS232.cc:18
openmsx::REGISTER_MSXDEVICE
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
openmsx::MSXRS232::peekIO
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Definition: MSXRS232.cc:180
openmsx::ClockPin::getState
bool getState(EmuTime::param time) const
Definition: ClockPin.cc:58
openmsx::MSXRS232::setStopBits
void setStopBits(StopBits bits) override
Definition: MSXRS232.cc:419
Ram.hh
openmsx::MSXRS232::getReadCacheLine
const byte * getReadCacheLine(word start) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
Definition: MSXRS232.cc:92
openmsx::ClockPin
ClockPin
Definition: ClockPin.cc:149
OUTER
#define OUTER(type, member)
Definition: outer.hh:41
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::MSXRS232
MSXRS232
Definition: MSXRS232.cc:460
openmsx::Keys::getName
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:740
openmsx::I8254::getClockPin
ClockPin & getClockPin(unsigned cntr)
Definition: I8254.cc:168
openmsx::MSXRS232
Definition: MSXRS232.hh:17
openmsx::MSXDevice::getCurrentTime
EmuTime::param getCurrentTime() const
Definition: MSXDevice.cc:131
one_of
Definition: one_of.hh:7
openmsx::MSXRS232::allowUnaligned
bool allowUnaligned() const override
By default we don't allow unaligned <mem> specifications in the config file.
Definition: MSXRS232.cc:140
openmsx::MSXRS232::reset
void reset(EmuTime::param time) override
This method is called on reset.
Definition: MSXRS232.cc:66
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
openmsx::MSXRS232::readIO
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
Definition: MSXRS232.cc:146
openmsx::I8251::writeIO
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8251.cc:108
CacheLine.hh
openmsx::MSXRS232::setParityBit
void setParityBit(bool enable, ParityBit parity) override
Definition: MSXRS232.cc:424
openmsx::SerialDataInterface::StopBits
StopBits
Definition: SerialDataInterface.hh:15
openmsx::Rom
Definition: Rom.hh:20
openmsx::IntHelper::reset
void reset()
Reset the interrupt request on the bus.
Definition: IRQHelper.hh:96
openmsx::SerialDataInterface::DataBits
DataBits
Definition: SerialDataInterface.hh:12
outer.hh
openmsx::Ram
Definition: Ram.hh:15
openmsx::I8251::isRecvEnabled
bool isRecvEnabled() const
Definition: I8251.cc:318
RS232Device.hh
openmsx::MSXRS232::serialize
void serialize(Archive &ar, unsigned version)
Definition: MSXRS232.cc:438
openmsx::MSXDevice::unmappedRead
static byte unmappedRead[0x10000]
Definition: MSXDevice.hh:300
MSXRS232.hh
openmsx::IntHelper::set
void set()
Set the interrupt request on the bus.
Definition: IRQHelper.hh:87
openmsx::word
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
openmsx::RS232Connector
Definition: RS232Connector.hh:12
openmsx::BooleanSetting
Definition: BooleanSetting.hh:8
openmsx::SerialDataInterface::ParityBit
ParityBit
Definition: SerialDataInterface.hh:19
unreachable.hh
openmsx::MSXRS232::ready
bool ready() override
Definition: MSXRS232.cc:404
openmsx::I8254::writeIO
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8254.cc:120
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
openmsx::I8251::recvByte
void recvByte(byte value, EmuTime::param time) override
Definition: I8251.cc:300
openmsx::I8254::readIO
byte readIO(word port, EmuTime::param time)
Definition: I8254.cc:94
openmsx::RAM_OFFSET
const unsigned RAM_OFFSET
Definition: MSXRS232.cc:17
openmsx::I8251::isRecvReady
bool isRecvReady() const
Definition: I8251.hh:40