1use std::time::Duration;
21
22use bitflags::bitflags;
23use displaydoc::Display;
24use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
25use thiserror::Error;
26use tracing::debug;
27use winnow::{
28 Parser,
29 binary::{
30 be_u16,
31 bits::{bits, bool, take as bits_take},
32 length_and_then, u8,
33 },
34 combinator::{preceded, separated_pair},
35 error::{ContextError, StrContext},
36 token::{rest, take},
37};
38
39use crate::{
40 Mmc,
41 mmc::{Cdb, MmcDirection, MmcError},
42};
43
44impl Mmc {
46 pub fn supported_events(&self) -> Result<EventClass, MmcStatusError> {
48 let data = self.get_event_status_notification(EventMode::Polled, EventClass::empty())?;
49 let supported_events = parse_header(EventClass::empty(), &mut data.as_slice())?;
50
51 Ok(supported_events)
52 }
53
54 fn get_event_status_notification(
56 &self,
57 mode: EventMode,
58 class: EventClass,
59 ) -> Result<EventData, MmcError> {
60 let mut data = EventData::default();
61 let mut cdb = Cdb::default();
62 cdb[0] = OPCODE;
63 cdb[1] = if mode == EventMode::Polled { 1 } else { 0 };
64 cdb[4] = class.bits();
65 cdb[7..9].copy_from_slice(&(data.len() as u16).to_be_bytes());
66
67 self.run_command(Some(MmcDirection::Read), &mut data, cdb)?;
68
69 return Ok(data);
70
71 const OPCODE: u8 = 0x4A;
72 }
73
74 pub fn is_tray_open(&self) -> Result<bool, MmcStatusError> {
78 self.media_status()
79 .map(|status| status.state.contains(MediaState::DoorOrTrayOpen))
80 }
81
82 pub fn operational_event_status(&self) -> Result<OperationalStatus, MmcStatusError> {
84 let data =
85 self.get_event_status_notification(EventMode::Polled, EventClass::OperationalChange)?;
86 let input = &mut data.as_slice();
87 parse_header(EventClass::OperationalChange, input)?;
88
89 let prevent_bit_and_op_status = bits(separated_pair(
91 bool,
92 take(3_usize),
93 bits_take::<_, u8, _, ContextError>(4_usize),
94 ));
95 let (_, (prevent_bit, _op_status), op_change) =
98 (parse_event_code, prevent_bit_and_op_status, be_u16)
99 .context(StrContext::Label("operational change event descriptor"))
100 .parse_next(input)?;
101 let change = (op_change != 0)
102 .then(|| OperationalEvent::try_from(op_change))
103 .transpose()?;
104
105 Ok(OperationalStatus {
106 event: change,
107 persistent_prevent: prevent_bit,
108 })
109 }
110
111 pub fn power_status(&self) -> Result<PowerStatus, MmcStatusError> {
113 let data =
114 self.get_event_status_notification(EventMode::Polled, EventClass::PowerManagement)?;
115 let input = &mut data.as_slice();
116 parse_header(EventClass::PowerManagement, input)?;
117
118 let (event_code, status) = (parse_event_code, u8)
119 .context(StrContext::Label("power management event descriptor"))
120 .parse_next(input)?;
121 let event = (event_code != 0)
122 .then(|| PowerEvent::try_from(event_code))
123 .transpose()?;
124 let state = PowerState::try_from(status)?;
125
126 Ok(PowerStatus { event, state })
127 }
128
129 pub fn external_status(&self) -> Result<ExternalRequestStatus, MmcStatusError> {
131 let data =
132 self.get_event_status_notification(EventMode::Polled, EventClass::ExternalRequest)?;
133 let input = &mut data.as_slice();
134 parse_header(EventClass::ExternalRequest, input)?;
135
136 let status = bits(preceded(
139 bits_take::<_, u8, _, ContextError>(4_usize),
140 bits_take::<_, u8, _, _>(4_usize),
141 ));
142 let (event_code, status, request) = (parse_event_code, status, be_u16)
143 .context(StrContext::Label("external request event descriptor"))
144 .parse_next(input)?;
145 let event = (event_code != 0)
146 .then(|| ExternalRequestEvent::try_from(event_code))
147 .transpose()?;
148 let state = ExternalRequestState::try_from(status)?;
149 let request = (request != 0)
150 .then(|| ExternalRequest::try_from(request))
151 .transpose()?;
152
153 Ok(ExternalRequestStatus {
154 event,
155 state,
156 request,
157 })
158 }
159
160 pub fn media_status(&self) -> Result<MediaStatus, MmcStatusError> {
162 let data = self.get_event_status_notification(EventMode::Polled, EventClass::Media)?;
163 let input = &mut data.as_slice();
164 parse_header(EventClass::Media, input)?;
165
166 let (event_code, status, start_slot, end_slot) = (parse_event_code, u8, u8, u8)
167 .context(StrContext::Label("media event descriptor"))
168 .parse_next(input)?;
169 let event = (event_code != 0)
170 .then(|| MediaEvent::try_from(event_code))
171 .transpose()?;
172 let state = MediaState::from_bits_truncate(status);
173
174 Ok(MediaStatus {
175 event,
176 state,
177 start_slot,
178 end_slot,
179 })
180 }
181
182 pub fn multihost_status(&self) -> Result<MultiHostStatus, MmcStatusError> {
184 let data = self.get_event_status_notification(EventMode::Polled, EventClass::MultiHost)?;
185 let input = &mut data.as_slice();
186 parse_header(EventClass::MultiHost, input)?;
187
188 let status = bits(preceded(
191 bits_take::<_, u8, _, ContextError>(4_usize),
192 bits_take::<_, u8, _, _>(4_usize),
193 ));
194 let (event_code, status, priority) = (parse_event_code, status, be_u16)
195 .context(StrContext::Label("multiple host event descriptor"))
196 .parse_next(input)?;
197 let event = (event_code != 0)
198 .then(|| MultiHostEvent::try_from(event_code))
199 .transpose()?;
200 let state = MultiHostState::try_from(status)?;
201 let priority = (priority != 0)
202 .then(|| MultiHostPriority::try_from(priority))
203 .transpose()?;
204
205 Ok(MultiHostStatus {
206 event,
207 state,
208 priority,
209 })
210 }
211
212 pub fn busy_status(&self) -> Result<BusyStatus, MmcStatusError> {
214 let data = self.get_event_status_notification(EventMode::Polled, EventClass::DeviceBusy)?;
215 let input = &mut data.as_slice();
216 parse_header(EventClass::DeviceBusy, input)?;
217
218 let (event_code, status, time) = (parse_event_code, u8, be_u16)
219 .context(StrContext::Label("device busy event descriptor"))
220 .parse_next(input)?;
221 let event = (event_code != 0)
222 .then(|| BusyEvent::try_from(event_code))
223 .transpose()?;
224 let state = BusyState::try_from(status)?;
225 let time = (state != BusyState::NotBusy).then(|| Duration::from_millis(u64::from(time)));
226
227 Ok(BusyStatus {
228 event,
229 state,
230 busy_time: time,
231 })
232 }
233}
234type EventData = [u8; 12];
235
236bitflags! {
237 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
239 pub struct EventClass: u8 {
240 const OperationalChange = 1 << 1;
242 const PowerManagement = 1 << 2;
244 const ExternalRequest = 1 << 3;
246 const Media = 1 << 4;
248 const MultiHost = 1 << 5;
250 const DeviceBusy = 1 << 6;
252 }
253}
254
255#[non_exhaustive]
257#[derive(Debug, Display, Error)]
258pub enum MmcStatusError {
259 Cmd(#[from] MmcError),
261
262 InvalidResponse(String),
264
265 EventNotSupported(EventClass),
267}
268
269impl From<ContextError> for MmcStatusError {
270 fn from(err: ContextError) -> Self {
271 Self::InvalidResponse(err.to_string())
272 }
273}
274
275impl<T: TryFromPrimitive> From<TryFromPrimitiveError<T>> for MmcStatusError {
276 fn from(err: TryFromPrimitiveError<T>) -> Self {
277 Self::InvalidResponse(err.to_string())
278 }
279}
280
281#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
283enum EventMode {
284 #[allow(unused)]
286 Async,
287
288 #[default]
290 Polled,
291}
292
293fn parse_header(
295 expected_class: EventClass,
296 input: &mut &[u8],
297) -> Result<EventClass, MmcStatusError> {
298 debug!(header = ?input, "parse_header()");
299 let nea_and_notif_class = bits(separated_pair::<_, _, u8, u8, ContextError, _, _, _>(
300 bool,
301 bits_take(4_usize),
302 bits_take(3_usize),
303 ));
304 let ((nea, notif_class), supported_events, remaining) =
305 length_and_then(be_u16, (nea_and_notif_class, u8, rest))
306 .context(StrContext::Label(
307 "GET EVENT STATUS NOTIFICATION response header",
308 ))
309 .parse_next(input)?;
310 *input = remaining;
311 if !expected_class.is_empty() && nea {
312 return Err(MmcStatusError::EventNotSupported(expected_class));
313 }
314 let event_class = 1 << notif_class;
315 if !expected_class.is_empty() && event_class != expected_class.bits() {
316 return Err(MmcStatusError::InvalidResponse(format!(
317 "invalid event code, expected 0b{:b} got 0b{:b}",
318 expected_class.bits(),
319 event_class
320 )));
321 }
322
323 Ok(EventClass::from_bits_truncate(supported_events))
324}
325
326#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
328pub struct OperationalStatus {
329 pub event: Option<OperationalEvent>,
331
332 pub persistent_prevent: bool,
338}
339#[repr(u16)]
341#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
342pub enum OperationalEvent {
343 #[default]
345 FeatureChange = 0x1,
346
347 NewFeatures = 0x2,
349
350 Reset = 0x3,
352
353 FirmwareChanged = 0x4,
355
356 InquiryChange = 0x5,
358}
359
360fn parse_event_code(input: &mut &[u8]) -> winnow::Result<u8> {
362 bits(preceded(
363 take::<_, _, ContextError>(4_usize),
364 bits_take(4_usize),
365 ))
366 .context(StrContext::Label("event code"))
367 .parse_next(input)
368}
369
370#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
372pub struct PowerStatus {
373 pub event: Option<PowerEvent>,
375
376 pub state: PowerState,
378}
379#[repr(u8)]
381#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
382pub enum PowerEvent {
383 #[default]
385 PwrChgSuccessful = 0x1,
386
387 PwrChgFail = 0x2,
390}
391#[repr(u8)]
393#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
394pub enum PowerState {
395 #[default]
397 Active = 0x1,
398
399 Idle = 0x2,
401
402 Standby = 0x3,
404
405 Sleep = 0x4,
407}
408
409#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
411pub struct ExternalRequestStatus {
412 pub event: Option<ExternalRequestEvent>,
414
415 pub state: ExternalRequestState,
417
418 pub request: Option<ExternalRequest>,
420}
421#[repr(u8)]
423#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
424pub enum ExternalRequestEvent {
425 DriveKeyDown = 0x1,
427
428 DriveKeyUp = 0x2,
430
431 #[default]
435 ExternalRequestNotification = 0x3,
436}
437#[repr(u8)]
439#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
440pub enum ExternalRequestState {
441 #[default]
443 Ready = 0x0,
444
445 OtherPrevent = 0x1,
447}
448#[repr(u16)]
450#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
451pub enum ExternalRequest {
452 Overrun = 0x1,
454
455 Play = 0x101,
457
458 RewindOrBack = 0x102,
460
461 FastForward = 0x103,
463
464 Pause = 0x104,
466
467 Stop = 0x106,
469
470 Ascii = 0x107,
472
473 #[default]
475 #[num_enum(alternatives = [0xF001..=0xFFFF])]
476 VendorUnique = 0xF000,
477}
478
479#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
481pub struct MediaStatus {
482 pub event: Option<MediaEvent>,
484
485 pub state: MediaState,
487
488 pub start_slot: u8,
492
493 pub end_slot: u8,
497}
498#[repr(u8)]
500#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
501pub enum MediaEvent {
502 #[default]
505 EjectRequest = 0x1,
506
507 NewMedia = 0x2,
509
510 MediaRemoval = 0x3,
514
515 MediaChanged = 0x4,
518
519 BackgroundFormatCompleted = 0x5,
521
522 BackgroundFormatRestarted = 0x6,
525}
526bitflags! {
527 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
529 pub struct MediaState: u8 {
530 const DoorOrTrayOpen = 1;
532
533 const MediaPresent = 1 << 1;
535 }
536}
537
538#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
540pub struct MultiHostStatus {
541 pub event: Option<MultiHostEvent>,
543
544 pub state: MultiHostState,
546
547 pub priority: Option<MultiHostPriority>,
549}
550#[repr(u8)]
552#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
553pub enum MultiHostEvent {
554 #[default]
556 ControlRequest = 0x1,
557
558 ControlGrant = 0x2,
560
561 ControlRelease = 0x3,
563}
564#[repr(u8)]
566#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
567pub enum MultiHostState {
568 #[default]
570 Ready = 0x0,
571
572 OtherPrevent = 0x1,
574}
575#[repr(u16)]
577#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
578pub enum MultiHostPriority {
579 #[default]
581 Low = 0x1,
582
583 Medium = 0x2,
585
586 High = 0x3,
588}
589
590#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
593pub struct BusyStatus {
594 pub event: Option<BusyEvent>,
596
597 pub state: BusyState,
599
600 pub busy_time: Option<Duration>,
603}
604#[repr(u8)]
606#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
607pub enum BusyEvent {
608 #[default]
610 Change = 0x1,
611
612 LoChange = 0x2,
615}
616#[repr(u8)]
618#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
619pub enum BusyState {
620 #[default]
622 NotBusy = 0x0,
623
624 Busy = 0x1,
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 use tracing::info;
633
634 #[test_log::test(test)]
635 #[ignore = "requires a disc drive with mmc"]
636 fn supported_events() {
637 Mmc::new().unwrap().supported_events().unwrap();
638 }
639
640 #[test_log::test(test)]
641 #[ignore = "requires a disc drive with mmc"]
642 fn is_tray_open() {
643 let is_tray_open = Mmc::new().unwrap().is_tray_open();
644 info!(?is_tray_open);
645 assert!(matches!(
646 is_tray_open,
647 Ok(_) | Err(MmcStatusError::EventNotSupported(_))
648 ));
649 }
650
651 #[test_log::test(test)]
652 #[ignore = "requires a disc drive with mmc"]
653 fn operational_status() {
654 let status = Mmc::new().unwrap().operational_event_status();
655 info!(?status);
656 assert!(matches!(
657 status,
658 Ok(_) | Err(MmcStatusError::EventNotSupported(_))
659 ));
660 }
661
662 #[test_log::test(test)]
663 #[ignore = "requires a disc drive with mmc"]
664 fn power_status() {
665 let status = Mmc::new().unwrap().power_status();
666 info!(?status);
667 assert!(matches!(
668 status,
669 Ok(_) | Err(MmcStatusError::EventNotSupported(_))
670 ));
671 }
672
673 #[test_log::test(test)]
674 #[ignore = "requires a disc drive with mmc"]
675 fn external_status() {
676 let status = Mmc::new().unwrap().external_status();
677 info!(?status);
678 assert!(matches!(
679 status,
680 Ok(_) | Err(MmcStatusError::EventNotSupported(_))
681 ));
682 }
683
684 #[test_log::test(test)]
685 #[ignore = "requires a disc drive with mmc"]
686 fn media_status() {
687 let status = Mmc::new().unwrap().media_status();
688 info!(?status);
689 assert!(matches!(
690 status,
691 Ok(_) | Err(MmcStatusError::EventNotSupported(_))
692 ));
693 }
694
695 #[test_log::test(test)]
696 #[ignore = "requires a disc drive with mmc"]
697 fn multihost_status() {
698 let status = Mmc::new().unwrap().multihost_status();
699 info!(?status);
700 assert!(matches!(
701 status,
702 Ok(_) | Err(MmcStatusError::EventNotSupported(_))
703 ));
704 }
705
706 #[test_log::test(test)]
707 #[ignore = "requires a disc drive with mmc"]
708 fn busy_status() {
709 let status = Mmc::new().unwrap().busy_status();
710 info!(?status);
711 assert!(matches!(
712 status,
713 Ok(_) | Err(MmcStatusError::EventNotSupported(_))
714 ));
715 }
716}