Nugget
Loading...
Searching...
No Matches
memory-card.hh
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27#pragma once
28
29#include <EASTL/functional.h>
30#include <stdint.h>
31
32#include <coroutine>
33
34#include "psyqo/task.hh"
35
36namespace psyqo {
37
38class GPU;
39
68 public:
75 enum class Error : uint8_t {
76 OK = 0, // No error.
77 NoCard, // No card present / no acknowledge on the bus.
78 NotFormatted, // Card is present but is not a valid Sony card.
79 BadChecksum, // A frame checksum did not match.
80 BadSector, // The card reported a bad / out of range sector.
81 Timeout, // The card stopped acknowledging mid-transfer.
82 Unconnected, // The port could not be selected.
83 ProtocolError, // Unexpected response byte from the card.
84 DirectoryFull, // No free directory entry for a new file.
85 OutOfSpace, // Not enough free blocks for a file.
86 FileNotFound, // The requested file does not exist.
87 FileExists, // A file with that name already exists.
88 NameTooLong, // The filename exceeds 20 characters.
89 FileTooLarge, // The payload would not fit in 15 blocks.
90 SerializeOverflow, // The serialized payload exceeded the buffer.
91 BadData, // The payload header / structure is corrupt.
92 BadPort, // The port argument is invalid.
93 };
94
98 enum class Port : unsigned { Port0 = 0, Port1 = 1 };
99
100 // Geometry constants.
101 static constexpr uint32_t c_sectorSize = 128; // bytes per sector / frame
102 static constexpr uint32_t c_sectorCount = 1024; // sectors per card
103 static constexpr uint32_t c_blockSize = 8192; // bytes per block
104 static constexpr uint32_t c_blockCount = 16; // blocks per card
105 static constexpr uint32_t c_sectorsPerBlock = 64; // sectors per block
106
107 static constexpr uint32_t sectorSize() { return c_sectorSize; }
108 static constexpr uint32_t sectorCount() { return c_sectorCount; }
109 static constexpr uint32_t blockSize() { return c_blockSize; }
110 static constexpr uint32_t blockCount() { return c_blockCount; }
111
115 static const char *errorMessage(Error error);
116
124 void prepare();
125
134 Error readSectorBlocking(Port port, uint16_t sector, void *buffer);
135
144 Error writeSectorBlocking(Port port, uint16_t sector, const void *buffer);
145
153
154 // --- Callback variants -------------------------------------------------
155 void readSector(Port port, uint16_t sector, void *buffer, eastl::function<void(Error)> &&callback);
156 void writeSector(Port port, uint16_t sector, const void *buffer, eastl::function<void(Error)> &&callback);
157
158 // --- TaskQueue schedulers ---------------------------------------------
159 TaskQueue::Task scheduleReadSector(Port port, uint16_t sector, void *buffer, Error *resultOut);
160 TaskQueue::Task scheduleWriteSector(Port port, uint16_t sector, const void *buffer, Error *resultOut);
161
162 // --- Coroutine-friendly awaiters --------------------------------------
164 ReadSectorAwaiter(MemoryCard &device, Port port, uint16_t sector, void *buffer)
165 : m_device(device), m_port(port), m_sector(sector), m_buffer(buffer) {}
166 bool await_ready() const { return false; }
167 template <typename U>
168 void await_suspend(std::coroutine_handle<U> handle) {
169 m_device.readSector(m_port, m_sector, m_buffer, [handle, this](Error result) {
170 m_result = result;
171 handle.resume();
172 });
173 }
174 Error await_resume() { return m_result; }
175
176 private:
177 MemoryCard &m_device;
178 Port m_port;
179 uint16_t m_sector;
180 void *m_buffer;
181 Error m_result = Error::OK;
182 };
183
185 WriteSectorAwaiter(MemoryCard &device, Port port, uint16_t sector, const void *buffer)
186 : m_device(device), m_port(port), m_sector(sector), m_buffer(buffer) {}
187 bool await_ready() const { return false; }
188 template <typename U>
189 void await_suspend(std::coroutine_handle<U> handle) {
190 m_device.writeSector(m_port, m_sector, m_buffer, [handle, this](Error result) {
191 m_result = result;
192 handle.resume();
193 });
194 }
195 Error await_resume() { return m_result; }
196
197 private:
198 MemoryCard &m_device;
199 Port m_port;
200 uint16_t m_sector;
201 const void *m_buffer;
202 Error m_result = Error::OK;
203 };
204
205 ReadSectorAwaiter readSector(Port port, uint16_t sector, void *buffer) { return {*this, port, sector, buffer}; }
206 WriteSectorAwaiter writeSector(Port port, uint16_t sector, const void *buffer) {
207 return {*this, port, sector, buffer};
208 }
209
210 private:
211 // -- Transport selection -----------------------------------------------
212 // The driver has two interchangeable transports for a single sector:
213 // * an interrupt-driven state machine (the default, modelled on the
214 // retail BIOS / openbios sio0 driver), and
215 // * a synchronous busy-polled transport (kept as a proven fallback).
216 // If the IRQ transport ever misbehaves on a particular setup, flip this to
217 // false to fall back to polling without touching anything else.
218 static constexpr bool c_useIrq = true;
219
220 // -- Retry / dispatch layer --------------------------------------------
221 Error singleSectorRead(Port port, uint16_t sector, uint8_t *out);
222 Error singleSectorWrite(Port port, uint16_t sector, const uint8_t *in);
223 Error readSectorRetried(Port port, uint16_t sector, uint8_t *out);
224 Error writeSectorRetried(Port port, uint16_t sector, const uint8_t *in);
225 static bool isTransient(Error error);
226
227 // -- Interrupt-driven transport ----------------------------------------
228 enum class Action : uint8_t { None, Read, Write };
229 enum class StepResult { Continue, Done };
230
231 void installIrqHandler();
232 void irq(); // entered on each SIO0 (controller) acknowledge interrupt
233 void startTransfer(Action action, Port port, uint16_t sector, void *readBuf, const void *writeBuf);
234 void finishTransfer(Error result);
235 StepResult readStep();
236 StepResult writeStep();
237 uint8_t exchangeByte(uint8_t out); // openbios-style: read previous, send next, ack
238 Error irqTransferBlocking(Action action, Port port, uint16_t sector, void *readBuf, const void *writeBuf);
239 // Asynchronous whole-transfer retry, mirroring the blocking *Retried path:
240 // re-issues a transient-failed callback transfer up to c_maxAttempts times
241 // before delivering the result. With the first-byte select timing corrected
242 // this is a safeguard against the occasional genuine bus glitch, not the
243 // primary mechanism, so it should rarely fire.
244 void startAsyncAttempt(Action action, Port port, uint16_t sector, void *readBuf, const void *writeBuf);
245 void onAsyncAttemptDone(Error result);
246 static uint16_t portMask(Port port);
247
248 // -- Polled transport (fallback) ---------------------------------------
249 Error doReadSector(Port port, uint16_t sector, uint8_t *out);
250 Error doWriteSector(Port port, uint16_t sector, const uint8_t *in);
251 void selectPort(Port port);
252 void deselect();
253 void flushRxBuffer();
254 uint8_t transceive(uint8_t dataOut);
255 bool waitAck(uint32_t timeout);
256
257 // Hold the bus deselected (/CS high) for the inter-transaction recovery
258 // window before selecting the port for a fresh transfer. Shared by both
259 // transports (startTransfer and selectPort).
260 void recoverBeforeSelect();
261
262 static void busyLoop(unsigned delay) {
263 unsigned cycles = 0;
264 while (++cycles < delay) asm("");
265 }
266
267 // -- Interrupt-driven state --------------------------------------------
268 volatile Action m_action = Action::None;
269 int m_step = 0;
270 Port m_port = Port::Port0;
271 uint16_t m_sector = 0;
272 uint8_t *m_readBuffer = nullptr;
273 const uint8_t *m_writeBuffer = nullptr;
274 uint8_t m_runningChecksum = 0;
275 uint8_t m_cardChecksum = 0;
276 volatile bool m_done = false;
277 volatile Error m_result = Error::OK;
278 bool m_blocking = false;
279 eastl::function<void(Error)> m_callback;
280 uint32_t m_event = 0;
281 // Async retry bookkeeping: the user's callback is preserved in m_userCallback
282 // across the internal retries, while m_callback is reused to drive each attempt.
283 eastl::function<void(Error)> m_userCallback;
284 Action m_retryAction = Action::None;
285 unsigned m_attempt = 0;
286
287 // The first (addressing) byte uses this timeout to detect a missing card.
288 // It is the one value that is deliberately generous: a present-but-slow
289 // real card can take a while to acknowledge the very first byte after the
290 // port is selected, and too short a value here is what makes a good card
291 // spuriously report "no card". It does not affect mid-transfer timing
292 // (a present card acknowledges the first byte well before the limit).
293 static constexpr uint32_t c_ackTimeoutShort = 0x4000;
294 // Sony cards introduce a ~31000 cycle gap before the acknowledge that
295 // follows the seventh byte of a read; the long timeout covers it with a
296 // comfortable margin and is also used as the general mid-transfer timeout.
297 static constexpr uint32_t c_ackTimeoutLong = 0x40000;
298 static constexpr uint32_t c_ackHighTimeout = 0x4000;
299 // Settle time after asserting the port select, before the first clock.
300 static constexpr unsigned c_selectDelay = 100;
301 // Recovery window held with the bus deselected (/CS high) between sector
302 // transactions. The retail BIOS gets this for free by pacing each sector on
303 // a vblank boundary (~16ms apart); we issue transactions back-to-back, so a
304 // slow third-party card can be reselected before it has finished recovering
305 // from the previous read and then serve stale (wrong-but-valid) sector data.
306 // busyLoop(100) is ~23us, so ~70000 approximates the BIOS's ~16ms. Tune down
307 // toward the smallest reliable value once a working floor is found on hardware.
308 static constexpr unsigned c_interTransactionDelay = 70000;
309 // How many times a whole-sector transient failure is retried.
310 static constexpr unsigned c_maxAttempts = 3;
311 // Spin-loop bound for the blocking IRQ path: large enough never to trip
312 // during a normal ~8ms transfer, small enough to bail on a dead bus.
313 static constexpr uint32_t c_irqWatchdog = 0x800000;
314};
315
316} // namespace psyqo
A low level driver for the PlayStation memory cards.
Definition memory-card.hh:67
Error
The error codes returned by every memory card operation.
Definition memory-card.hh:75
static constexpr uint32_t c_sectorSize
Definition memory-card.hh:101
static constexpr uint32_t sectorCount()
Definition memory-card.hh:108
static constexpr uint32_t c_sectorsPerBlock
Definition memory-card.hh:105
Port
The memory card port to talk to.
Definition memory-card.hh:98
Error writeSectorBlocking(Port port, uint16_t sector, const void *buffer)
Writes a single 128-byte sector synchronously.
Definition memory-card.cpp:704
static constexpr uint32_t sectorSize()
Definition memory-card.hh:107
static constexpr uint32_t blockCount()
Definition memory-card.hh:110
TaskQueue::Task scheduleReadSector(Port port, uint16_t sector, void *buffer, Error *resultOut)
Definition memory-card.cpp:782
WriteSectorAwaiter writeSector(Port port, uint16_t sector, const void *buffer)
Definition memory-card.hh:206
static const char * errorMessage(Error error)
Returns a human readable string for an error code.
Definition memory-card.cpp:62
void writeSector(Port port, uint16_t sector, const void *buffer, eastl::function< void(Error)> &&callback)
Definition memory-card.cpp:742
Error readSectorBlocking(Port port, uint16_t sector, void *buffer)
Reads a single 128-byte sector synchronously.
Definition memory-card.cpp:700
ReadSectorAwaiter readSector(Port port, uint16_t sector, void *buffer)
Definition memory-card.hh:205
void readSector(Port port, uint16_t sector, void *buffer, eastl::function< void(Error)> &&callback)
Definition memory-card.cpp:718
TaskQueue::Task scheduleWriteSector(Port port, uint16_t sector, const void *buffer, Error *resultOut)
Definition memory-card.cpp:792
static constexpr uint32_t c_blockSize
Definition memory-card.hh:103
static constexpr uint32_t c_sectorCount
Definition memory-card.hh:102
Error probeBlocking(Port port)
Probes the card for presence.
Definition memory-card.cpp:708
static constexpr uint32_t blockSize()
Definition memory-card.hh:109
void prepare()
Prepares the SIO0 bus for memory card access.
Definition memory-card.cpp:102
static constexpr uint32_t c_blockCount
Definition memory-card.hh:104
The Task class.
Definition task.hh:140
uint32_t out
Definition cpu.c:62
unsigned timeout
Definition dma.c:115
char in[50]
Definition memcpy.c:74
void * result
Definition memcpy.c:47
Definition lua.hh:38
Definition memory-card.hh:163
Error await_resume()
Definition memory-card.hh:174
bool await_ready() const
Definition memory-card.hh:166
ReadSectorAwaiter(MemoryCard &device, Port port, uint16_t sector, void *buffer)
Definition memory-card.hh:164
void await_suspend(std::coroutine_handle< U > handle)
Definition memory-card.hh:168
Definition memory-card.hh:184
WriteSectorAwaiter(MemoryCard &device, Port port, uint16_t sector, const void *buffer)
Definition memory-card.hh:185
bool await_ready() const
Definition memory-card.hh:187
void await_suspend(std::coroutine_handle< U > handle)
Definition memory-card.hh:189
Error await_resume()
Definition memory-card.hh:195
static void * buffer
Definition syscalls.h:231
static int sector
Definition syscalls.h:468
void void(ptr, size)
void uint32_t(classId, spec)