OXYGENHUB
◆ main openmsx / src / MSXPiDevice.hh
9.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef MSXPIDEVICE_HH
2
#define MSXPIDEVICE_HH
3
4
#include "EnumSetting.hh"
5
#include "IntegerSetting.hh"
6
#include "MSXDevice.hh"
7
#include "EmuTime.hh"
8
#include "Observer.hh"
9
#include "Poller.hh"
10
#include "Rom.hh"
11
#include "Socket.hh"
12
13
#include <atomic>
14
#include <condition_variable>
15
#include <cstdint>
16
#include <deque>
17
#include <memory>
18
#include <mutex>
19
#include <optional>
20
#include <thread>
21
22
namespace openmsx {
23
24
class DeviceConfig;
25
26
/** MSXPi interface, emulated at I/O-port level after its CPLD (firmware v1.6,
27
  * MSXPi.vhd in the MSXPi repository).
28
  *
29
  * THE HARDWARE
30
  *
31
  * The MSX side is a CPLD with a shift register; the Raspberry Pi is the SPI
32
  * master. The MSX cannot pull a byte out of the Pi, it can only start a
33
  * transfer, which the Pi then clocks. Every transfer moves one byte in each
34
  * direction at once: the byte the MSX last wrote goes to the Pi, and the Pi's
35
  * byte comes in. The Pi signals with its READY line that it has a byte and
36
  * is about to clock; with no Pi attached a pull-down keeps READY low.
37
  *
38
  * Port 0x56, read:  bit 0 is 1 while a transfer is running or the Pi is not
39
  *                   ready, 0 when the last transfer completed and the Pi is
40
  *                   ready. The other bits read 0.
41
  * Port 0x56, write: 0xFF resets the interface: no transfer running, port
42
  *                   0x5A reads 0x00, wait mode off. Any other value starts a
43
  *                   transfer that sends that value to the Pi.
44
  * Port 0x57, read:  0x0E (the firmware version) with bit 7 set when wait
45
  *                   mode is on: 0x0E or 0x8E.
46
  * Port 0x57, write: 0x01 turns wait mode on, 0x00 turns it off, other values
47
  *                   are ignored.
48
  * Port 0x5A, read:  the byte received from the Pi by the last transfer. In
49
  *                   wait mode a read also starts a transfer, but only when
50
  *                   the Pi is ready.
51
  * Port 0x5A, write: starts a transfer that sends the value to the Pi.
52
  *
53
  * The value written is held in a latch that the Pi copies when it starts
54
  * clocking, not when the MSX writes it. While a transfer is running, a
55
  * further write only replaces that latch.
56
  *
57
  * Software in normal (polled) mode reads a byte with: wait until port 0x56
58
  * reads 0, OUT (0x56),0, wait until port 0x56 reads 0, IN (0x5A). It writes
59
  * one with: wait until port 0x56 reads 0, OUT (0x5A),byte.
60
  *
61
  * In wait mode the interface holds the Z80's /WAIT line for the duration of
62
  * a transfer, so a read or write only completes once the byte has moved and
63
  * INIR/OTIR need no polling. For writes this is what keeps the next OUT from
64
  * replacing the latch before the Pi took the previous byte. /WAIT is only
65
  * held while the Pi's READY is up: with the Pi not ready, a wait-mode read
66
  * returns the previous byte at once and starts nothing, which is why drivers
67
  * check port 0x56 before a burst.
68
  *
69
  * Mid-transfer the shift register holds a partly shifted value, which
70
  * port 0x5A returns. Correct software never observes it (it waits for 0x56,
71
  * or is held by /WAIT); it is emulated only because the hardware does it.
72
  *
73
  * THE EMULATION
74
  *
75
  * The Pi is replaced by msxpi-server.py over TCP. The link carries what the
76
  * Pi's pins carry ("virtual SPI"), in two-byte frames:
77
  *
78
  *   server -> device   01 d   offer: READY up, Pi sends d, READY drops after
79
  *                      02 d   offer: as 01, but READY stays up for the next
80
  *                      03 00  cancel: withdraw offers not clocked yet
81
  *                      7E v   hello, protocol version v (sent on accept)
82
  *   device -> server   01 m   an offer was clocked; the MSX sent m
83
  *                      03 00  cancel acknowledged
84
  *                      7E v   hello reply
85
  *
86
  * A run of 02 offers only becomes visible once its closing 01 offer has
87
  * arrived, so READY is never up without a byte to go with it and no port
88
  * access ever waits for the socket. Offers are taken into account at the port
89
  * accesses themselves, which is enough because the interface's state can only
90
  * be observed through its ports. A /WAIT stall is charged in emulated time.
91
  *
92
  * A server that sends no hello is too old to speak this protocol. It is
93
  * treated like an absent Pi: READY stays low.
94
  *
95
  * THE ROM
96
  *
97
  * The device also serves the board's 32KB EEPROM, wired
98
  * as on the board (J3 "ROM_SWITCH"): /CE is /SLTSL through the SLTSL
99
  * jumper, with no address decoding, so the chip answers in every page the
100
  * device is mapped in; its A14 pin is jumpered to CPU A14 or to CPU A15.
101
  * Two settings stand for the two jumpers, named as on the PCB:
102
  *
103
  *   msxpirom_bank  BANK2  EEPROM A14 = CPU A15: lower half at 4000h, upper
104
  *                         half mirrored at 8000h (default; the only
105
  *                         position the EEPROM can be programmed in)
106
  *                  BANK1  EEPROM A14 = CPU A14: upper half at 4000h, lower
107
  *                         half mirrored at 8000h
108
  *   msxpirom_sltsl ON     EEPROM enabled (default)
109
  *                  OFF    EEPROM disconnected: the slot reads FFh. The CPLD
110
  *                         and its ports are not affected.
111
  *
112
  * Like moving a jumper with the power off, a change takes effect at the next
113
  * reset. Map the device over the whole slot (<mem base="0x0000"
114
  * size="0x10000"/>) to see the mirroring the BIOS sees. A 16KB file is a
115
  * chip written with 16KB: the upper half reads as erased (FFh). The chip is
116
  * not writable here. The <rom> child is required; the board is shipped with
117
  * the EEPROM in place, and the SLTSL jumper covers running without it.
118
  */
119
class MSXPiDevice final : public MSXDevice, private Observer<Setting>
120
{
121
public:
122
	explicit MSXPiDevice(DeviceConfig& config);
123
	~MSXPiDevice() override;
124
125
	// The interface has no reset input: an MSX reset leaves it untouched,
126
	// only power-up (and OUT (0x56),0xFF) clears it.
127
	void powerUp(EmuTime time) override;
128
	void reset(EmuTime time) override;
129
	[[nodiscard]] byte readMem(uint16_t address, EmuTime time) override;
130
	[[nodiscard]] byte peekMem(uint16_t address, EmuTime time) const override;
131
	[[nodiscard]] const byte* getReadCacheLine(uint16_t start) const override;
132
	[[nodiscard]] byte readIO(uint16_t port, EmuTime time) override;
133
	[[nodiscard]] byte peekIO(uint16_t port, EmuTime time) const override;
134
	void writeIO(uint16_t port, byte value, EmuTime time) override;
135
136
	template<typename Archive>
137
	void serialize(Archive& ar, unsigned version);
138
139
private:
140
	enum class Link : uint8_t { DOWN, PROBING, FRAMED, INCOMPATIBLE };
141
	struct Offer {
142
		byte miso; // the byte the Pi sends
143
		bool hold; // READY stays up after this transfer
144
	};
145
146
	// interface model (emulation thread)
147
	void update(EmuTime time);
148
	void startTransfer(EmuTime time);
149
	void tryBind(EmuTime time);
150
	void complete();
151
	[[nodiscard]] bool ready() const;
152
	[[nodiscard]] byte shiftRegister(EmuTime time) const;
153
	EmuTime stall(EmuTime time);
154
	void interfaceReset();
155
	void waitForPeer(EmuTime time);
156
157
	// EEPROM
158
	void applyJumpers();
159
	[[nodiscard]] const byte* romByte(uint16_t address) const;
160
161
	// transport
162
	void readLoop();
163
	[[nodiscard]] bool connectSocket();
164
	void handleFrame(byte op, byte arg);
165
	void sendFrame(byte op, byte arg); // mtx must be held
166
	void closeSocket();
167
168
	// Observer<Setting>
169
	void update(const Setting& setting) noexcept override;
170
171
	// --- Emulation thread only -------------------------------------------
172
	byte latch = 0xFF;          // last value written to port 0x56 or 0x5A
173
	bool busy = false;          // a transfer has been started, not completed
174
	bool waitMode = false;
175
	byte srValue = 0;           // port 0x5A value when no transfer runs
176
	std::optional<Offer> bound; // the offer the Pi clocks the transfer with
177
	EmuTime startTime = EmuTime::zero(); // the Pi started clocking 'bound'
178
	EmuTime doneTime = EmuTime::zero();  // ... and completes it here
179
	unsigned boundEpoch = 0;    // connection 'bound' was offered on
180
181
	// --- Reader thread only ----------------------------------------------
182
	std::deque<Offer> pendingHold; // a run whose closing offer is still due
183
	std::optional<byte> partial;   // first byte of a frame
184
185
	// --- Shared, only accessed while holding mtx ---------------------------
186
	// Both threads take mtx for 'offers'; sending a frame on the socket also
187
	// happens under mtx, so frames from both threads never interleave.
188
	mutable std::mutex mtx;
189
	std::deque<Offer> offers;        // visible offers, oldest first
190
	std::condition_variable offerCv; // notified when offers become visible
191
192
	// --- Shared, only accessed while holding socketMtx ---------------------
193
	std::mutex socketMtx; // synchronizes closing/replacing 'sock'
194
195
	// --- Shared, atomic (no lock needed) ------------------------------------
196
	// offerCount always equals offers.size(): it is written only under mtx,
197
	// together with every change to 'offers'. It exists so the status poll on
198
	// port 0x56 - tens of thousands of reads per second during a transfer -
199
	// can test for offers without taking the lock; anything that then uses an
200
	// offer takes mtx and checks 'offers' itself.
201
	std::atomic<size_t> offerCount = 0;
202
	std::atomic<Link> link = Link::DOWN;
203
	std::atomic<unsigned> epoch = 0; // incremented on every new connection
204
	std::atomic<SOCKET> sock = OPENMSX_INVALID_SOCKET;
205
	std::atomic<int> activePort = 0;
206
	std::atomic<bool> shouldStop = false;
207
208
	// EEPROM
209
	const Rom rom;
210
	// EEPROM A14 wired to CPU A15 (PCB label BANK2) or CPU A14 (BANK1)
211
	enum class Bank : uint8_t { A15, A14 };
212
	enum class Sltsl : uint8_t { OFF, ON };  // EEPROM /CE connected to /SLTSL
213
	EnumSetting<Bank> bankSetting;           // msxpirom_bank
214
	EnumSetting<Sltsl> sltslSetting;         // msxpirom_sltsl
215
	Bank bank = Bank::A15;                   // the jumpers as of the last reset
216
	bool romEnabled = true;
217
218
	// thread & connection
219
	IntegerSetting portSetting;
220
	SocketActivator socketActivator; // ensure windows sockets are initialized
221
	std::thread thread;
222
	Poller poller; // to abort read-thread in a portable way
223
};
224
225
} // namespace openmsx
226
227
#endif