1use crate::engine::{Config, Engine, LeaseState, Owner, Phase, StreamStats};
26use crate::hw::CdHw;
27use crate::request::{Request, RequestState, SubmitError, Ticket};
28use core::cell::UnsafeCell;
29use psx_hw::cd::{index_status, irq as flag};
30use psx_hw::irq::source;
31use psx_io::irq;
32use psx_io::periph::Cd;
33use psx_io::timers::{self, Timer};
34
35const CD_BIT: u32 = 1 << source::CDROM;
37const PARAM_POLLS: u32 = 32;
39const FIFO_POLLS: u32 = 256;
41pub const HANDLER_STACK_BYTES: usize = 2048;
43const STACK_FILL: u8 = 0xa5;
45
46pub struct Mmio {
50 time_handler: bool,
51}
52
53impl Mmio {
54 const fn new() -> Self {
55 Mmio {
56 time_handler: false,
57 }
58 }
59}
60
61fn token() -> Cd {
64 unsafe { Cd::steal() }
69}
70
71impl CdHw for Mmio {
72 const TRACE: bool = cfg!(feature = "trace");
73
74 fn issue(&mut self, command: u8, params: &[u8]) -> bool {
75 let mut cd = token();
76 cd.set_irq_enable_mask(0);
77 cd.acknowledge_all_and_reset_parameters();
78 cd.discard_response();
79 cd.reset_parameter_fifo();
80 for ¶m in params {
81 if !cd.wait_parameter_room(PARAM_POLLS) {
82 return false;
83 }
84 cd.send_parameter_byte(param);
85 }
86 cd.send_command_byte(command);
87 cd.set_irq_enable_mask(flag::ALL);
88 true
89 }
90
91 fn interrupt_code(&mut self) -> u8 {
92 token().irq_flag_value()
93 }
94
95 fn error_response(&mut self) -> (u8, u8) {
96 let mut cd = token();
97 let status = cd.read_response_byte();
98 (status, cd.read_response_byte())
99 }
100
101 fn discard_response(&mut self) {
102 token().discard_response();
103 }
104
105 fn acknowledge(&mut self, bits: u8) {
106 token().acknowledge_irq(bits);
107 }
108
109 fn silence_output(&mut self) {
110 token().set_irq_enable_mask(0);
111 }
112
113 fn drop_data_request(&mut self) {
114 token().clear_data_request();
115 }
116
117 unsafe fn pop_sector(&mut self, destination: *mut u32, store_words: usize) -> bool {
118 let mut cd = token();
119 cd.request_data();
120 let mut ready = false;
121 for _ in 0..FIFO_POLLS {
122 if cd.status_register() & index_status::DATA_FIFO_NOT_EMPTY != 0 {
123 ready = true;
124 break;
125 }
126 }
127 if !ready {
128 return false;
129 }
130 for word in 0..crate::SECTOR_WORDS as usize {
131 let b0 = u32::from(cd.read_data_byte());
132 let b1 = u32::from(cd.read_data_byte());
133 let b2 = u32::from(cd.read_data_byte());
134 let b3 = u32::from(cd.read_data_byte());
135 if word < store_words {
136 unsafe {
139 destination
140 .add(word)
141 .write_volatile(b0 | b1 << 8 | b2 << 16 | b3 << 24);
142 }
143 }
144 }
145 true
146 }
147
148 fn set_source_enabled(&mut self, enabled: bool) {
149 let mask = irq::mask();
156 if enabled {
157 irq::set_mask(mask | CD_BIT);
158 } else {
159 irq::set_mask(mask & !CD_BIT);
160 }
161 }
162
163 fn vblank_count(&mut self) -> u32 {
164 psx_rt::interrupts::vblank_count()
165 }
166
167 fn clock(&mut self) -> u16 {
168 if self.time_handler {
169 timers::counter(Timer::Timer2)
170 } else {
171 0
172 }
173 }
174
175 #[cfg(feature = "trace")]
176 fn trace(&mut self, word: u32) {
177 unsafe {
181 let n = core::ptr::read_volatile(&raw const PSX_CD_TRACE_N);
182 core::ptr::write_volatile(&raw mut PSX_CD_TRACE_N, n.wrapping_add(1));
183 let slot = (&raw mut PSX_CD_TRACE)
184 .cast::<u32>()
185 .add((n & 127) as usize);
186 core::ptr::write_volatile(slot, word);
187 }
188 }
189}
190
191struct Global(UnsafeCell<Engine<Mmio>>);
194
195unsafe impl Sync for Global {}
198
199static ENGINE: Global = Global(UnsafeCell::new(Engine::new(Mmio::new(), Config::DEFAULT)));
200
201struct TokenSlot(UnsafeCell<Option<Cd>>);
203
204unsafe impl Sync for TokenSlot {}
207
208impl TokenSlot {
209 fn is_held(&self) -> bool {
210 unsafe { (*self.0.get()).is_some() }
212 }
213
214 fn put(&self, cd: Cd) {
215 unsafe { *self.0.get() = Some(cd) };
217 }
218
219 fn take(&self) -> Option<Cd> {
220 unsafe { (*self.0.get()).take() }
222 }
223}
224
225static TOKEN: TokenSlot = TokenSlot(UnsafeCell::new(None));
226
227#[repr(C, align(16))]
228struct Stack([u8; HANDLER_STACK_BYTES]);
229
230#[no_mangle]
234static mut PSX_CD_IRQ_STACK: Stack = Stack([0; HANDLER_STACK_BYTES]);
235#[no_mangle]
237static mut PSX_CD_IRQ_CONTEXT: [u32; 34] = [0; 34];
238
239#[no_mangle]
243pub static mut PSX_CD_STATS: StreamStats = StreamStats {
244 irq_count: 0,
245 sectors: 0,
246 discarded_sectors: 0,
247 max_irq_ticks: 0,
248 phase: 0,
249 error: 0,
250 requests_done: 0,
251 requests_cancelled: 0,
252 requests_failed: 0,
253 chained: 0,
254 resumed: 0,
255 rejected: 0,
256};
257
258#[cfg(feature = "trace")]
261#[no_mangle]
262pub static mut PSX_CD_TRACE: [u32; 128] = [0; 128];
263#[cfg(feature = "trace")]
265#[no_mangle]
266pub static mut PSX_CD_TRACE_N: u32 = 0;
267
268#[inline(never)]
271fn publish(engine: &Engine<Mmio>) {
272 unsafe { core::ptr::write_volatile(&raw mut PSX_CD_STATS, engine.stats()) }
275}
276
277#[inline(never)]
281fn enter() -> &'static mut Engine<Mmio> {
282 let mask = irq::mask();
283 irq::set_mask(mask & !CD_BIT);
284 let engine = unsafe { &mut *ENGINE.0.get() };
288 engine.note_source_closed();
289 engine
290}
291
292#[inline(never)]
295fn leave(engine: &mut Engine<Mmio>) {
296 engine.sync_source();
297 publish(engine);
298}
299
300#[inline(always)]
306fn with_engine<R>(f: impl FnOnce(&mut Engine<Mmio>) -> R) -> R {
307 let engine = enter();
308 let result = f(engine);
309 leave(engine);
310 result
311}
312
313#[cfg(target_arch = "mips")]
316core::arch::global_asm!(
317 r#"
318 .set noreorder
319 .set noat
320 .section .text.psx_cdstream_irq
321 .globl psx_cdstream_exception_wrapper
322psx_cdstream_exception_wrapper:
323 # Not an interrupt: straight through.
324 mfc0 $26, $13
325 nop
326 andi $26, $26, 0x007c
327 bnez $26, 9f
328 nop
329 # An interrupt, but is the CD source one of the pending, enabled ones?
330 lui $26, {io_hi}
331 lw $27, {i_stat}($26)
332 lw $26, {i_mask}($26)
333 nop
334 and $27, $27, $26
335 andi $27, $27, {cd_bit}
336 beqz $27, 9f
337 nop
338 # Save the interrupted context (everything but $k0/$k1) in our own block.
339 lui $26, %hi({context})
340 addiu $26, $26, %lo({context})
341 sw $1, 0($26)
342 sw $2, 4($26)
343 sw $3, 8($26)
344 sw $4, 12($26)
345 sw $5, 16($26)
346 sw $6, 20($26)
347 sw $7, 24($26)
348 sw $8, 28($26)
349 sw $9, 32($26)
350 sw $10, 36($26)
351 sw $11, 40($26)
352 sw $12, 44($26)
353 sw $13, 48($26)
354 sw $14, 52($26)
355 sw $15, 56($26)
356 sw $16, 60($26)
357 sw $17, 64($26)
358 sw $18, 68($26)
359 sw $19, 72($26)
360 sw $20, 76($26)
361 sw $21, 80($26)
362 sw $22, 84($26)
363 sw $23, 88($26)
364 sw $24, 92($26)
365 sw $25, 96($26)
366 sw $28, 100($26)
367 sw $29, 104($26)
368 sw $30, 108($26)
369 sw $31, 112($26)
370 mfhi $27
371 sw $27, 116($26)
372 mflo $27
373 sw $27, 120($26)
374 # Our own stack; the interrupted one is never written.
375 lui $29, %hi({stack}+{stack_bytes})
376 addiu $29, $29, %lo({stack}+{stack_bytes})
377 addiu $29, $29, -16
378 jal {handler}
379 nop
380 lui $26, %hi({context})
381 addiu $26, $26, %lo({context})
382 lw $27, 116($26)
383 nop
384 mthi $27
385 lw $27, 120($26)
386 nop
387 mtlo $27
388 lw $1, 0($26)
389 lw $2, 4($26)
390 lw $3, 8($26)
391 lw $4, 12($26)
392 lw $5, 16($26)
393 lw $6, 20($26)
394 lw $7, 24($26)
395 lw $8, 28($26)
396 lw $9, 32($26)
397 lw $10, 36($26)
398 lw $11, 40($26)
399 lw $12, 44($26)
400 lw $13, 48($26)
401 lw $14, 52($26)
402 lw $15, 56($26)
403 lw $16, 60($26)
404 lw $17, 64($26)
405 lw $18, 68($26)
406 lw $19, 72($26)
407 lw $20, 76($26)
408 lw $21, 80($26)
409 lw $22, 84($26)
410 lw $23, 88($26)
411 lw $24, 92($26)
412 lw $25, 96($26)
413 lw $28, 100($26)
414 lw $29, 104($26)
415 lw $30, 108($26)
416 lw $31, 112($26)
417 nop
4189:
419 # psx-rt's handler: VBlank, display flip, faults, strays, and the return.
420 j __psx_rt_exception_handler
421 nop
422 .set at
423 .set reorder
424 "#,
425 io_hi = const psx_hw::memory::io::BASE >> 16,
426 i_stat = const psx_hw::irq::I_STAT & 0xFFFF,
427 i_mask = const psx_hw::irq::I_MASK & 0xFFFF,
428 cd_bit = const CD_BIT,
429 stack = sym PSX_CD_IRQ_STACK,
430 stack_bytes = const HANDLER_STACK_BYTES,
431 context = sym PSX_CD_IRQ_CONTEXT,
432 handler = sym psx_cdstream_interrupt,
433);
434
435#[cfg(target_arch = "mips")]
438const _: () = {
439 let window = psx_hw::memory::io::BASE >> 16;
440 assert!(psx_hw::irq::I_STAT >> 16 == window && psx_hw::irq::I_MASK >> 16 == window);
441 assert!(psx_hw::irq::I_STAT & 0xFFFF < 0x8000 && psx_hw::irq::I_MASK & 0xFFFF < 0x8000);
442};
443
444#[cfg(target_arch = "mips")]
445extern "C" {
446 fn psx_cdstream_exception_wrapper();
447}
448
449#[no_mangle]
451extern "C" fn psx_cdstream_interrupt() {
452 let engine = unsafe { &mut *ENGINE.0.get() };
456 engine.on_interrupt();
457 publish(engine);
458}
459
460pub fn install(cd: Cd, config: Config) -> Result<(), Cd> {
478 if TOKEN.is_held() {
479 return Err(cd);
480 }
481 TOKEN.put(cd);
482 let mask = irq::mask();
483 irq::set_mask(mask & !CD_BIT);
484 if !psx_rt::interrupts::is_stack_safe_handler_installed() {
485 psx_rt::interrupts::install_vblank_counter();
486 }
487 if config.time_handler {
488 timers::set_mode(Timer::Timer2, psx_hw::timers::mode::clock_source(2));
491 }
492 paint_stack();
493 install_vector();
494 with_engine(|engine| {
495 engine.hw_mut().time_handler = config.time_handler;
496 engine.configure(config);
497 engine.attach();
498 });
499 Ok(())
500}
501
502fn paint_stack() {
503 unsafe {
506 let stack = (&raw mut PSX_CD_IRQ_STACK).cast::<u8>();
507 for i in 0..HANDLER_STACK_BYTES {
508 stack.add(i).write_volatile(STACK_FILL);
509 }
510 }
511}
512
513#[cfg(target_arch = "mips")]
514fn install_vector() {
515 const EXCEPTION_VECTOR: *mut u32 = 0x8000_0080 as *mut u32;
516 const J_OPCODE: u32 = 0x0800_0000;
517 let wrapper = psx_cdstream_exception_wrapper as *const () as usize as u32;
518 unsafe {
524 core::ptr::write_volatile(EXCEPTION_VECTOR, J_OPCODE | ((wrapper >> 2) & 0x03ff_ffff));
525 core::ptr::write_volatile(EXCEPTION_VECTOR.add(1), 0);
526 psx_rt::cache::flush_instruction_cache();
527 psx_rt::interrupts::declare_stack_safe_handler(psx_cdstream_exception_wrapper);
528 }
529}
530
531#[cfg(not(target_arch = "mips"))]
532fn install_vector() {}
533
534pub fn uninstall() -> Option<Cd> {
539 let detached = with_engine(Engine::detach);
540 if !detached {
541 return None;
542 }
543 TOKEN.take()
544}
545
546pub fn configure(config: Config) {
552 with_engine(|engine| engine.configure(config));
553}
554
555pub fn reset_max_irq_ticks() {
558 with_engine(Engine::reset_max_irq_ticks);
559}
560
561pub fn submit(request: Request) -> Result<Ticket, SubmitError> {
563 with_engine(|engine| engine.submit(request))
564}
565
566pub fn cancel(ticket: Ticket) -> bool {
568 with_engine(|engine| engine.cancel(ticket))
569}
570
571pub fn cancel_all() {
573 with_engine(Engine::cancel_all);
574}
575
576pub fn state(ticket: Ticket) -> RequestState {
578 with_engine(|engine| engine.state(ticket))
579}
580
581pub fn service() {
583 with_engine(Engine::service);
584}
585
586pub fn is_idle() -> bool {
588 with_engine(|engine| engine.is_idle())
589}
590
591pub fn queued_count() -> usize {
593 with_engine(|engine| engine.queued_count())
594}
595
596pub fn owner() -> Owner {
598 with_engine(|engine| engine.owner())
599}
600
601pub fn phase() -> Phase {
603 with_engine(|engine| engine.phase())
604}
605
606pub fn stats() -> StreamStats {
608 with_engine(|engine| engine.stats())
609}
610
611pub fn request_audio_lease() -> LeaseState {
617 with_engine(|engine| engine.request_audio_lease())
618}
619
620pub fn lease_state() -> LeaseState {
622 with_engine(|engine| engine.lease_state())
623}
624
625pub fn take_audio_lease() -> Option<Cd> {
629 with_engine(|engine| {
630 if engine.lease_state() != LeaseState::Granted {
631 return None;
632 }
633 TOKEN.take()
634 })
635}
636
637pub fn release_audio_lease(cd: Cd) -> Result<(), Cd> {
641 with_engine(|engine| {
642 if engine.owner() != Owner::Audio {
643 return Err(cd);
644 }
645 TOKEN.put(cd);
646 engine.release_audio_lease();
647 Ok(())
648 })
649}
650
651pub fn try_take_audio_lease() -> Option<Cd> {
657 with_engine(|engine| {
658 if engine.try_audio_lease() {
659 TOKEN.take()
660 } else {
661 None
662 }
663 })
664}
665
666pub fn withdraw_audio_lease() -> bool {
669 with_engine(Engine::withdraw_audio_lease)
670}
671
672pub fn handler_stack_unused_bytes() -> usize {
678 let mut unused = 0;
679 unsafe {
681 let stack = (&raw const PSX_CD_IRQ_STACK).cast::<u8>();
682 while unused < HANDLER_STACK_BYTES && stack.add(unused).read_volatile() == STACK_FILL {
683 unused += 1;
684 }
685 }
686 unused
687}