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