Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

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}