psx_cdstream/request.rs
1//! Read requests and what comes back from them.
2
3/// Bytes in one data sector.
4pub const SECTOR_BYTES: u32 = 2048;
5/// Words in one data sector.
6pub const SECTOR_WORDS: u32 = 512;
7
8/// How urgent a request is. Lower numbers are served first.
9///
10/// The transport only orders by this number; what each level means is the
11/// scheduler's business. The design maps demand loads to [`URGENT`](Self::URGENT)
12/// and speculative prefetch to [`BACKGROUND`](Self::BACKGROUND).
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct Priority(pub u8);
15
16impl Priority {
17 /// Served before everything else.
18 pub const URGENT: Self = Self(0);
19 /// The default.
20 pub const NORMAL: Self = Self(128);
21 /// Served only when nothing more urgent waits.
22 pub const BACKGROUND: Self = Self(255);
23}
24
25impl Default for Priority {
26 fn default() -> Self {
27 Self::NORMAL
28 }
29}
30
31/// A run of sectors to read into memory.
32///
33/// Built with [`Request::new_raw`], then refined with the `with_*` methods.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct Request {
36 pub(crate) lba: u32,
37 pub(crate) sectors: u32,
38 pub(crate) destination: *mut u32,
39 /// 0 means every sector is stored whole.
40 pub(crate) byte_len: u32,
41 pub(crate) priority: Priority,
42 pub(crate) resumes: u8,
43}
44
45impl Request {
46 /// Read `sectors` sectors starting at `lba` (program-relative, shifted by
47 /// the loader's disc base like every SDK disc read) into `destination`.
48 ///
49 /// # Safety
50 ///
51 /// `destination` must be word-aligned and valid for writes of
52 /// `sectors * 2048` bytes, and nothing else may read or write that memory
53 /// from the moment the request is submitted until it has finished
54 /// (see [`Engine::state`](crate::Engine::state)). The transport writes
55 /// from the interrupt handler, so the memory is not held by any borrow the
56 /// compiler can see. Memory that is only ever read through `read_volatile`
57 /// until the request finishes meets this.
58 pub const unsafe fn new_raw(lba: u32, sectors: u32, destination: *mut u32) -> Self {
59 Request {
60 lba,
61 sectors,
62 destination,
63 byte_len: 0,
64 priority: Priority::NORMAL,
65 resumes: 0,
66 }
67 }
68
69 /// Serve this request at `priority`.
70 #[must_use]
71 pub const fn with_priority(mut self, priority: Priority) -> Self {
72 self.priority = priority;
73 self
74 }
75
76 /// Let the transport pick this request up again from where it stopped,
77 /// up to `times` times, when the drive reports an error mid-transfer.
78 /// Each resume costs the recovery pause and one seek. With 0 (the
79 /// default) an error ends the request as [`Outcome::Failed`] and the
80 /// caller decides.
81 #[must_use]
82 pub const fn with_resumes(mut self, times: u8) -> Self {
83 self.resumes = times;
84 self
85 }
86
87 /// Store only `bytes` bytes instead of `sectors * 2048`: the last sector
88 /// is popped whole but only its first words, rounded up to a word, are
89 /// written, so a transfer never writes past a chunk that does not end on
90 /// a sector boundary.
91 ///
92 /// `bytes` must lie in `(sectors - 1) * 2048 + 1 ..= sectors * 2048`;
93 /// anything else is ignored (every sector stored whole). The destination
94 /// then only has to be valid for `bytes` rounded up to a word.
95 #[must_use]
96 pub const fn with_byte_len(mut self, bytes: u32) -> Self {
97 let sectors = self.sectors;
98 if sectors > 0 && bytes > (sectors - 1) * SECTOR_BYTES && bytes <= sectors * SECTOR_BYTES {
99 self.byte_len = bytes;
100 }
101 self
102 }
103
104 /// First sector.
105 pub const fn lba(&self) -> u32 {
106 self.lba
107 }
108
109 /// Sector count.
110 pub const fn sectors(&self) -> u32 {
111 self.sectors
112 }
113
114 /// Priority.
115 pub const fn priority(&self) -> Priority {
116 self.priority
117 }
118
119 /// The sector after the last one, where a request that can chain onto
120 /// this one starts.
121 pub const fn end_lba(&self) -> u32 {
122 self.lba.wrapping_add(self.sectors)
123 }
124
125 /// The rest of this request after `received` sectors landed: the request
126 /// to submit to continue an aborted or failed transfer. `None` when
127 /// nothing is left.
128 ///
129 /// The result is a request like any other. It starts with a fresh seek,
130 /// at `lba + received`, into the destination advanced by `received`
131 /// sectors, with the same priority and resume budget.
132 ///
133 /// # Safety
134 ///
135 /// `received` must be a count the transport reported for this request
136 /// (or lower): the memory the new request writes is the tail of this
137 /// request's destination, and the safety contract of
138 /// [`new_raw`](Self::new_raw) applies to it again.
139 pub unsafe fn remaining_after(&self, received: u32) -> Option<Request> {
140 if received >= self.sectors {
141 return None;
142 }
143 // SAFETY: `received < sectors`, so the advanced pointer stays inside
144 // the `sectors * 2048` bytes the caller vouched for in `new_raw`.
145 let destination = unsafe {
146 self.destination
147 .add(received as usize * SECTOR_WORDS as usize)
148 };
149 let byte_len = if self.byte_len == 0 {
150 0
151 } else {
152 self.byte_len - received * SECTOR_BYTES
153 };
154 Some(Request {
155 lba: self.lba + received,
156 sectors: self.sectors - received,
157 destination,
158 byte_len,
159 priority: self.priority,
160 resumes: self.resumes,
161 })
162 }
163
164 /// Words to store from sector `index` of this request.
165 pub(crate) const fn store_words(&self, index: u32) -> usize {
166 if self.byte_len == 0 {
167 return SECTOR_WORDS as usize;
168 }
169 let remaining = self.byte_len - index * SECTOR_BYTES;
170 let bytes = if remaining < SECTOR_BYTES {
171 remaining
172 } else {
173 SECTOR_BYTES
174 };
175 bytes.div_ceil(4) as usize
176 }
177}
178
179/// Names a submitted request.
180#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
181pub struct Ticket(pub(crate) u32);
182
183impl Ticket {
184 /// The ticket's number, for logs.
185 pub const fn id(self) -> u32 {
186 self.0
187 }
188}
189
190/// Why a request was not accepted.
191#[derive(Clone, Copy, Debug, PartialEq, Eq)]
192pub enum SubmitError {
193 /// [`QUEUE_DEPTH`](crate::QUEUE_DEPTH) requests are already waiting.
194 QueueFull,
195 /// The destination pointer is null.
196 NullDestination,
197 /// The destination is not word-aligned.
198 MisalignedDestination,
199 /// The request has no sectors.
200 Empty,
201}
202
203/// How a request ended.
204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
205pub enum Outcome {
206 /// Every sector landed.
207 Done,
208 /// Stopped by a cancel or an audio lease. `received` sectors landed.
209 Cancelled,
210 /// The drive or the controller failed. `received` sectors landed.
211 Failed(Failure),
212}
213
214/// A finished request.
215#[derive(Clone, Copy, Debug, PartialEq, Eq)]
216pub struct Completion {
217 /// Which request.
218 pub ticket: Ticket,
219 /// How it ended.
220 pub outcome: Outcome,
221 /// Sectors stored at the destination, starting from its first word.
222 pub received: u32,
223}
224
225/// Where a request is in its life.
226#[derive(Clone, Copy, Debug, PartialEq, Eq)]
227pub enum RequestState {
228 /// Waiting behind other requests, or for the drive to be handed back.
229 Queued,
230 /// The drive is working on it; `received` sectors have landed.
231 Active {
232 /// Sectors stored so far.
233 received: u32,
234 },
235 /// It ended. Results of the last [`RECENT_RESULTS`](crate::RECENT_RESULTS)
236 /// requests are kept.
237 Finished(Completion),
238 /// Not queued, not active, and no longer remembered (or never submitted).
239 Unknown,
240}
241
242/// A transfer failure, as the code the transport records for it.
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
244pub struct Failure(pub(crate) u32);
245
246/// What a [`Failure`] code says.
247#[derive(Clone, Copy, Debug, PartialEq, Eq)]
248pub enum FailureKind {
249 /// The parameter FIFO never had room for this command.
250 CommandRefused {
251 /// The command byte.
252 command: u8,
253 },
254 /// The data FIFO never filled after a sector interrupt.
255 DataNotReady,
256 /// The drive answered with an error.
257 Drive {
258 /// Status byte of the error response.
259 status: u8,
260 /// Error code byte.
261 code: u8,
262 },
263 /// An interrupt arrived that the current phase has no use for.
264 UnexpectedInterrupt {
265 /// The interrupt flag.
266 flag: u8,
267 },
268 /// No progress for the configured number of VBlanks.
269 Watchdog,
270 /// A code this version does not produce.
271 Unknown,
272}
273
274impl Failure {
275 pub(crate) const fn command_refused(command: u8) -> Self {
276 Failure(0xfe00_0000 | (command as u32) << 8)
277 }
278
279 pub(crate) const fn data_not_ready(phase: u32) -> Self {
280 Failure(0xfa00_0000 | phase)
281 }
282
283 pub(crate) const fn drive(status: u8, phase: u32, code: u8) -> Self {
284 Failure(0x0500_0000 | (status as u32) << 16 | phase << 8 | code as u32)
285 }
286
287 pub(crate) const fn unexpected(phase: u32, flag: u8) -> Self {
288 Failure(0xf900_0000 | phase << 8 | flag as u32)
289 }
290
291 pub(crate) const fn watchdog(phase: u32) -> Self {
292 Failure(0xff00_0000 | phase)
293 }
294
295 /// The raw code, as published in `PSX_CD_STATS`.
296 pub const fn code(self) -> u32 {
297 self.0
298 }
299
300 /// Decode the code.
301 pub const fn kind(self) -> FailureKind {
302 match self.0 >> 24 {
303 0xfe => FailureKind::CommandRefused {
304 command: (self.0 >> 8) as u8,
305 },
306 0xfa => FailureKind::DataNotReady,
307 0x05 => FailureKind::Drive {
308 status: (self.0 >> 16) as u8,
309 code: self.0 as u8,
310 },
311 0xf9 => FailureKind::UnexpectedInterrupt { flag: self.0 as u8 },
312 0xff => FailureKind::Watchdog,
313 _ => FailureKind::Unknown,
314 }
315 }
316}