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

psx_cdstream/
engine.rs

1//! The transport state machine ([`Engine`]) and its configuration and counters.
2
3use crate::hw::CdHw;
4use crate::request::{
5    Completion, Failure, Outcome, Priority, Request, RequestState, SubmitError, Ticket,
6    SECTOR_WORDS,
7};
8use psx_hw::cd::{
9    irq as flag, CMD_PAUSE, CMD_READN, CMD_SEEKL, CMD_SETLOC, CMD_SETMODE, MODE_DOUBLE_SPEED,
10};
11
12/// Requests that can wait behind the active one.
13pub const QUEUE_DEPTH: usize = 4;
14/// Finished requests whose results [`Engine::state`] still remembers.
15pub const RECENT_RESULTS: usize = 8;
16
17/// Where the controller is in a transfer. The numbers are published in
18/// `PSX_CD_STATS` and match the earlier game-local transports.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20#[repr(u32)]
21pub enum Phase {
22    /// Nothing has run since the engine was created.
23    Idle = 0,
24    /// Setloc sent, waiting for its acknowledge.
25    SettingTarget = 1,
26    /// SeekL sent, waiting for its acknowledge.
27    StartingSeek = 2,
28    /// Seek acknowledged, waiting for it to complete.
29    Seeking = 3,
30    /// Setmode sent, waiting for its acknowledge.
31    SettingMode = 4,
32    /// ReadN sent, waiting for its acknowledge. Sectors may already arrive.
33    StartingRead = 5,
34    /// Sectors are arriving.
35    Reading = 6,
36    /// Pause sent, waiting for its acknowledge.
37    Stopping = 7,
38    /// Pause acknowledged, waiting for the drive to stop.
39    WaitingForStop = 8,
40    /// The drive is stopped and the last transfer ended cleanly.
41    Done = 9,
42    /// A transfer failed; the drive's state is unknown. The next transfer
43    /// starts with a recovery Pause.
44    Failed = 10,
45}
46
47impl Phase {
48    const fn is_terminal(self) -> bool {
49        matches!(self, Phase::Idle | Phase::Done | Phase::Failed)
50    }
51}
52
53/// Who may program the controller.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum Owner {
56    /// Nobody has attached the transport yet, or it was detached: the
57    /// program uses the blocking `SectorReader` or drives the controller
58    /// itself, and no request starts.
59    Boot,
60    /// The transport reads. Requests run.
61    Data,
62    /// CD-DA or XA playback holds the drive. Requests queue but do not start.
63    Audio,
64}
65
66/// Where an audio lease request stands.
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum LeaseState {
69    /// No lease is held or wanted.
70    None,
71    /// Asked for; the in-flight request is being stopped.
72    Pending,
73    /// The drive is the audio code's. The transport masks its interrupt
74    /// source and starts nothing until the lease is released.
75    Granted,
76}
77
78/// Whether the CD interrupt source stays open between transfers.
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum IrqMaskPolicy {
81    /// Open from attach until detach or an audio lease.
82    AlwaysOn,
83    /// Open only while a request is queued or active, so polled code
84    /// (the SDK's `SectorReader`) never shares the controller with the
85    /// handler between transfers.
86    OnlyWhileBusy,
87}
88
89/// How the engine behaves.
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub struct Config {
92    /// A transfer with no sector, response or phase change for this many
93    /// VBlanks fails with [`FailureKind::Watchdog`](crate::FailureKind).
94    pub timeout_vblanks: u32,
95    /// See [`IrqMaskPolicy`].
96    pub irq_mask_policy: IrqMaskPolicy,
97    /// Start the first transfer after an audio lease with a recovery Pause,
98    /// so a drive that was playing or parked at an audio position is stopped
99    /// before the seek. Costs one command; skip it only if the audio code
100    /// always ends with its own Pause.
101    pub pause_after_audio: bool,
102    /// Measure how long each handler call takes, with Timer 2, which the
103    /// transport then owns. The longest call is `max_irq_ticks`, in
104    /// system-clock / 8 ticks that wrap after about 15.5 ms.
105    pub time_handler: bool,
106    /// Read at double speed (150 sectors per second, the default and the
107    /// only rate a streamer wants). Clear it for single speed (75 sectors
108    /// per second), which halves the CPU the sector pops cost per second of
109    /// reading; it applies from the next transfer.
110    pub double_speed: bool,
111}
112
113impl Config {
114    /// The defaults: 600 VBlanks, interrupt source always on, recovery pause
115    /// after audio, no handler timing, double speed.
116    pub const DEFAULT: Config = Config {
117        timeout_vblanks: 600,
118        irq_mask_policy: IrqMaskPolicy::AlwaysOn,
119        pause_after_audio: true,
120        time_handler: false,
121        double_speed: true,
122    };
123}
124
125impl Default for Config {
126    fn default() -> Self {
127        Self::DEFAULT
128    }
129}
130
131/// Counters, published as one block (`PSX_CD_STATS`) for tools that read
132/// guest memory. All wrap at 2^32.
133#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
134#[repr(C)]
135pub struct StreamStats {
136    /// Interrupts handled.
137    pub irq_count: u32,
138    /// Sectors stored at a destination.
139    pub sectors: u32,
140    /// Sectors popped and dropped: after a cancel, after the last sector of
141    /// a request while the Pause was on its way, or with nothing to store to.
142    pub discarded_sectors: u32,
143    /// The longest handler call, in Timer 2 ticks (needs
144    /// [`Config::time_handler`]).
145    pub max_irq_ticks: u32,
146    /// The current [`Phase`], as its number.
147    pub phase: u32,
148    /// The code of the latest failure ([`Failure::code`]); 0 after a start.
149    pub error: u32,
150    /// Requests that ended [`Outcome::Done`].
151    pub requests_done: u32,
152    /// Requests that ended [`Outcome::Cancelled`].
153    pub requests_cancelled: u32,
154    /// Requests that ended [`Outcome::Failed`].
155    pub requests_failed: u32,
156    /// Requests started by chaining onto the previous one, without a seek.
157    pub chained: u32,
158    /// Requests put back in the queue to resume after an error.
159    pub resumed: u32,
160    /// Submissions refused because the queue was full.
161    pub rejected: u32,
162}
163
164#[derive(Clone, Copy)]
165struct Active {
166    ticket: Ticket,
167    request: Request,
168    /// Sectors a request resumed from an error had stored before this
169    /// activation; `request` is only the rest.
170    base: u32,
171    received: u32,
172    cancelled: bool,
173    resumes_left: u8,
174}
175
176#[derive(Clone, Copy)]
177struct Queued {
178    ticket: Ticket,
179    request: Request,
180    /// Sectors already stored by earlier activations (see `Active::base`).
181    base: u32,
182    /// Submission order; resumed requests carry 0 so they go first within
183    /// their priority.
184    seq: u32,
185    resumes_left: u8,
186}
187
188/// The CD transport.
189///
190/// One engine owns the drive. Foreground code submits [`Request`]s; the CD
191/// interrupt handler calls [`Engine::on_interrupt`] once per interrupt, and
192/// each call advances the machine by one controller event. Nothing in the
193/// machine waits for the drive: a command is written and the machine returns,
194/// and the response arrives as the next interrupt.
195///
196/// # A transfer
197///
198/// ```text
199/// Setloc ──INT3──▶ SeekL ──INT3──▶ (seek) ──INT2──▶ Setmode(double speed)
200///   ──INT3──▶ ReadN ──INT3──▶ INT1 × n (one sector each) ──▶ Pause
201///   ──INT3──▶ (stop) ──INT2──▶ stopped
202/// ```
203///
204/// The read is seek-first (Setloc, SeekL, then Setmode and ReadN), the form
205/// the BIOS uses and the only one proven to stream byte-perfect on a console:
206/// a bare Setloc plus ReadN starts delivering while the mechanism is still
207/// settling. Each INT1 pops one sector into the request's destination. After
208/// a failure the drive's state is unknown, so the next transfer opens with a
209/// recovery Pause.
210///
211/// # Chaining
212///
213/// When the last sector of a request lands and the best queued request starts
214/// at the next sector, the machine swaps the destination inside that same
215/// interrupt and keeps reading: no Pause, no seek. A request is `Done` the
216/// moment its last sector lands, even though the drive is still running on
217/// to the Pause; [`Engine::is_idle`] says when it has stopped.
218///
219/// # Abort
220///
221/// [`Engine::cancel`] only sets a flag. The handler drops the next sector it
222/// pops and issues Pause itself. Pausing from the foreground while sectors
223/// keep arriving lost the Pause acknowledge on silicon (no interrupt for 600
224/// VBlanks), so the foreground never does it.
225///
226/// # Generic over the drive
227///
228/// On the console the one instance lives in the crate's global and is reached
229/// through the free functions at the crate root; on the host a scripted drive
230/// stands in.
231pub struct Engine<H: CdHw> {
232    hw: H,
233    config: Config,
234    owner: Owner,
235    lease_pending: bool,
236    /// Whether the CPU-side CD source is currently open, as last written.
237    source_open: bool,
238    /// The first transfer starts with a recovery Pause.
239    recover_next: bool,
240    phase: Phase,
241    /// Set while the Pause that opens a transfer after a failure is running.
242    recovering: bool,
243    error: u32,
244    /// VBlank count at the last sign of life.
245    stamp: u32,
246    /// Absolute BCD Setloc parameters of the active request.
247    location: [u8; 3],
248    active: Option<Active>,
249    /// One spare slot beyond [`QUEUE_DEPTH`]: a failing request that resumes
250    /// goes back in the queue even when it is full.
251    queue: [Option<Queued>; QUEUE_DEPTH + 1],
252    results: [Option<Completion>; RECENT_RESULTS],
253    results_written: u32,
254    next_ticket: u32,
255    next_seq: u32,
256    stats: StreamStats,
257}
258
259impl<H: CdHw> Engine<H> {
260    /// An engine in the [`Owner::Boot`] state: nothing runs until
261    /// [`attach`](Self::attach).
262    pub const fn new(hw: H, config: Config) -> Self {
263        Engine {
264            hw,
265            config,
266            owner: Owner::Boot,
267            lease_pending: false,
268            source_open: false,
269            recover_next: false,
270            phase: Phase::Idle,
271            recovering: false,
272            error: 0,
273            stamp: 0,
274            location: [0; 3],
275            active: None,
276            queue: [None; QUEUE_DEPTH + 1],
277            results: [None; RECENT_RESULTS],
278            results_written: 0,
279            next_ticket: 1,
280            next_seq: 1,
281            stats: StreamStats {
282                irq_count: 0,
283                sectors: 0,
284                discarded_sectors: 0,
285                max_irq_ticks: 0,
286                phase: 0,
287                error: 0,
288                requests_done: 0,
289                requests_cancelled: 0,
290                requests_failed: 0,
291                chained: 0,
292                resumed: 0,
293                rejected: 0,
294            },
295        }
296    }
297
298    /// Replace the configuration. It applies from the next event.
299    pub fn configure(&mut self, config: Config) {
300        self.config = config;
301    }
302
303    /// The drive layer.
304    pub fn hw(&self) -> &H {
305        &self.hw
306    }
307
308    /// The drive layer, mutably.
309    pub fn hw_mut(&mut self) -> &mut H {
310        &mut self.hw
311    }
312
313    /// Who may program the controller.
314    pub fn owner(&self) -> Owner {
315        self.owner
316    }
317
318    /// The controller phase.
319    pub fn phase(&self) -> Phase {
320        self.phase
321    }
322
323    /// A copy of the counters, with the current phase and error filled in.
324    pub fn stats(&self) -> StreamStats {
325        StreamStats {
326            phase: self.phase as u32,
327            error: self.error,
328            ..self.stats
329        }
330    }
331
332    /// Start the longest-handler measurement over
333    /// ([`StreamStats::max_irq_ticks`] to 0), to attribute the peak to the
334    /// next stretch of work.
335    pub fn reset_max_irq_ticks(&mut self) {
336        self.stats.max_irq_ticks = 0;
337    }
338
339    /// Requests waiting behind the active one.
340    pub fn queued_count(&self) -> usize {
341        self.queue.iter().flatten().count()
342    }
343
344    /// Whether the drive is stopped and no request holds it. Queued requests
345    /// may still be waiting for their turn (while the owner is not `Data`).
346    pub fn is_idle(&self) -> bool {
347        self.phase.is_terminal() && self.active.is_none()
348    }
349
350    // ----------------------------------------------------------------- owner
351
352    /// Take the drive: [`Owner::Boot`] becomes [`Owner::Data`] and queued
353    /// requests start.
354    ///
355    /// The controller must be in a clean state: the polled reader released,
356    /// no command in flight. A no-op in any other state.
357    pub fn attach(&mut self) {
358        if self.owner != Owner::Boot {
359            return;
360        }
361        self.owner = Owner::Data;
362        self.recover_next = false;
363        self.hw.discard_response();
364        self.hw.acknowledge(flag::ACK_ALL);
365        self.settle();
366        self.sync_source();
367    }
368
369    /// Hand the drive back to polled code. Succeeds only when it is idle and
370    /// nothing is queued or wanted; [`cancel_all`](Self::cancel_all) and wait
371    /// for [`is_idle`](Self::is_idle) first.
372    pub fn detach(&mut self) -> bool {
373        if self.owner != Owner::Data
374            || !self.is_idle()
375            || self.queued_count() != 0
376            || self.lease_pending
377        {
378            return false;
379        }
380        self.owner = Owner::Boot;
381        self.sync_source();
382        true
383    }
384
385    /// Where an audio lease stands.
386    pub fn lease_state(&self) -> LeaseState {
387        if self.owner == Owner::Audio {
388            LeaseState::Granted
389        } else if self.lease_pending {
390            LeaseState::Pending
391        } else {
392            LeaseState::None
393        }
394    }
395
396    /// Ask for the drive on behalf of CD-DA or XA playback.
397    ///
398    /// New requests are still accepted but queue. A request in flight is
399    /// aborted at its next sector (it ends [`Outcome::Cancelled`] with what
400    /// landed; resume it with [`Request::remaining_after`] after the lease).
401    /// When the drive has stopped the lease is granted: the CD interrupt
402    /// source is masked, and the audio code may program the controller
403    /// directly. Poll [`lease_state`](Self::lease_state) until it reads
404    /// [`LeaseState::Granted`] if this returns `Pending`.
405    ///
406    /// Before [`attach`](Self::attach) the program already holds the drive
407    /// and this returns `Granted` without changing anything.
408    pub fn request_audio_lease(&mut self) -> LeaseState {
409        match self.owner {
410            Owner::Boot => return LeaseState::Granted,
411            Owner::Audio => return LeaseState::Granted,
412            Owner::Data => {}
413        }
414        self.lease_pending = true;
415        if let Some(active) = self.active.as_mut() {
416            active.cancelled = true;
417        }
418        if self.is_idle() {
419            self.grant_lease();
420        }
421        self.sync_source();
422        self.lease_state()
423    }
424
425    /// Take the drive for audio only if it can be had right now, without
426    /// waiting and without leaving anything behind.
427    ///
428    /// Returns `true` when the lease is granted (the drive was idle, or the
429    /// program still holds it before [`attach`](Self::attach)). Returns
430    /// `false` when a request is in flight: nothing changes, no lease is left
431    /// pending and the request is not aborted, so a read submitted afterwards
432    /// cannot end up queued behind a lease that is granted later. Ask again
433    /// when the drive is idle.
434    ///
435    /// [`request_audio_lease`](Self::request_audio_lease) is for audio that
436    /// must displace a read; this is for audio that only wants the drive
437    /// while nobody else is using it.
438    pub fn try_audio_lease(&mut self) -> bool {
439        match self.owner {
440            Owner::Boot | Owner::Audio => true,
441            Owner::Data => {
442                if self.lease_pending || !self.is_idle() {
443                    return false;
444                }
445                self.grant_lease();
446                self.sync_source();
447                true
448            }
449        }
450    }
451
452    /// Withdraw a lease that was asked for and not yet granted. Returns
453    /// whether one was pending.
454    ///
455    /// Call it before submitting a read that must not wait for audio: a
456    /// pending lease is granted the moment the drive stops, which parks every
457    /// read queued behind it until the audio code gives the drive back. The
458    /// request that was aborted for the lease still ends
459    /// [`Outcome::Cancelled`] (resume it with [`Request::remaining_after`]).
460    /// A lease that is already granted is ended with
461    /// [`release_audio_lease`](Self::release_audio_lease).
462    pub fn withdraw_audio_lease(&mut self) -> bool {
463        if self.owner == Owner::Data && self.lease_pending {
464            self.lease_pending = false;
465            return true;
466        }
467        false
468    }
469
470    /// Give the drive back after audio playback, or withdraw a lease that is
471    /// still pending. Returns whether there was a lease to end.
472    ///
473    /// The audio code must have ended playback with Pause (never Stop: the
474    /// motor then spins down, reports status 0 for a second or two, and reads
475    /// started in that window failed on a console). The next transfer starts
476    /// with a recovery Pause if [`Config::pause_after_audio`] is set.
477    pub fn release_audio_lease(&mut self) -> bool {
478        if self.withdraw_audio_lease() {
479            return true;
480        }
481        if self.owner != Owner::Audio {
482            return false;
483        }
484        self.hw.discard_response();
485        self.hw.acknowledge(flag::ACK_ALL);
486        self.owner = Owner::Data;
487        self.recover_next = self.config.pause_after_audio;
488        self.settle();
489        self.sync_source();
490        true
491    }
492
493    fn grant_lease(&mut self) {
494        self.owner = Owner::Audio;
495        self.lease_pending = false;
496    }
497
498    // -------------------------------------------------------------- requests
499
500    /// Queue a request. It starts at once when the drive is free.
501    ///
502    /// Higher priority (lower number) goes first; equal priorities go in
503    /// submission order, except that one starting at the sector after the
504    /// active request's last is picked ahead of its peers so it chains
505    /// without a seek.
506    pub fn submit(&mut self, request: Request) -> Result<Ticket, SubmitError> {
507        if request.destination.is_null() {
508            return Err(SubmitError::NullDestination);
509        }
510        if request.destination as usize & 3 != 0 {
511            return Err(SubmitError::MisalignedDestination);
512        }
513        if request.sectors == 0 {
514            return Err(SubmitError::Empty);
515        }
516        if self.queued_count() >= QUEUE_DEPTH {
517            self.stats.rejected = self.stats.rejected.wrapping_add(1);
518            return Err(SubmitError::QueueFull);
519        }
520        let ticket = self.new_ticket();
521        let seq = self.next_seq;
522        self.next_seq = self.next_seq.wrapping_add(1).max(1);
523        self.enqueue(Queued {
524            ticket,
525            request,
526            base: 0,
527            seq,
528            resumes_left: request.resumes,
529        });
530        self.service();
531        // A free drive picks the request up now; a busy one keeps it queued
532        // and `settle` runs when it frees up.
533        self.settle();
534        self.sync_source();
535        Ok(ticket)
536    }
537
538    /// Stop a request. Returns whether `ticket` was queued or active.
539    ///
540    /// A queued request is dropped at once ([`Outcome::Cancelled`], 0
541    /// sectors). The active one is flagged: the handler drops the next
542    /// sector, pauses the drive, and the request ends `Cancelled` with the
543    /// sectors that had landed.
544    pub fn cancel(&mut self, ticket: Ticket) -> bool {
545        if self.active.is_some_and(|active| active.ticket == ticket) {
546            self.cancel_active();
547            return true;
548        }
549        for index in 0..self.queue.len() {
550            if self.queue[index].is_some_and(|queued| queued.ticket == ticket) {
551                self.drop_queued(index);
552                return true;
553            }
554        }
555        false
556    }
557
558    /// [`cancel`](Self::cancel) everything: the queue and the active request.
559    pub fn cancel_all(&mut self) {
560        for index in 0..self.queue.len() {
561            self.drop_queued(index);
562        }
563        if self.active.is_some() {
564            self.cancel_active();
565        }
566    }
567
568    fn cancel_active(&mut self) {
569        self.trace(2, 0);
570        if let Some(active) = self.active.as_mut() {
571            active.cancelled = true;
572        }
573    }
574
575    fn drop_queued(&mut self, index: usize) {
576        if let Some(queued) = self.queue[index].take() {
577            self.record(Completion {
578                ticket: queued.ticket,
579                outcome: Outcome::Cancelled,
580                received: queued.base,
581            });
582        }
583    }
584
585    /// Where `ticket` is. Also runs the no-progress check ([`service`](Self::service)).
586    pub fn state(&mut self, ticket: Ticket) -> RequestState {
587        self.service();
588        self.sync_source();
589        if let Some(active) = &self.active {
590            if active.ticket == ticket {
591                return RequestState::Active {
592                    received: active.base + active.received,
593                };
594            }
595        }
596        if self.queue.iter().flatten().any(|q| q.ticket == ticket) {
597            return RequestState::Queued;
598        }
599        if let Some(done) = self.results.iter().flatten().find(|c| c.ticket == ticket) {
600            return RequestState::Finished(*done);
601        }
602        RequestState::Unknown
603    }
604
605    /// Foreground upkeep: fail a transfer that has made no progress for
606    /// [`Config::timeout_vblanks`]. [`state`](Self::state) calls this; a main
607    /// loop that never asks about a ticket should call it once per frame.
608    pub fn service(&mut self) {
609        if self.owner != Owner::Data || self.phase.is_terminal() {
610            return;
611        }
612        let now = self.hw.vblank_count();
613        if now.wrapping_sub(self.stamp) > self.config.timeout_vblanks {
614            self.fail(Failure::watchdog(self.phase as u32));
615            self.sync_source();
616        }
617    }
618
619    // ------------------------------------------------------------- interrupt
620
621    /// Handle one CD interrupt. Called by the interrupt handler (the crate's
622    /// exception wrapper on the console, the test harness on the host).
623    pub fn on_interrupt(&mut self) {
624        let started = self.hw.clock();
625        self.stats.irq_count = self.stats.irq_count.wrapping_add(1);
626        if self.owner == Owner::Data {
627            let code = self.hw.interrupt_code();
628            self.trace(1, code);
629            self.dispatch(code);
630            self.sync_source();
631        }
632        let ticks = u32::from(self.hw.clock().wrapping_sub(started));
633        self.stats.max_irq_ticks = self.stats.max_irq_ticks.max(ticks);
634    }
635
636    fn dispatch(&mut self, code: u8) {
637        if code == flag::DATA_READY {
638            match self.take_sector() {
639                Ok(()) => {
640                    self.hw.discard_response();
641                    self.hw.acknowledge(code);
642                    self.after_sector();
643                }
644                Err(failure) => {
645                    self.hw.discard_response();
646                    self.hw.acknowledge(flag::ACK_ALL);
647                    self.fail(failure);
648                }
649            }
650            return;
651        }
652        if code == flag::ERROR {
653            let (status, detail) = self.hw.error_response();
654            self.hw.discard_response();
655            self.hw.acknowledge(flag::ACK_ALL);
656            self.fail(Failure::drive(status, self.phase as u32, detail));
657            return;
658        }
659        self.hw.discard_response();
660        self.hw.acknowledge(code);
661        match (self.phase, code) {
662            (Phase::SettingTarget, flag::ACKNOWLEDGE) => {
663                self.command(CMD_SEEKL, &[], Phase::StartingSeek)
664            }
665            (Phase::StartingSeek, flag::ACKNOWLEDGE) => self.change(Phase::Seeking),
666            (Phase::Seeking, flag::COMPLETE) => {
667                let mode = if self.config.double_speed {
668                    MODE_DOUBLE_SPEED
669                } else {
670                    0
671                };
672                self.command(CMD_SETMODE, &[mode], Phase::SettingMode)
673            }
674            (Phase::SettingMode, flag::ACKNOWLEDGE) => {
675                self.command(CMD_READN, &[], Phase::StartingRead)
676            }
677            (Phase::StartingRead, flag::ACKNOWLEDGE) => {
678                self.change(Phase::Reading);
679                if self.active.is_some_and(|a| a.cancelled) {
680                    self.pause();
681                }
682            }
683            (Phase::Stopping, flag::ACKNOWLEDGE) => self.change(Phase::WaitingForStop),
684            (Phase::WaitingForStop, flag::COMPLETE) => self.pause_complete(),
685            (_, 0) => {}
686            _ => self.fail(Failure::unexpected(self.phase as u32, code)),
687        }
688    }
689
690    /// Pop the sector the interrupt announced; store it when a request wants
691    /// it. Fails when the data FIFO never filled.
692    fn take_sector(&mut self) -> Result<(), Failure> {
693        let reading = matches!(self.phase, Phase::Reading | Phase::StartingRead);
694        let (destination, words) = match self.active {
695            Some(a) if reading && !a.cancelled && a.received < a.request.sectors => {
696                // SAFETY: `received < sectors`, so this stays inside the
697                // `sectors * 2048` bytes `Request::new_raw` vouched for.
698                let at = unsafe {
699                    a.request
700                        .destination
701                        .add(a.received as usize * SECTOR_WORDS as usize)
702                };
703                (at, a.request.store_words(a.received))
704            }
705            _ => (core::ptr::null_mut(), 0),
706        };
707        // SAFETY: `destination` is the sector's slot inside the active
708        // request's buffer, valid for `words` words by the contract of
709        // `Request::new_raw` (and unused when `words` is 0).
710        if !unsafe { self.hw.pop_sector(destination, words) } {
711            return Err(Failure::data_not_ready(self.phase as u32));
712        }
713        if words != 0 {
714            if let Some(a) = self.active.as_mut() {
715                a.received += 1;
716            }
717            self.stats.sectors = self.stats.sectors.wrapping_add(1);
718        } else {
719            self.stats.discarded_sectors = self.stats.discarded_sectors.wrapping_add(1);
720        }
721        self.stamp = self.hw.vblank_count();
722        Ok(())
723    }
724
725    /// After a sector was popped and acknowledged: finish the request, chain
726    /// onto the next, or pause for a cancel.
727    fn after_sector(&mut self) {
728        if !matches!(self.phase, Phase::Reading | Phase::StartingRead) {
729            return;
730        }
731        let Some(active) = self.active else {
732            return;
733        };
734        if active.cancelled {
735            self.pause();
736            return;
737        }
738        if active.received < active.request.sectors {
739            return;
740        }
741        self.active = None;
742        self.finish(active, Outcome::Done);
743        match self.take_chain(active.request.end_lba()) {
744            Some(next) => {
745                self.start_chained(next);
746                self.stats.chained = self.stats.chained.wrapping_add(1);
747            }
748            None => self.pause(),
749        }
750    }
751
752    fn pause_complete(&mut self) {
753        if self.recovering && self.active.is_some_and(|a| !a.cancelled) {
754            // The recovery Pause is done: start the seek it was clearing the
755            // way for.
756            self.recovering = false;
757            let location = self.location;
758            self.hw.drop_data_request();
759            self.command(CMD_SETLOC, &location, Phase::SettingTarget);
760            return;
761        }
762        self.recovering = false;
763        self.change(Phase::Done);
764        self.hw.silence_output();
765        if let Some(active) = self.active.take() {
766            self.finish(active, Outcome::Cancelled);
767        }
768        self.settle();
769    }
770
771    // -------------------------------------------------------------- stepping
772
773    fn change(&mut self, phase: Phase) {
774        self.phase = phase;
775        self.stamp = self.hw.vblank_count();
776    }
777
778    /// Send a command; its acknowledge and completion arrive as interrupts.
779    fn command(&mut self, command: u8, params: &[u8], next: Phase) {
780        self.trace(6, command);
781        if !self.hw.issue(command, params) {
782            self.fail(Failure::command_refused(command));
783            return;
784        }
785        self.change(next);
786    }
787
788    fn pause(&mut self) {
789        self.command(CMD_PAUSE, &[], Phase::Stopping);
790    }
791
792    /// End the transfer in the failed state. The active request resumes from
793    /// where it stopped if it has resumes left, else it ends `Failed`.
794    fn fail(&mut self, failure: Failure) {
795        self.trace(4, (failure.0 >> 24) as u8);
796        self.trace(5, (failure.0 >> 8) as u8);
797        self.error = failure.0;
798        self.recovering = false;
799        self.change(Phase::Failed);
800        self.hw.silence_output();
801        self.hw.acknowledge(0);
802        if let Some(active) = self.active.take() {
803            if active.cancelled {
804                self.finish(active, Outcome::Cancelled);
805            } else if active.resumes_left > 0 && active.received < active.request.sectors {
806                self.requeue_remainder(active);
807            } else {
808                self.finish(active, Outcome::Failed(failure));
809            }
810        }
811        self.settle();
812    }
813
814    fn requeue_remainder(&mut self, active: Active) {
815        // SAFETY: `received` is what this transfer stored, so the tail
816        // begins inside the request's destination (`Request::new_raw`).
817        let rest = unsafe { active.request.remaining_after(active.received) };
818        if let Some(request) = rest {
819            self.stats.resumed = self.stats.resumed.wrapping_add(1);
820            self.enqueue(Queued {
821                ticket: active.ticket,
822                request,
823                base: active.base + active.received,
824                seq: 0,
825                resumes_left: active.resumes_left - 1,
826            });
827        }
828    }
829
830    /// The drive has stopped (or is in an unknown state after a failure) and
831    /// no request holds it: grant a waiting lease, start the next request, or
832    /// stay idle.
833    fn settle(&mut self) {
834        if self.owner != Owner::Data || !self.is_ready_to_start() {
835            return;
836        }
837        if self.lease_pending {
838            self.grant_lease();
839            return;
840        }
841        if let Some(index) = self.select(None) {
842            if let Some(queued) = self.queue[index].take() {
843                self.begin(queued);
844            }
845        }
846    }
847
848    fn is_ready_to_start(&self) -> bool {
849        self.active.is_none() && matches!(self.phase, Phase::Idle | Phase::Done | Phase::Failed)
850    }
851
852    /// Start a transfer from a stopped drive.
853    fn begin(&mut self, queued: Queued) {
854        let recover = self.phase == Phase::Failed || self.recover_next;
855        self.recover_next = false;
856        self.error = 0;
857        self.active = Some(Active {
858            ticket: queued.ticket,
859            request: queued.request,
860            base: queued.base,
861            received: 0,
862            cancelled: false,
863            resumes_left: queued.resumes_left,
864        });
865        self.location =
866            psx_io::cd::lba_to_bcd_msf(psx_io::disc_base::shift_lba(queued.request.lba));
867        self.trace(3, 0);
868        if recover {
869            // The drive may still be reading or playing: stop it, then seek.
870            self.recovering = true;
871            self.pause();
872        } else {
873            self.recovering = false;
874            self.hw.drop_data_request();
875            let location = self.location;
876            self.command(CMD_SETLOC, &location, Phase::SettingTarget);
877        }
878    }
879
880    /// Continue reading into the next request with no command in between.
881    fn start_chained(&mut self, queued: Queued) {
882        self.active = Some(Active {
883            ticket: queued.ticket,
884            request: queued.request,
885            base: queued.base,
886            received: 0,
887            cancelled: false,
888            resumes_left: queued.resumes_left,
889        });
890        self.stamp = self.hw.vblank_count();
891    }
892
893    /// The queued request to chain onto a request that ended just before
894    /// `end_lba`: the best-ranked one, if it starts exactly there.
895    fn take_chain(&mut self, end_lba: u32) -> Option<Queued> {
896        // No owner or lease check: the handler only runs for the data owner,
897        // and a lease request aborts the active request before it can end.
898        let index = self.select(Some(end_lba))?;
899        if self.queue[index]?.request.lba != end_lba {
900            return None;
901        }
902        self.queue[index].take()
903    }
904
905    /// Index of the best queued request: most urgent priority first, then
906    /// one that continues from `contiguous_with`, then submission order.
907    fn select(&self, contiguous_with: Option<u32>) -> Option<usize> {
908        let rank = |q: &Queued| -> (Priority, bool, u32) {
909            (
910                q.request.priority,
911                contiguous_with != Some(q.request.lba),
912                q.seq,
913            )
914        };
915        let mut best: Option<usize> = None;
916        for (index, slot) in self.queue.iter().enumerate() {
917            let Some(candidate) = slot else {
918                continue;
919            };
920            let better = match best.and_then(|b| self.queue[b].as_ref()) {
921                None => true,
922                Some(current) => rank(candidate) < rank(current),
923            };
924            if better {
925                best = Some(index);
926            }
927        }
928        best
929    }
930
931    fn enqueue(&mut self, queued: Queued) {
932        if let Some(slot) = self.queue.iter_mut().find(|slot| slot.is_none()) {
933            *slot = Some(queued);
934        }
935    }
936
937    fn new_ticket(&mut self) -> Ticket {
938        let ticket = Ticket(self.next_ticket);
939        self.next_ticket = self.next_ticket.wrapping_add(1).max(1);
940        ticket
941    }
942
943    fn finish(&mut self, active: Active, outcome: Outcome) {
944        match outcome {
945            Outcome::Done => self.stats.requests_done = self.stats.requests_done.wrapping_add(1),
946            Outcome::Cancelled => {
947                self.stats.requests_cancelled = self.stats.requests_cancelled.wrapping_add(1)
948            }
949            Outcome::Failed(_) => {
950                self.stats.requests_failed = self.stats.requests_failed.wrapping_add(1)
951            }
952        }
953        self.record(Completion {
954            ticket: active.ticket,
955            outcome,
956            received: active.base + active.received,
957        });
958    }
959
960    fn record(&mut self, completion: Completion) {
961        let slot = self.results_written as usize % RECENT_RESULTS;
962        self.results[slot] = Some(completion);
963        self.results_written = self.results_written.wrapping_add(1);
964    }
965
966    // ------------------------------------------------------------ the source
967
968    /// Whether the CD interrupt source should reach the CPU right now.
969    pub fn source_wanted(&self) -> bool {
970        match self.owner {
971            Owner::Boot | Owner::Audio => false,
972            Owner::Data => match self.config.irq_mask_policy {
973                IrqMaskPolicy::AlwaysOn => true,
974                IrqMaskPolicy::OnlyWhileBusy => {
975                    !self.is_idle() || self.queue.iter().any(Option::is_some)
976                }
977            },
978        }
979    }
980
981    /// Close the CPU-side CD source. The console wrapper calls this before it
982    /// touches the engine from the foreground, so the handler cannot run
983    /// meanwhile; every foreground method ends by opening it again if
984    /// [`source_wanted`](Self::source_wanted).
985    pub fn close_source(&mut self) {
986        self.hw.set_source_enabled(false);
987        self.source_open = false;
988    }
989
990    /// Record that the source was closed behind the engine's back (by the
991    /// console wrapper, through `I_MASK` directly), so the next
992    /// [`sync_source`](Self::sync_source) reopens it.
993    pub fn note_source_closed(&mut self) {
994        self.source_open = false;
995    }
996
997    /// Open or close the source to match [`source_wanted`](Self::source_wanted).
998    pub fn sync_source(&mut self) {
999        let wanted = self.source_wanted();
1000        if wanted != self.source_open {
1001            self.hw.set_source_enabled(wanted);
1002            self.source_open = wanted;
1003        }
1004    }
1005
1006    #[inline(always)]
1007    fn trace(&mut self, kind: u32, value: u8) {
1008        if H::TRACE {
1009            let received = self.active.map_or(0, |a| a.received);
1010            self.hw.trace(
1011                kind << 24 | (self.phase as u32) << 16 | u32::from(value) << 8 | received & 0xff,
1012            );
1013        }
1014    }
1015}