OXYGENHUB
◆ main openmsx / src / MSXPiDevice.cc
13.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "MSXPiDevice.hh"
2
3
#include "DeviceConfig.hh"
4
#include "MSXCPU.hh"
5
#include "MSXException.hh"
6
#include "MSXMotherBoard.hh"
7
#include "RealTime.hh"
8
#include "Timer.hh"
9
#include "serialize.hh"
10
11
#include <algorithm>
12
#include <array>
13
#include <cassert>
14
#include <chrono>
15
16
namespace openmsx {
17
18
// virtual-SPI frame opcodes, see MSXPiDevice.hh
19
static constexpr byte OP_OFFER  = 0x01;
20
static constexpr byte OP_HOLD   = 0x02;
21
static constexpr byte OP_CANCEL = 0x03;
22
static constexpr byte OP_HELLO  = 0x7E;
23
static constexpr byte PROTOCOL_VERSION = 1;
24
25
static constexpr byte MSXPI_VERSION = 0x0E; // firmware version, port 0x57
26
static constexpr int DEFAULT_SERVER_PORT = 5000;
27
28
// How long the Pi takes to clock one byte, and so how long a wait-mode stall
29
// lasts. In the order of what the Pi's native GPIO engine needs.
30
static constexpr auto TRANSFER_TIME = EmuDuration::usec(20);
31
32
MSXPiDevice::MSXPiDevice(DeviceConfig& config)
33
	: MSXDevice(config)
34
	, activePort(DEFAULT_SERVER_PORT)
35
	, rom(getName() + " ROM", "MSXPi EEPROM", config)
36
	, bankSetting(getCommandController(), "msxpirom_bank",
37
		"MSXPi bank jumper, as labelled on the PCB: BANK2 (EEPROM A14 = CPU "
38
		"A15) puts the lower half (MSX-DOS) at 4000h, BANK1 (EEPROM A14 = CPU "
39
		"A14) the upper half (BIOS only). Takes effect at the next reset",
40
		Bank::A15,
41
		EnumSetting<Bank>::Map{{"BANK1", Bank::A14}, {"BANK2", Bank::A15}})
42
	, sltslSetting(getCommandController(), "msxpirom_sltsl",
43
		"MSXPi J3 SLTSL jumper: OFF disconnects the EEPROM, the interface "
44
		"ports keep working. Takes effect at the next reset", Sltsl::ON,
45
		EnumSetting<Sltsl>::Map{{"ON", Sltsl::ON}, {"OFF", Sltsl::OFF}})
46
	, portSetting(getCommandController(), "msxpiserver_port",
47
		"TCP port used to connect to the MSXPi server",
48
		DEFAULT_SERVER_PORT, 1, 65535)
49
{
50
	activePort = portSetting.getInt();
51
	portSetting.attach(*this);
52
	if (rom.size() != 0x4000 && rom.size() != 0x8000) {
53
		throw MSXException("MSXPi EEPROM image must be 16KB or 32KB, got ",
54
		                   rom.size(), " bytes");
55
	}
56
	bank = bankSetting.getEnum(); // read before the first access
57
	romEnabled = sltslSetting.getEnum() == Sltsl::ON;
58
	thread = std::thread(&MSXPiDevice::readLoop, this);
59
}
60
61
MSXPiDevice::~MSXPiDevice()
62
{
63
	portSetting.detach(*this);
64
	shouldStop = true;
65
	closeSocket();
66
	if (thread.joinable()) {
67
		poller.abort();
68
		thread.join();
69
	}
70
}
71
72
void MSXPiDevice::update(const Setting& setting) noexcept
73
{
74
	(void)setting;
75
	assert(&setting == &portSetting);
76
	activePort = portSetting.getInt();
77
	closeSocket();
78
}
79
80
void MSXPiDevice::closeSocket()
81
{
82
	std::lock_guard lock(socketMtx);
83
	auto oldSock = sock.exchange(OPENMSX_INVALID_SOCKET);
84
	if (oldSock != OPENMSX_INVALID_SOCKET) {
85
		sock_close(oldSock);
86
	}
87
}
88
89
// ---------------------------------------------------------------------------
90
// Interface model
91
// ---------------------------------------------------------------------------
92
93
void MSXPiDevice::interfaceReset()
94
{
95
	// What OUT (0x56),0xFF does. The Pi does not see it: an offer it made
96
	// still waits to be clocked, and a transfer it is already clocking just
97
	// finishes on its side.
98
	busy = false;
99
	bound.reset();
100
	srValue = 0x00;
101
	waitMode = false;
102
}
103
104
void MSXPiDevice::powerUp(EmuTime /*time*/)
105
{
106
	interfaceReset();
107
	latch = 0xFF;
108
	applyJumpers();
109
}
110
111
void MSXPiDevice::reset(EmuTime /*time*/)
112
{
113
	// The interface itself has no reset input (see powerUp); only the
114
	// EEPROM jumpers, which are read as the machine comes up, are picked up.
115
	applyJumpers();
116
}
117
118
// ---------------------------------------------------------------------------
119
// EEPROM
120
// ---------------------------------------------------------------------------
121
122
void MSXPiDevice::applyJumpers()
123
{
124
	auto newBank = bankSetting.getEnum();
125
	auto newEnabled = sltslSetting.getEnum() == Sltsl::ON;
126
	if (newBank == bank && newEnabled == romEnabled) return;
127
	bank = newBank;
128
	romEnabled = newEnabled;
129
	invalidateDeviceRCache();
130
}
131
132
const byte* MSXPiDevice::romByte(uint16_t address) const
133
{
134
	// The chip's A14 is CPU A15 or CPU A14, as the bank jumper wires it.
135
	if (!romEnabled) return unmappedRead.data(); // /CE not connected
136
	unsigned a14 = (bank == Bank::A15) ? (address >> 15) & 1 : (address >> 14) & 1;
137
	unsigned chip = (a14 << 14) | (address & 0x3FFF);
138
	if (chip >= rom.size()) return unmappedRead.data(); // 16KB image
139
	return &rom[chip];
140
}
141
142
byte MSXPiDevice::readMem(uint16_t address, EmuTime time)
143
{
144
	return peekMem(address, time);
145
}
146
147
byte MSXPiDevice::peekMem(uint16_t address, EmuTime /*time*/) const
148
{
149
	return *romByte(address);
150
}
151
152
const byte* MSXPiDevice::getReadCacheLine(uint16_t start) const
153
{
154
	// A cache line never crosses a 16KB boundary, so one lookup covers it.
155
	return romByte(start);
156
}
157
158
bool MSXPiDevice::ready() const
159
{
160
	// The Pi's READY line.
161
	if (link != Link::FRAMED) return false; // no Pi: pulled low
162
	return bound || offerCount != 0; // up while clocking or offering a byte
163
}
164
165
void MSXPiDevice::startTransfer(EmuTime time)
166
{
167
	busy = true;
168
	bound.reset();
169
	tryBind(time);
170
}
171
172
void MSXPiDevice::tryBind(EmuTime time)
173
{
174
	// A started transfer gets clocked as soon as the Pi offers a byte.
175
	if (!busy || bound || link != Link::FRAMED || offerCount == 0) return;
176
	std::lock_guard lock(mtx);
177
	if (offers.empty()) return; // withdrawn by a cancel meanwhile
178
	bound = offers.front();
179
	offers.pop_front();
180
	offerCount = offers.size();
181
	boundEpoch = epoch;
182
	// The Pi starts clocking now, and copies the latch as it does.
183
	startTime = time;
184
	doneTime = time + TRANSFER_TIME;
185
	sendFrame(OP_OFFER, latch);
186
}
187
188
void MSXPiDevice::complete()
189
{
190
	srValue = bound->miso;
191
	bound.reset();
192
	busy = false;
193
}
194
195
void MSXPiDevice::update(EmuTime time)
196
{
197
	if (bound && boundEpoch != epoch) {
198
		// The server went away mid-byte. Nobody clocks this transfer any
199
		// more: it stays started until reset, or until a new server's first
200
		// offer picks it up - exactly what the hardware does.
201
		bound.reset();
202
	}
203
	if (busy && !bound) tryBind(time);
204
	if (bound && time >= doneTime) complete();
205
}
206
207
byte MSXPiDevice::shiftRegister(EmuTime time) const
208
{
209
	if (!busy) return srValue;
210
	// A marker bit is loaded at bit 0 when a transfer starts; the Pi's bits
211
	// then shift in below it, one per ninth of the transfer time.
212
	if (!bound || time <= startTime) return 0x01;
213
	auto elapsed = (time - startTime).toUint64();
214
	auto total = std::max<uint64_t>(TRANSFER_TIME.toUint64(), 1);
215
	auto bits = unsigned(std::min<uint64_t>(8, (elapsed * 9) / total));
216
	unsigned sr = (1u << bits) | (unsigned(bound->miso) >> (8 - bits));
217
	return byte(sr); // after 8 bits the marker is in bit 8, not visible
218
}
219
220
void MSXPiDevice::waitForPeer(EmuTime time)
221
{
222
	// Emulation runs ahead of real time in slices and then sleeps to throttle.
223
	// A byte the server offers during that sleep would only be seen at the
224
	// start of the next slice, so every request/response exchange with the
225
	// server would cost a whole slice of emulated time - far more than the
226
	// real Pi needs. Instead, spend the time emulation is ahead of real time
227
	// here, waiting for the offer. That is time RealTime would have slept
228
	// anyway, so this never makes emulation fall behind real time.
229
	static constexpr uint64_t STEP_US = 1000;
230
	static constexpr uint64_t ALLOWED_LAG_US = 20000; // RealTime's own slack
231
	if (link != Link::FRAMED) return;
232
	auto& realTime = getMotherBoard().getRealTime();
233
	std::unique_lock lock(mtx);
234
	while (offers.empty() && link == Link::FRAMED &&
235
	       realTime.timeLeft(STEP_US + ALLOWED_LAG_US, time)) {
236
		offerCv.wait_for(lock, std::chrono::microseconds(STEP_US));
237
	}
238
}
239
240
EmuTime MSXPiDevice::stall(EmuTime time)
241
{
242
	// Wait mode holds /WAIT while a transfer runs and the Pi's READY is up;
243
	// the stretched I/O cycle ends when the Pi has clocked the byte. Returns
244
	// the time the I/O cycle ends.
245
	if (!waitMode || !busy || !bound) return time;
246
	if (doneTime > time) {
247
		getCPU().wait(doneTime);
248
		update(doneTime);
249
		return doneTime;
250
	}
251
	return time;
252
}
253
254
byte MSXPiDevice::readIO(uint16_t port, EmuTime time)
255
{
256
	switch (port & 0xff) {
257
	case 0x56:
258
		update(time);
259
		if (!bound && offerCount == 0) {
260
			// The MSX is polling for a byte the Pi has not offered yet.
261
			waitForPeer(time);
262
			update(time);
263
		}
264
		return peekIO(port, time);
265
	case 0x57:
266
		update(time);
267
		return peekIO(port, time);
268
	case 0x5A:
269
		update(time);
270
		if (waitMode) {
271
			// a wait-mode read starts a transfer, but only when the Pi is ready
272
			if (!busy && ready()) startTransfer(time);
273
			return shiftRegister(stall(time));
274
		}
275
		return shiftRegister(time);
276
	default:
277
		return 0xFF;
278
	}
279
}
280
281
byte MSXPiDevice::peekIO(uint16_t port, EmuTime time) const
282
{
283
	switch (port & 0xff) {
284
	case 0x56: // bit 0: transfer running or Pi not ready
285
		return (busy || !ready()) ? 0x01 : 0x00;
286
	case 0x57:
287
		return byte((waitMode ? 0x80 : 0x00) | MSXPI_VERSION);
288
	case 0x5A:
289
		return shiftRegister(time);
290
	default:
291
		return 0xFF;
292
	}
293
}
294
295
void MSXPiDevice::writeIO(uint16_t port, byte value, EmuTime time)
296
{
297
	switch (port & 0xff) {
298
	case 0x56:
299
		if (value == 0xFF) {
300
			latch = value;
301
			interfaceReset();
302
			break;
303
		}
304
		[[fallthrough]];
305
	case 0x5A:
306
		update(time);
307
		latch = value;
308
		// a write starts a transfer, whether the Pi is ready or not
309
		if (!busy) startTransfer(time);
310
		stall(time);
311
		break;
312
	case 0x57:
313
		// only 0x01 sets and only 0x00 clears wait mode
314
		if (value == 0x01) {
315
			waitMode = true;
316
		} else if (value == 0x00) {
317
			waitMode = false;
318
		}
319
		break;
320
	default:
321
		break;
322
	}
323
}
324
325
// ---------------------------------------------------------------------------
326
// Transport
327
// ---------------------------------------------------------------------------
328
329
void MSXPiDevice::sendFrame(byte op, byte arg)
330
{
331
	auto s = sock.load();
332
	if (s == OPENMSX_INVALID_SOCKET) return;
333
	std::array<char, 2> frame = {char(op), char(arg)};
334
	(void)sock_send(s, frame.data(), frame.size());
335
}
336
337
void MSXPiDevice::handleFrame(byte op, byte arg)
338
{
339
	switch (op) {
340
	case OP_HOLD:
341
		pendingHold.emplace_back(arg, true);
342
		break;
343
	case OP_OFFER: {
344
		// a run of holds becomes visible together with its closing offer
345
		std::lock_guard lock(mtx);
346
		offers.insert(offers.end(), pendingHold.begin(), pendingHold.end());
347
		offers.emplace_back(arg, false);
348
		offerCount = offers.size();
349
		pendingHold.clear();
350
		offerCv.notify_all();
351
		break;
352
	}
353
	case OP_CANCEL: {
354
		// Withdraw every offer not clocked yet. Replies for clocked offers
355
		// are sent under this same mutex, so they all precede the
356
		// acknowledgement on the wire.
357
		std::lock_guard lock(mtx);
358
		pendingHold.clear();
359
		offers.clear();
360
		offerCount = 0;
361
		sendFrame(OP_CANCEL, 0);
362
		break;
363
	}
364
	default:
365
		break; // unknown frame
366
	}
367
}
368
369
bool MSXPiDevice::connectSocket()
370
{
371
	SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
372
	if (s == OPENMSX_INVALID_SOCKET) return false;
373
	auto port = activePort.load();
374
	auto addr = sock_makeIPv4(INADDR_LOOPBACK, uint16_t(port)); // 127.0.0.1
375
	// Every transfer is a small request/response; Nagle plus the server's
376
	// delayed ACK would hold each frame back ~40 ms.
377
	sock_setNoDelay(s);
378
	if (connect(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0) {
379
		sock_close(s);
380
		return false;
381
	}
382
383
	std::lock_guard lock(socketMtx);
384
	if ((port != activePort.load()) || (sock != OPENMSX_INVALID_SOCKET)) {
385
		sock_close(s);
386
		return false;
387
	}
388
	sock = s;
389
	return true;
390
}
391
392
void MSXPiDevice::readLoop()
393
{
394
	std::array<char, 4096> buf;
395
	while (!shouldStop) {
396
		if (sock == OPENMSX_INVALID_SOCKET) {
397
			link = Link::DOWN;
398
			if (!connectSocket()) {
399
				Timer::sleep(1'000'000); // retry once per second
400
				continue;
401
			}
402
			{
403
				std::lock_guard lock(mtx);
404
				offers.clear();
405
				offerCount = 0;
406
			}
407
			pendingHold.clear();
408
			partial.reset();
409
			++epoch;
410
			link = Link::PROBING; // READY stays low until the server greets
411
		}
412
413
#ifndef _WIN32
414
		if (poller.poll(sock)) {
415
			continue; // error or abort
416
		}
417
#endif
418
		auto n = sock_recv(sock, buf.data(), buf.size());
419
		if (n <= 0) { // closed or error
420
			closeSocket();
421
			continue;
422
		}
423
424
		size_t i = 0;
425
		if (link == Link::PROBING) {
426
			if (!partial) {
427
				if (byte(buf[0]) != OP_HELLO) {
428
					// not a virtual-SPI server: it never offers, so
429
					// READY stays low, as with no Pi at all
430
					link = Link::INCOMPATIBLE;
431
				} else {
432
					partial = byte(buf[i++]);
433
				}
434
			}
435
			if (partial && i < size_t(n)) {
436
				partial.reset();
437
				++i; // protocol version: 1 is the only one so far
438
				std::lock_guard lock(mtx);
439
				sendFrame(OP_HELLO, PROTOCOL_VERSION);
440
				link = Link::FRAMED;
441
			}
442
		}
443
		if (link == Link::FRAMED) {
444
			for (; i < size_t(n); ++i) {
445
				auto b = byte(buf[i]);
446
				if (!partial) {
447
					partial = b;
448
				} else {
449
					handleFrame(*partial, b);
450
					partial.reset();
451
				}
452
			}
453
		}
454
	}
455
}
456
457
// The state the MSX can observe, which is what a savestate has to carry: the
458
// EEPROM jumpers as of the last reset, and the port-level state of the
459
// interface. The link to the server is deliberately left out - a socket and a
460
// reader thread cannot be restored - so after a load the device reconnects
461
// exactly as it does at startup, and a transfer that was in flight is dropped:
462
// the protocol's checksum/retry layer recovers from that.
463
template<typename Archive>
464
void MSXPiDevice::serialize(Archive& ar, unsigned /*version*/)
465
{
466
	ar.template serializeBase<MSXDevice>(*this);
467
468
	auto bankA14 = byte(bank == Bank::A14);
469
	ar.serialize("bankA14",    bankA14,
470
	             "romEnabled", romEnabled,
471
	             "latch",      latch,
472
	             "busy",       busy,
473
	             "waitMode",   waitMode,
474
	             "srValue",    srValue);
475
476
	if constexpr (Archive::IS_LOADER) {
477
		bank = bankA14 ? Bank::A14 : Bank::A15;
478
		bound.reset(); // nobody is clocking a transfer any more
479
		invalidateDeviceRCache(); // the ROM may be mapped differently now
480
	}
481
}
482
INSTANTIATE_SERIALIZE_METHODS(MSXPiDevice);
483
REGISTER_MSXDEVICE(MSXPiDevice, "MSXPiDevice");
484
485
} // namespace openmsx