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