1pub mod audio;
16pub mod reader;
17pub mod xa;
18
19use crate::periph::Cd;
20use crate::{irq, read_u8, write_u8};
21
22use psx_hw::cd::index_status as status;
23use psx_hw::cd::irq as code;
24use psx_hw::cd::{reg, request, volume_apply};
25use psx_hw::cd::{
26 CMD_DEMUTE, CMD_GETLOCP, CMD_GETSTAT, CMD_MUTE, CMD_PAUSE, CMD_PLAY, CMD_READN, CMD_SETLOC,
27 CMD_SETMODE, CMD_STOP, STAT_PLAYING, STAT_SEEKING,
28};
29
30const SECTOR_POLL_DRAIN_LIMIT: u32 = 256;
33
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
36pub struct Response {
37 bytes: [u8; 16],
38 len: usize,
39}
40
41impl Response {
42 pub const fn empty() -> Self {
45 Response {
46 bytes: [0; 16],
47 len: 0,
48 }
49 }
50
51 pub const fn len(&self) -> usize {
53 self.len
54 }
55
56 pub const fn is_empty(&self) -> bool {
58 self.len == 0
59 }
60
61 pub fn bytes(&self) -> &[u8] {
63 &self.bytes[..self.len]
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum CdError {
70 Timeout,
73 DriveError,
75}
76
77pub const DEFAULT_COMMAND_SPINS: u32 = 131_072;
82
83impl Cd {
84 pub fn command(&mut self, command: u8, params: &[u8]) -> Result<Response, CdError> {
91 self.command_within(command, params, DEFAULT_COMMAND_SPINS)
92 }
93
94 pub fn command_within(
96 &mut self,
97 command: u8,
98 params: &[u8],
99 spin_limit: u32,
100 ) -> Result<Response, CdError> {
101 run_command(&mut Mmio, command, params, spin_limit)
102 }
103
104 pub fn try_command(&mut self, command: u8, params: &[u8], spin_limit: u32) -> Option<Response> {
113 self.command_within(command, params, spin_limit).ok()
114 }
115
116 pub fn irq_flag_value(&mut self) -> u8 {
124 irq_flag()
125 }
126
127 pub fn acknowledge_irq(&mut self, bits: u8) {
129 ack_irq(bits);
130 }
131
132 pub fn discard_response(&mut self) {
134 drain_response_fifo();
135 }
136
137 pub fn dispatch_command(&mut self, command: u8, params: &[u8], spin_limit: u32) -> Option<u8> {
146 let irq_enable = begin_polled_command();
147 select_index(0);
148 for ¶m in params {
149 if !wait_param_room_bounded(spin_limit) {
150 finish_failed_polled_command(irq_enable);
151 return None;
152 }
153 write_byte(reg::PARAMETER, param);
154 }
155 write_byte(reg::COMMAND_RESPONSE, command);
156 Some(irq_enable)
157 }
158
159 pub fn restore_irq_output(&mut self, saved: u8) {
161 drain_response_fifo();
162 ack_irq(code::ACK_ALL);
163 restore_irq_enable(saved);
164 select_index(0);
165 }
166
167 pub fn poll_data_sector(&mut self) -> Result<bool, SectorPollError> {
172 poll_sector(&mut SectorPollMmio)
173 }
174
175 pub fn try_wait_data_sector(&mut self, spin_limit: u32) -> bool {
182 let mut spins = spin_limit;
183 loop {
184 let flag = irq_flag();
185 if flag == code::DATA_READY {
186 ack_irq(flag);
187 return true;
188 }
189 if flag == code::ERROR {
190 let _ = read_response_fifo();
191 ack_irq(flag);
192 return false;
193 }
194 if flag != 0 {
195 let _ = read_response_fifo();
196 ack_irq(flag);
197 }
198 if spins == 0 {
199 return false;
200 }
201 spins -= 1;
202 core::hint::spin_loop();
203 }
204 }
205
206 #[doc(alias = "Getstat")]
208 #[doc(alias = "CdlNop")]
209 pub fn status(&mut self) -> Result<Response, CdError> {
210 self.command(CMD_GETSTAT, &[])
211 }
212
213 #[doc(alias = "Getstat")]
215 pub fn try_status(&mut self, spin_limit: u32) -> Option<Response> {
216 self.try_command(CMD_GETSTAT, &[], spin_limit)
217 }
218
219 pub fn set_mode(&mut self, mode: u8) -> Result<Response, CdError> {
221 self.command(CMD_SETMODE, &[mode])
222 }
223
224 pub fn try_set_mode(&mut self, mode: u8, spin_limit: u32) -> Option<Response> {
226 self.try_command(CMD_SETMODE, &[mode], spin_limit)
227 }
228
229 #[doc(alias = "Setloc")]
231 #[doc(alias = "CdlSetloc")]
232 pub fn try_set_target_lba(&mut self, lba: u32, spin_limit: u32) -> Option<Response> {
233 let [minute, second, frame] = lba_to_bcd_msf(lba);
234 self.try_command(CMD_SETLOC, &[minute, second, frame], spin_limit)
235 }
236
237 #[doc(alias = "ReadN")]
239 #[doc(alias = "CdlReadN")]
240 pub fn try_start_reading(&mut self, spin_limit: u32) -> Option<Response> {
241 self.try_command(CMD_READN, &[], spin_limit)
242 }
243
244 #[doc(alias = "Demute")]
246 #[doc(alias = "CdlDemute")]
247 pub fn unmute(&mut self) -> Result<Response, CdError> {
248 self.command(CMD_DEMUTE, &[])
249 }
250
251 #[doc(alias = "Demute")]
253 pub fn try_unmute(&mut self, spin_limit: u32) -> Option<Response> {
254 self.try_command(CMD_DEMUTE, &[], spin_limit)
255 }
256
257 pub fn mute(&mut self) -> Result<Response, CdError> {
259 self.command(CMD_MUTE, &[])
260 }
261
262 pub fn try_mute(&mut self, spin_limit: u32) -> Option<Response> {
264 self.try_command(CMD_MUTE, &[], spin_limit)
265 }
266
267 pub fn play_track(&mut self, track: u8) -> Result<Response, CdError> {
272 self.command(
273 CMD_PLAY,
274 &[bin_to_bcd(crate::disc_base::shift_track(track))],
275 )
276 }
277
278 pub fn try_play_track(&mut self, track: u8, spin_limit: u32) -> Option<Response> {
281 self.try_command(
282 CMD_PLAY,
283 &[bin_to_bcd(crate::disc_base::shift_track(track))],
284 spin_limit,
285 )
286 }
287
288 pub fn pause(&mut self) -> Result<Response, CdError> {
290 self.command(CMD_PAUSE, &[])
291 }
292
293 pub fn try_pause(&mut self, spin_limit: u32) -> Option<Response> {
295 self.try_command(CMD_PAUSE, &[], spin_limit)
296 }
297
298 pub fn try_pause_until_complete(&mut self, spin_limit: u32) -> bool {
303 try_command_until_complete_inner(CMD_PAUSE, &[], spin_limit)
304 }
305
306 pub fn stop(&mut self) -> Result<Response, CdError> {
308 self.command(CMD_STOP, &[])
309 }
310
311 pub fn try_stop(&mut self, spin_limit: u32) -> Option<Response> {
321 self.try_command(CMD_STOP, &[], spin_limit)
322 }
323
324 pub fn stop_and_settle(&mut self, spin_limit: u32, max_polls: u32) -> bool {
333 let _ = self.try_stop(spin_limit);
334 let mut settled = 0u8;
335 for _ in 0..max_polls {
336 for _ in 0..20_000u32 {
340 unsafe { core::ptr::read_volatile(reg::INDEX as *const u8) };
343 }
344 let quiet = match self.try_status(spin_limit) {
345 Some(r) => r
346 .bytes()
347 .first()
348 .is_some_and(|s| s & (STAT_PLAYING | STAT_SEEKING) == 0),
349 None => false,
350 };
351 settled = if quiet { settled + 1 } else { 0 };
352 if settled >= 2 {
353 return true;
354 }
355 }
356 false
357 }
358
359 #[doc(alias = "GetlocP")]
363 #[doc(alias = "CdlGetlocP")]
364 pub fn play_position(&mut self) -> Result<Response, CdError> {
365 self.command(CMD_GETLOCP, &[])
366 }
367
368 #[doc(alias = "GetlocP")]
370 pub fn try_play_position(&mut self, spin_limit: u32) -> Option<Response> {
371 self.try_command(CMD_GETLOCP, &[], spin_limit)
372 }
373
374 pub fn try_command_until_complete(
382 &mut self,
383 command: u8,
384 params: &[u8],
385 spin_limit: u32,
386 ) -> bool {
387 try_command_until_complete_inner(command, params, spin_limit)
388 }
389
390 pub fn set_audio_mixer(
395 &mut self,
396 left_to_left: u8,
397 left_to_right: u8,
398 right_to_right: u8,
399 right_to_left: u8,
400 ) {
401 select_index(2);
402 write_byte(reg::PARAMETER, left_to_left);
403 write_byte(reg::REQUEST_IRQ, left_to_right);
404 select_index(3);
405 write_byte(reg::COMMAND_RESPONSE, right_to_right);
406 write_byte(reg::PARAMETER, right_to_left);
407 write_byte(reg::REQUEST_IRQ, volume_apply::APPLY);
409 select_index(0);
410 }
411}
412
413impl Cd {
420 pub fn acknowledge_all_and_reset_parameters(&mut self) {
423 ack_all_and_reset_parameters();
424 }
425
426 pub fn irq_enable_mask(&mut self) -> u8 {
428 irq_enable()
429 }
430
431 pub fn set_irq_enable_mask(&mut self, mask: u8) {
433 set_irq_enable(mask);
434 }
435
436 pub fn drain_response_limited(&mut self, limit: u32) {
439 drain_response_limited(limit);
440 }
441
442 pub fn is_data_fifo_ready(&mut self) -> bool {
444 data_fifo_ready()
445 }
446
447 pub fn wait_parameter_room(&mut self, spins: u32) -> bool {
450 wait_param_room_bounded(spins)
451 }
452
453 pub fn reset_parameter_fifo(&mut self) {
455 clear_parameter_fifo();
456 }
457
458 pub fn send_parameter_byte(&mut self, byte: u8) {
460 write_byte(reg::PARAMETER, byte);
461 }
462
463 pub fn send_command_byte(&mut self, byte: u8) {
465 write_byte(reg::COMMAND_RESPONSE, byte);
466 }
467
468 pub fn read_response_byte(&mut self) -> u8 {
470 read_byte(reg::COMMAND_RESPONSE)
471 }
472
473 pub fn status_register(&mut self) -> u8 {
475 read_status()
476 }
477
478 #[doc(alias = "BFRD")]
482 pub fn request_data(&mut self) {
483 write_byte(reg::REQUEST_IRQ, request::WANT_DATA);
484 }
485
486 pub fn clear_data_request(&mut self) {
488 write_byte(reg::REQUEST_IRQ, 0);
489 }
490
491 pub fn read_data_byte(&mut self) -> u8 {
493 read_byte(reg::PARAMETER)
494 }
495}
496
497trait CommandIo {
500 fn begin(&mut self) -> u8;
501 fn wait_param_room(&mut self, spins: u32) -> bool;
502 fn write_param(&mut self, value: u8);
503 fn write_command(&mut self, command: u8);
504 fn wait_irq(&mut self, expected: u8, spins: u32) -> Result<u8, CdError>;
505 fn finish(&mut self, irq_enable: u8, irq: u8) -> Response;
506 fn recover(&mut self, irq_enable: u8);
509 fn restore_enable(&mut self, irq_enable: u8);
511}
512
513struct Mmio;
514
515impl CommandIo for Mmio {
516 fn begin(&mut self) -> u8 {
517 begin_polled_command()
518 }
519 fn wait_param_room(&mut self, spins: u32) -> bool {
520 wait_param_room_bounded(spins)
521 }
522 fn write_param(&mut self, value: u8) {
523 write_byte(reg::PARAMETER, value);
524 }
525 fn write_command(&mut self, command: u8) {
526 write_byte(reg::COMMAND_RESPONSE, command);
527 }
528 fn wait_irq(&mut self, expected: u8, spins: u32) -> Result<u8, CdError> {
529 wait_irq_flag(expected, spins, irq_flag, |flag| {
530 let _ = read_response_fifo();
531 ack_irq(flag);
532 })
533 }
534 fn finish(&mut self, irq_enable: u8, irq: u8) -> Response {
535 finish_polled_command(irq_enable, irq)
536 }
537 fn recover(&mut self, irq_enable: u8) {
538 finish_failed_polled_command(irq_enable);
539 }
540 fn restore_enable(&mut self, irq_enable: u8) {
541 restore_irq_enable(irq_enable);
542 select_index(0);
543 }
544}
545
546fn run_command(
547 io: &mut impl CommandIo,
548 command: u8,
549 params: &[u8],
550 spin_limit: u32,
551) -> Result<Response, CdError> {
552 let irq_enable = io.begin();
553 for ¶m in params {
554 if !io.wait_param_room(spin_limit) {
555 io.restore_enable(irq_enable);
557 return Err(CdError::Timeout);
558 }
559 io.write_param(param);
560 }
561 io.write_command(command);
562 match io.wait_irq(code::ACKNOWLEDGE, spin_limit) {
563 Ok(irq) => Ok(io.finish(irq_enable, irq)),
564 Err(CdError::DriveError) => {
565 io.recover(irq_enable);
566 Err(CdError::DriveError)
567 }
568 Err(CdError::Timeout) => Err(CdError::Timeout),
571 }
572}
573
574#[derive(Clone, Copy, Debug, PartialEq, Eq)]
576pub struct SectorPollError;
577
578trait SectorPollIo {
579 fn flag(&mut self) -> u8;
580 fn drain(&mut self);
581 fn acknowledge(&mut self, flag: u8, reset: bool);
582 fn data_ready(&mut self) -> bool;
583}
584struct SectorPollMmio;
585impl SectorPollIo for SectorPollMmio {
586 #[inline]
587 fn flag(&mut self) -> u8 {
588 irq_flag()
589 }
590 #[inline]
591 fn drain(&mut self) {
592 drain_response_limited(SECTOR_POLL_DRAIN_LIMIT);
593 }
594 #[inline]
595 fn acknowledge(&mut self, flag: u8, reset: bool) {
596 if reset {
597 ack_all_and_reset_parameters();
598 } else {
599 ack_irq(flag);
600 }
601 }
602 #[inline]
603 fn data_ready(&mut self) -> bool {
604 data_fifo_ready()
605 }
606}
607#[inline]
608fn poll_sector(io: &mut impl SectorPollIo) -> Result<bool, SectorPollError> {
609 match io.flag() {
610 code::DATA_READY => Ok(true),
611 code::ERROR => {
612 io.drain();
613 io.acknowledge(code::ACK_ALL, true);
614 Err(SectorPollError)
615 }
616 flag @ (code::COMPLETE | code::ACKNOWLEDGE | code::DATA_END) => {
617 io.drain();
618 io.acknowledge(flag, false);
619 Ok(false)
620 }
621 _ => Ok(io.data_ready()),
622 }
623}
624
625pub const fn lba_to_bcd_msf(lba: u32) -> [u8; 3] {
629 let absolute = lba.saturating_add(150);
630 let raw_minute = absolute / (60 * 75);
631 let minute = if raw_minute > 99 {
632 99
633 } else {
634 raw_minute as u8
635 };
636 let second = ((absolute / 75) % 60) as u8;
637 let frame = (absolute % 75) as u8;
638 [bin_to_bcd(minute), bin_to_bcd(second), bin_to_bcd(frame)]
639}
640
641pub const fn bin_to_bcd(v: u8) -> u8 {
643 let v = if v > 99 { 99 } else { v };
644 ((v / 10) << 4) | (v % 10)
645}
646
647pub const fn bcd_to_bin(v: u8) -> u8 {
650 (v >> 4) * 10 + (v & 0x0F)
651}
652
653#[derive(Clone, Copy, PartialEq, Eq, Debug)]
658pub struct PlayPosition {
659 pub track: u8,
661 pub index: u8,
663 pub relative_min: u8,
665 pub relative_sec: u8,
667 pub relative_frame: u8,
669 pub absolute_min: u8,
671 pub absolute_sec: u8,
673 pub absolute_frame: u8,
675}
676
677impl PlayPosition {
678 pub fn parse(resp: &Response) -> Option<Self> {
682 let b = resp.bytes();
683 if b.len() < 8 {
684 return None;
685 }
686 Some(PlayPosition {
687 track: bcd_to_bin(b[0]),
688 index: b[1],
689 relative_min: bcd_to_bin(b[2]),
690 relative_sec: bcd_to_bin(b[3]),
691 relative_frame: bcd_to_bin(b[4]),
692 absolute_min: bcd_to_bin(b[5]),
693 absolute_sec: bcd_to_bin(b[6]),
694 absolute_frame: bcd_to_bin(b[7]),
695 })
696 }
697
698 pub fn relative_sectors(&self) -> u32 {
700 (self.relative_min as u32 * 60 + self.relative_sec as u32) * 75 + self.relative_frame as u32
701 }
702
703 pub fn relative_millis(&self) -> u32 {
705 self.relative_sectors() * 1000 / 75
706 }
707}
708
709fn begin_polled_command() -> u8 {
710 let irq_enable = irq_enable();
711 set_irq_enable(0);
712 ack_irq(code::ACK_ALL);
713 select_index(0);
714 drain_response_fifo();
715 clear_parameter_fifo();
716 irq_enable
717}
718
719fn finish_polled_command(irq_enable: u8, irq: u8) -> Response {
720 let response = read_response_fifo();
721 ack_irq(irq);
722 restore_irq_enable(irq_enable);
723 select_index(0);
724 response
725}
726
727fn wait_irq_flag(
731 expected: u8,
732 mut spins: u32,
733 mut flag: impl FnMut() -> u8,
734 mut discard: impl FnMut(u8),
735) -> Result<u8, CdError> {
736 loop {
737 let irq = flag();
738 if irq == expected {
739 return Ok(irq);
740 }
741 if irq == code::ERROR {
742 return Err(CdError::DriveError);
743 }
744 if irq != 0 {
745 discard(irq);
746 }
747 if spins == 0 {
748 return Err(CdError::Timeout);
749 }
750 spins -= 1;
751 core::hint::spin_loop();
752 }
753}
754
755fn wait_irq_bounded(expected: u8, spins: u32) -> Option<u8> {
756 Mmio.wait_irq(expected, spins).ok()
757}
758
759fn try_command_until_complete_inner(command: u8, params: &[u8], spin_limit: u32) -> bool {
760 let irq_enable = begin_polled_command();
761 select_index(0);
762 for ¶m in params {
763 if !wait_param_room_bounded(spin_limit) {
764 finish_failed_polled_command(irq_enable);
765 return false;
766 }
767 write_byte(reg::PARAMETER, param);
768 }
769 write_byte(reg::COMMAND_RESPONSE, command);
770
771 let ok = wait_ack_then_complete(spin_limit);
772 drain_response_fifo();
773 ack_irq(code::ACK_ALL);
774 restore_irq_enable(irq_enable);
775 select_index(0);
776 ok
777}
778
779fn wait_ack_then_complete(spin_limit: u32) -> bool {
780 if wait_irq_bounded(code::ACKNOWLEDGE, spin_limit).is_none() {
781 return false;
782 }
783 drain_response_fifo();
784 ack_irq(code::ACKNOWLEDGE);
785
786 if wait_irq_bounded(code::COMPLETE, spin_limit).is_none() {
787 return false;
788 }
789 drain_response_fifo();
790 ack_irq(code::COMPLETE);
791 true
792}
793
794fn finish_failed_polled_command(irq_enable: u8) {
795 drain_response_fifo();
796 ack_irq(code::ACK_ALL);
797 restore_irq_enable(irq_enable);
798 select_index(0);
799}
800
801fn read_response_fifo() -> Response {
802 select_index(0);
803
804 let mut bytes = [0u8; 16];
805 let mut len = 0;
806 while read_status() & status::RESPONSE_NOT_EMPTY != 0 && len < bytes.len() {
807 bytes[len] = read_byte(reg::COMMAND_RESPONSE);
808 len += 1;
809 }
810 Response { bytes, len }
811}
812
813fn drain_response_fifo() {
814 let _ = read_response_fifo();
815}
816
817fn wait_param_room_bounded(mut spins: u32) -> bool {
818 while read_status() & status::PARAM_NOT_FULL == 0 {
819 if spins == 0 {
820 return false;
821 }
822 spins -= 1;
823 core::hint::spin_loop();
824 }
825 true
826}
827
828fn drain_response_limited(limit: u32) {
830 select_index(0);
831 let mut drained = 0;
832 while read_status() & status::RESPONSE_NOT_EMPTY != 0 && drained < limit {
833 let _ = read_byte(reg::COMMAND_RESPONSE);
834 drained += 1;
835 }
836}
837
838fn ack_all_and_reset_parameters() {
841 select_index(1);
842 write_byte(reg::REQUEST_IRQ, code::ACK_ALL | code::CLEAR_PARAMETER_FIFO);
843 irq::acknowledge(1 << psx_hw::irq::source::CDROM);
844 select_index(0);
845}
846
847fn data_fifo_ready() -> bool {
848 select_index(0);
849 read_status() & status::DATA_FIFO_NOT_EMPTY != 0
850}
851
852fn clear_parameter_fifo() {
853 select_index(1);
854 write_byte(reg::REQUEST_IRQ, code::CLEAR_PARAMETER_FIFO);
855 select_index(0);
856}
857
858fn ack_irq(bits: u8) {
859 select_index(1);
860 write_byte(reg::REQUEST_IRQ, bits & code::ACK_ALL);
861 irq::acknowledge(1 << psx_hw::irq::source::CDROM);
862 select_index(0);
863}
864
865fn irq_flag() -> u8 {
866 select_index(1);
867 let flag = read_byte(reg::REQUEST_IRQ) & code::ACK_ALL;
868 select_index(0);
869 flag
870}
871
872fn irq_enable() -> u8 {
873 select_index(0);
874 let enable = read_byte(reg::REQUEST_IRQ) & code::ACK_ALL;
875 select_index(0);
876 enable
877}
878
879fn restore_irq_enable(enable: u8) {
880 set_irq_enable(enable);
881}
882
883fn set_irq_enable(enable: u8) {
884 select_index(1);
885 write_byte(reg::PARAMETER, enable & code::ACK_ALL);
886 select_index(0);
887}
888
889fn read_status() -> u8 {
890 read_byte(reg::INDEX)
891}
892
893fn select_index(index: u8) {
894 write_byte(reg::INDEX, index & 0x03);
895}
896
897fn read_byte(addr: u32) -> u8 {
898 unsafe { read_u8(addr) }
900}
901
902fn write_byte(addr: u32, value: u8) {
903 unsafe { write_u8(addr, value) }
905}
906
907fn steal() -> Cd {
909 unsafe { Cd::steal() }
912}
913
914#[deprecated(note = "use `Cd::try_command` with the `Cd` token")]
916#[inline(always)]
917pub fn try_command(command: u8, params: &[u8], spin_limit: u32) -> Option<Response> {
918 steal().try_command(command, params, spin_limit)
919}
920
921#[deprecated(note = "use `Cd::irq_flag_value` with the `Cd` token")]
923#[inline(always)]
924pub fn irq_flag_value() -> u8 {
925 steal().irq_flag_value()
926}
927
928#[deprecated(note = "use `Cd::acknowledge_irq` with the `Cd` token")]
930#[inline(always)]
931pub fn acknowledge_irq(bits: u8) {
932 steal().acknowledge_irq(bits)
933}
934
935#[deprecated(note = "use `Cd::discard_response` with the `Cd` token")]
937#[inline(always)]
938pub fn discard_response() {
939 steal().discard_response()
940}
941
942#[deprecated(note = "use `Cd::dispatch_command` with the `Cd` token")]
944#[inline(always)]
945pub fn dispatch_command(command: u8, params: &[u8], spin_limit: u32) -> Option<u8> {
946 steal().dispatch_command(command, params, spin_limit)
947}
948
949#[deprecated(note = "use `Cd::restore_irq_output` with the `Cd` token")]
951#[inline(always)]
952pub fn restore_irq_output(saved: u8) {
953 steal().restore_irq_output(saved)
954}
955
956#[deprecated(note = "use `Cd::poll_data_sector` with the `Cd` token")]
958#[inline(always)]
959pub fn poll_data_sector() -> Result<bool, SectorPollError> {
960 steal().poll_data_sector()
961}
962
963#[deprecated(note = "use `Cd::try_status` with the `Cd` token")]
965#[inline(always)]
966pub fn try_status(spin_limit: u32) -> Option<Response> {
967 steal().try_status(spin_limit)
968}
969
970#[deprecated(note = "use `Cd::try_set_mode` with the `Cd` token")]
972#[inline(always)]
973pub fn try_set_mode(mode: u8, spin_limit: u32) -> Option<Response> {
974 steal().try_set_mode(mode, spin_limit)
975}
976
977#[deprecated(note = "use `Cd::try_set_target_lba` with the `Cd` token")]
979#[inline(always)]
980pub fn try_set_target_lba(lba: u32, spin_limit: u32) -> Option<Response> {
981 steal().try_set_target_lba(lba, spin_limit)
982}
983
984#[deprecated(note = "use `Cd::try_start_reading` with the `Cd` token")]
986#[inline(always)]
987pub fn try_start_reading(spin_limit: u32) -> Option<Response> {
988 steal().try_start_reading(spin_limit)
989}
990
991#[deprecated(note = "use `Cd::try_unmute` with the `Cd` token")]
993#[inline(always)]
994pub fn try_unmute(spin_limit: u32) -> Option<Response> {
995 steal().try_unmute(spin_limit)
996}
997
998#[deprecated(note = "use `Cd::try_mute` with the `Cd` token")]
1000#[inline(always)]
1001pub fn try_mute(spin_limit: u32) -> Option<Response> {
1002 steal().try_mute(spin_limit)
1003}
1004
1005#[deprecated(note = "use `Cd::try_play_track` with the `Cd` token")]
1007#[inline(always)]
1008pub fn try_play_track(track: u8, spin_limit: u32) -> Option<Response> {
1009 steal().try_play_track(track, spin_limit)
1010}
1011
1012#[deprecated(note = "use `Cd::try_pause` with the `Cd` token")]
1014#[inline(always)]
1015pub fn try_pause(spin_limit: u32) -> Option<Response> {
1016 steal().try_pause(spin_limit)
1017}
1018
1019#[deprecated(note = "use `Cd::try_pause_until_complete` with the `Cd` token")]
1021#[inline(always)]
1022pub fn try_pause_until_complete(spin_limit: u32) -> bool {
1023 steal().try_pause_until_complete(spin_limit)
1024}
1025
1026#[deprecated(note = "use `Cd::try_play_position` with the `Cd` token")]
1028#[inline(always)]
1029pub fn try_play_position(spin_limit: u32) -> Option<Response> {
1030 steal().try_play_position(spin_limit)
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035 use super::*;
1036
1037 #[test]
1038 fn sector_probe_leaves_int1_and_orders_other_acknowledgements() {
1039 struct Fake {
1040 flag: u8,
1041 ready: bool,
1042 events: [u8; 4],
1043 len: usize,
1044 }
1045 impl Fake {
1046 fn event(&mut self, e: u8) {
1047 self.events[self.len] = e;
1048 self.len += 1;
1049 }
1050 }
1051 impl SectorPollIo for Fake {
1052 fn flag(&mut self) -> u8 {
1053 self.event(1);
1054 self.flag
1055 }
1056 fn drain(&mut self) {
1057 self.event(2);
1058 }
1059 fn acknowledge(&mut self, flag: u8, reset: bool) {
1060 assert_eq!(flag, if reset { 31 } else { self.flag });
1061 self.event(if reset { 4 } else { 3 });
1062 }
1063 fn data_ready(&mut self) -> bool {
1064 self.event(5);
1065 self.ready
1066 }
1067 }
1068 for flag in 0..=5 {
1069 for ready in [false, true] {
1070 let mut io = Fake {
1071 flag,
1072 ready,
1073 events: [0; 4],
1074 len: 0,
1075 };
1076 let result = poll_sector(&mut io);
1077 let expected = match flag {
1078 1 => Ok(true),
1079 5 => Err(SectorPollError),
1080 2..=4 => Ok(false),
1081 _ => Ok(ready),
1082 };
1083 assert_eq!(result, expected);
1084 let events: &[u8] = match flag {
1085 1 => &[1],
1086 5 => &[1, 2, 4],
1087 2..=4 => &[1, 2, 3],
1088 _ => &[1, 5],
1089 };
1090 assert_eq!(&io.events[..io.len], events);
1091 }
1092 }
1093 }
1094
1095 struct FakeIo {
1098 room: bool,
1099 irq: Result<u8, CdError>,
1100 events: [u8; 12],
1101 len: usize,
1102 spins_seen: [u32; 4],
1103 spins_len: usize,
1104 }
1105
1106 impl FakeIo {
1107 fn new(room: bool, irq: Result<u8, CdError>) -> Self {
1108 FakeIo {
1109 room,
1110 irq,
1111 events: [0; 12],
1112 len: 0,
1113 spins_seen: [0; 4],
1114 spins_len: 0,
1115 }
1116 }
1117 fn event(&mut self, e: u8) {
1118 self.events[self.len] = e;
1119 self.len += 1;
1120 }
1121 fn events(&self) -> &[u8] {
1122 &self.events[..self.len]
1123 }
1124 fn spins(&mut self, spins: u32) {
1125 self.spins_seen[self.spins_len] = spins;
1126 self.spins_len += 1;
1127 }
1128 }
1129
1130 const BEGIN: u8 = 1;
1133 const ROOM: u8 = 2;
1134 const PARAM: u8 = 3;
1135 const COMMAND: u8 = 4;
1136 const WAIT: u8 = 5;
1137 const FINISH: u8 = 6;
1138 const RECOVER: u8 = 7;
1139 const RESTORE: u8 = 8;
1140
1141 impl CommandIo for FakeIo {
1142 fn begin(&mut self) -> u8 {
1143 self.event(BEGIN);
1144 0x1F
1145 }
1146 fn wait_param_room(&mut self, spins: u32) -> bool {
1147 self.event(ROOM);
1148 self.spins(spins);
1149 self.room
1150 }
1151 fn write_param(&mut self, _value: u8) {
1152 self.event(PARAM);
1153 }
1154 fn write_command(&mut self, _command: u8) {
1155 self.event(COMMAND);
1156 }
1157 fn wait_irq(&mut self, _expected: u8, spins: u32) -> Result<u8, CdError> {
1158 self.event(WAIT);
1159 self.spins(spins);
1160 self.irq
1161 }
1162 fn finish(&mut self, _irq_enable: u8, _irq: u8) -> Response {
1163 self.event(FINISH);
1164 response_from(&[0x02])
1165 }
1166 fn recover(&mut self, _irq_enable: u8) {
1167 self.event(RECOVER);
1168 }
1169 fn restore_enable(&mut self, _irq_enable: u8) {
1170 self.event(RESTORE);
1171 }
1172 }
1173
1174 #[test]
1175 fn a_full_parameter_fifo_times_out_without_writing_the_parameter() {
1176 let mut io = FakeIo::new(false, Ok(code::ACKNOWLEDGE));
1178 let result = run_command(&mut io, CMD_SETLOC, &[0x00, 0x02, 0x00], 77);
1179 assert_eq!(result, Err(CdError::Timeout));
1180 assert_eq!(io.events(), &[BEGIN, ROOM, RESTORE]);
1181 }
1182
1183 #[test]
1184 fn a_command_nobody_acknowledges_times_out_and_leaves_irq_output_masked() {
1185 let mut io = FakeIo::new(true, Err(CdError::Timeout));
1186 let result = run_command(&mut io, CMD_GETSTAT, &[], DEFAULT_COMMAND_SPINS);
1187 assert_eq!(result, Err(CdError::Timeout));
1188 assert_eq!(io.events(), &[BEGIN, COMMAND, WAIT]);
1190 assert_eq!(&io.spins_seen[..io.spins_len], &[DEFAULT_COMMAND_SPINS]);
1191 }
1192
1193 #[test]
1194 fn a_drive_error_is_reported_and_the_controller_cleaned_up() {
1195 let mut io = FakeIo::new(true, Err(CdError::DriveError));
1196 let result = run_command(&mut io, CMD_PLAY, &[0x01], 10);
1197 assert_eq!(result, Err(CdError::DriveError));
1198 assert_eq!(io.events(), &[BEGIN, ROOM, PARAM, COMMAND, WAIT, RECOVER]);
1199 }
1200
1201 #[test]
1202 fn an_acknowledged_command_returns_its_response() {
1203 let mut io = FakeIo::new(true, Ok(code::ACKNOWLEDGE));
1204 let result = run_command(&mut io, CMD_SETLOC, &[1, 2, 3], 10).unwrap();
1205 assert_eq!(result.bytes(), &[0x02]);
1206 assert_eq!(
1207 io.events(),
1208 &[BEGIN, ROOM, PARAM, ROOM, PARAM, ROOM, PARAM, COMMAND, WAIT, FINISH]
1209 );
1210 }
1211
1212 #[test]
1213 fn the_flag_wait_discards_stale_responses_and_stops_at_its_budget() {
1214 let script = [0u8, 2, 3];
1216 let mut next = 0;
1217 let mut discarded = [0u8; 4];
1218 let mut dropped = 0;
1219 let got = wait_irq_flag(
1220 code::ACKNOWLEDGE,
1221 10,
1222 || {
1223 let flag = script[next];
1224 next += 1;
1225 flag
1226 },
1227 |flag| {
1228 discarded[dropped] = flag;
1229 dropped += 1;
1230 },
1231 );
1232 assert_eq!(got, Ok(code::ACKNOWLEDGE));
1233 assert_eq!(&discarded[..dropped], &[2]);
1234
1235 let mut reads = 0u32;
1237 let got = wait_irq_flag(
1238 code::ACKNOWLEDGE,
1239 5,
1240 || {
1241 reads += 1;
1242 0
1243 },
1244 |_| {},
1245 );
1246 assert_eq!(got, Err(CdError::Timeout));
1247 assert_eq!(reads, 6);
1248
1249 let got = wait_irq_flag(code::ACKNOWLEDGE, 1000, || code::ERROR, |_| {});
1251 assert_eq!(got, Err(CdError::DriveError));
1252 }
1253
1254 #[test]
1255 fn bcd_clamps_to_two_digits() {
1256 assert_eq!(bin_to_bcd(2), 0x02);
1257 assert_eq!(bin_to_bcd(42), 0x42);
1258 assert_eq!(bin_to_bcd(100), 0x99);
1259 }
1260
1261 #[test]
1262 fn bcd_to_bin_roundtrips() {
1263 for v in 0u8..=99 {
1264 assert_eq!(bcd_to_bin(bin_to_bcd(v)), v);
1265 }
1266 }
1267
1268 #[test]
1269 fn lba_to_msf_includes_the_lead_in() {
1270 assert_eq!(lba_to_bcd_msf(0), [0x00, 0x02, 0x00]);
1271 assert_eq!(lba_to_bcd_msf(424), [0x00, 0x07, 0x49]);
1272 assert_eq!(lba_to_bcd_msf(992), [0x00, 0x15, 0x17]);
1273 }
1274
1275 fn response_from(bytes: &[u8]) -> Response {
1276 let mut buf = [0u8; 16];
1277 buf[..bytes.len()].copy_from_slice(bytes);
1278 Response {
1279 bytes: buf,
1280 len: bytes.len(),
1281 }
1282 }
1283
1284 #[test]
1285 fn play_position_parses_and_converts() {
1286 let resp = response_from(&[
1288 bin_to_bcd(2),
1289 0x01,
1290 bin_to_bcd(1),
1291 bin_to_bcd(23),
1292 bin_to_bcd(45),
1293 bin_to_bcd(4),
1294 bin_to_bcd(56),
1295 bin_to_bcd(0),
1296 ]);
1297 let p = PlayPosition::parse(&resp).unwrap();
1298 assert_eq!(p.track, 2);
1299 assert_eq!(p.index, 1);
1300 assert_eq!(
1301 (p.relative_min, p.relative_sec, p.relative_frame),
1302 (1, 23, 45)
1303 );
1304 assert_eq!(p.relative_sectors(), 6270);
1306 assert_eq!(p.relative_millis(), 83_600);
1308 }
1309
1310 #[test]
1311 fn play_position_rejects_short_response() {
1312 assert!(PlayPosition::parse(&response_from(&[0x02, 0x01, 0x00])).is_none());
1313 }
1314}