libcdio_rs/mmc/get_config.rs
1// Copyright (C) 2026 Shiva Kiran Koninty <shiva@skran.xyz>
2//
3// This file is part of libcdio-rs.
4//
5// libcdio-rs is free software: you can redistribute it and/or
6// modify it under the terms of the GNU General Public License as
7// published by the Free Software Foundation, either version 3 of the
8// License, or (at your option) any later version.
9//
10// libcdio-rs is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13// General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with libcdio-rs. If not, see <https://www.gnu.org/licenses/>.
17
18//! SCSI MMC (MultiMedia Commands) GET CONFIGURATION command.
19
20use displaydoc::Display;
21use num_enum::TryFromPrimitive;
22use tracing::{debug, error};
23
24use crate::mmc::{Mmc, MmcOperationError};
25
26/// Methods related to the `GET CONFIGURATION` command.
27impl Mmc {
28 /// Get a list of MMC features supported by the device.
29 ///
30 /// Returns `None` on error or if the device is unavailable.
31 pub fn features(&self) -> Result<Vec<MmcFeature>, MmcOperationError> {
32 let mut buf = [0_u8; ALLOC_BUF_SIZE];
33 let retval = unsafe {
34 libcdio_sys::mmc_get_configuration(
35 self.cdio.cdio.as_ptr(),
36 buf.as_mut_ptr().cast(),
37 buf.len() as u32,
38 GET_CONF_RET_TYPE_ZERO,
39 STARTING_FEATURE_NUMBER,
40 TIMEOUT_MILLIS,
41 )
42 };
43 if retval != 0 {
44 error!(retval, "non success code from mmc_get_configuration()");
45 return Err(MmcOperationError);
46 };
47
48 let dtors_len = read_u32(&buf) as usize;
49 // data length (len) doesn't include its own length..
50 let expected = dtors_len + 4;
51 if buf.len() < expected {
52 error!(
53 expected,
54 len = buf.len(),
55 "insufficient buffer length for mmc response data",
56 );
57 return Err(MmcOperationError);
58 }
59 // feature descriptors
60 let dtors = &buf[8..8 + dtors_len - 4];
61
62 let mut features = Vec::new();
63 let mut i = 0;
64 while i + 3 < dtors.len() {
65 let dtor_len = usize::from(dtors[i + 3]) + 4;
66 let dtor = &dtors[i..i + dtor_len];
67 if let Some(feature) = MmcFeature::parse(dtor) {
68 features.push(feature);
69 };
70 i += dtor_len;
71 }
72
73 /// Return type to request all features
74 const GET_CONF_RET_TYPE_ZERO: u32 = 0x0;
75 const ALLOC_BUF_SIZE: usize = 4 * 1024;
76 const TIMEOUT_MILLIS: u32 = 6000;
77 /// Starting feature set to profile list, to get all features
78 const STARTING_FEATURE_NUMBER: u32 = 0;
79
80 Ok(features)
81 }
82}
83
84// WARNING: Changes to the doc comments can affect the type's display output!
85/// A set of commands and behaviours that specify the capabilities of a drive
86/// and its associated medium.
87#[non_exhaustive]
88#[derive(Clone, Debug, Default, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
89#[ignore_extra_doc_attributes]
90pub enum MmcFeature {
91 /// Profile List
92 ///
93 /// A list of all profiles supported by the drive
94 ProfileList { profiles: Vec<MmcProfile> },
95
96 /// Core
97 ///
98 /// Mandatory behavior for all devices
99 Core {
100 /// Physical interface standard reported by MMC.
101 /// <div class="warning">
102 ///
103 /// **NOTE**: It is possible that more than one physical interface exists between the Host and Drive, e.g., an
104 /// IEEE1394 Host connecting to an ATAPI bridge to an ATAPI Drive. The Drive may not be aware of
105 /// interfaces beyond the ATAPI.
106 ///
107 /// </div>
108 interface: MmcInterface,
109 },
110
111 /// Morphing
112 ///
113 /// Ability to report operational changes to the host and accept requests to
114 /// prevent operational changes
115 Morphing {
116 /// Supports async in addition to polling implementations of
117 /// `GET EVENT STATUS NOTIFICATION`
118 async_events: bool,
119 /// Supports Operational Change Request/Notification Class Events
120 op_chg_events: bool,
121 },
122
123 /// Removable Medium
124 ///
125 /// Ability to remove the medium from the device
126 RemovableMedium {
127 /// The drive is capable of ejecting media via `START/STOP` commands
128 eject: bool,
129 /// The drive is capable of locking media
130 lock: bool,
131 /// The drive has a prevent jumper
132 prevent_jumper: bool,
133 /// The Loading mechanism type used by the drive
134 load_mech: MmcLoadMech,
135 },
136
137 /// Write Protect
138 ///
139 /// Ability to control write protection status
140 WriteProtect,
141
142 /// Random Readable
143 ///
144 /// Ability to read sectors with random addressing
145 RandomReadable,
146
147 /// Multi-Read
148 ///
149 /// The drive is able to read all CD media types; based on OSTA MultiRead
150 MultiRead,
151
152 /// CD Read
153 ///
154 /// Ability to read CD-specific structures
155 CdRead {
156 /// The feature is currently active
157 active: bool,
158 /// Supports C2 error pointers
159 c2_error: bool,
160 /// Supports CD-Text (Format Code `5h` of `READ TOC/PMA/ATIP`)
161 cd_text: bool,
162 /// Supports DAP bit for the `CDB` in `READ CD` and `READ CD MSF` commands
163 dap: bool,
164 },
165
166 /// DVD Read
167 ///
168 /// Ability to read DVD-specific structures
169 DvdRead,
170
171 /// Random Writable
172 ///
173 /// Write support for randomly addressed writes
174 RandomWritable,
175
176 /// Incremental Streaming Writable
177 ///
178 /// Write support for sequential recording
179 IncrementalStreamingWritable,
180
181 /// Sector Erasable
182 ///
183 /// Write support for erasable media and media that requires an erase pass
184 /// before overwrite
185 SectorErasable,
186
187 /// Formattable
188 ///
189 /// Support for formatting of media
190 Formattable,
191
192 /// Hardware Defect Management
193 ///
194 /// Ability of the drive/media system to provide an apparently defect-free
195 /// space
196 HardwareDefectManagement,
197
198 /// Write Once
199 ///
200 /// Write support for write-once media that is writable in random order
201 WriteOnce,
202
203 /// Restricted Overwrite
204 ///
205 /// Write support for media that shall be written from blocking boundaries
206 /// only
207 RestrictedOverwrite,
208
209 /// CD-RW CAV Write
210 ///
211 /// Ability to write high-speed CD-RW media
212 CdRwCavWrite,
213
214 /// MRW
215 ///
216 /// Ability to recognize and read and optionally write MRW formatted
217 /// media
218 Mrw,
219
220 /// Enhanced Defect Reporting
221 ///
222 /// Ability to control `RECOVERED ERROR` reporting
223 EnhancedDefectReporting,
224
225 /// Ability to recognize, read and optionally write DVD+RW media
226 DvdPlusRw,
227
228 /// DVD+R
229 ///
230 /// Ability to read DVD+R recorded media formats
231 DvdPlusR,
232
233 /// Rigid Restricted Overwrite
234 ///
235 /// Write support for media that is required to be written from Blocking
236 /// boundaries with length of integral multiple of Blocking size only.
237 RigidRestrictedOverwrite,
238
239 /// CD Track At Once
240 ///
241 /// Ability to write CD with Track at Once recording
242 CdTrackAtOnce,
243
244 /// CD Mastering
245 ///
246 /// Ability to write CD with Session at Once or Raw write methods
247 CdMastering,
248
249 /// DVD-R/-RW Write
250 ///
251 /// Ability to write DVD specific structures
252 DvdRRwWrite,
253
254 /// DDCD Read
255 ///
256 /// Ability to read user data from DDCD blocks
257 DdcdRead,
258
259 /// DDCD-R Write
260 ///
261 /// Ability to write and read DDCD-R media
262 DdcdRWrite,
263
264 /// DDCD-RW Write
265 ///
266 /// Ability to write and read DDCD-RW media
267 DdcdRwWrite,
268
269 /// Layer Jump Recording
270 ///
271 /// Ability to record in layer jump mode
272 LayerJumpRecording,
273
274 /// Layer Jump Rigid Restricted Overwrite
275 ///
276 /// Ability to perform Layer Jump recording on Rigid Restricted
277 /// Overwritable media
278 LayerJumpRigidRestrictedOverwrite,
279
280 /// Stop Long Operation
281 ///
282 /// Ability to stop the long immediate operation by a command
283 StopLongOperation,
284
285 /// CD-RW Media Write Support
286 ///
287 /// Ability to report CD-RW media sub-types that are supported
288 /// for write
289 CdRwMediaWriteSupport,
290
291 /// BD-R POW
292 ///
293 /// Logical Block overwrite service on BD-R discs formatted as SRM+POW
294 BdRPow,
295
296 /// DVD+RW Dual Layer
297 ///
298 /// Ability to read DVD+RW Dual Layer recorded media formats
299 DvdPlusRwDualLayer,
300
301 /// DVD+R Dual Layer
302 ///
303 /// Ability to read DVD+R Dual Layer recorded media formats
304 DvdPlusRDualLayer,
305
306 /// BD Read
307 ///
308 /// Ability to read control structures and user data from a BD disc
309 BdRead,
310
311 /// BD Write
312 ///
313 /// Ability to write control structures and user data to certain BD
314 /// discs
315 BdWrite,
316
317 /// Timely Safe Recording
318 ///
319 /// Timely, Safe Recording permits the Host to schedule defect management
320 Tsr,
321
322 /// HD DVD Read
323 ///
324 /// Ability to read control structures and user data from a HD DVD disc
325 HdDvdRead,
326
327 /// HD DVD Write
328 ///
329 /// Ability to write control structures and user data from a HD DVD disc
330 HdDvdWrite,
331
332 /// HD DVD-RW Fragment
333 ///
334 /// HD DVD-RW fragment recording
335 HdDvdRwFragment,
336
337 /// Hybrid Disc
338 ///
339 /// Ability to access some hybrid discs
340 HybridDisc,
341
342 /// Power Management
343 ///
344 /// Host and device directed power management
345 PowerManagement,
346
347 /// SMART
348 ///
349 /// Ability to perform Self Monitoring Analysis and Reporting Technology
350 Smart,
351
352 /// Embedded Changer
353 ///
354 /// Single mechanism multiple disc changer
355 EmbeddedChanger,
356
357 /// CD Audio External Play
358 ///
359 /// Ability to play audio CDs via the Logical Unit’s own analog output
360 CdAudioExternalPlay {
361 /// Feature is currently active
362 active: bool,
363 /// Supports the `SCAN` command
364 scan: bool,
365 /// Supports independent mute of audio channels
366 sep_channel_mute: bool,
367 /// Supports independent volume levels for audio channels
368 sep_volume: bool,
369 /// Number of discrete volume levels supported
370 volume_levels: u16,
371 },
372
373 /// Microcode Upgrade
374 ///
375 /// Ability for the device to accept new microcode via the interface
376 MicrocodeUpgrade,
377
378 /// Timeout
379 ///
380 /// Ability to respond to all commands within a specific time
381 Timeout,
382
383 /// DVD-CSS
384 ///
385 /// Ability to perform DVD CSS/CPPM authentication and RPC
386 DvdCss {
387 /// Feature is currently active (DVD CSS/CPPM media is present)
388 active: bool,
389 /// CSS Version
390 version: u8,
391 },
392
393 /// Real Time Streaming
394 ///
395 /// Ability to read and write using host requested performance parameters
396 RealTimeStreaming,
397
398 /// Drive Serial Number
399 ///
400 /// The drive has a unique identifier
401 DriveSerialNumber { sno: String },
402
403 /// Media Serial Number
404 ///
405 /// Ability to return unique Media Serial Number
406 MediaSerialNumber,
407
408 /// Disc Control Blocks
409 ///
410 /// Ability to read and/or write DCBs
411 DiscControlBlocks,
412
413 /// DVD CPRM
414 ///
415 /// Ability to perform DVD CPRM authentication
416 DvdCprm,
417
418 /// Firmware Information
419 ///
420 /// Firmware creation date report
421 FirmwareInformation,
422
423 /// AACS
424 ///
425 /// Ability to decode and optionally encode AACS protected information
426 Aacs,
427
428 /// DVD CSS Managed Recording
429 ///
430 /// Ability to perform DVD CSS managed recording
431 DvdCssManagedRecording,
432
433 /// VCPS
434 ///
435 /// Ability to decode and optionally encode VCPS protected information
436 Vcps,
437
438 /// SecurDisc
439 ///
440 /// Ability to encode and decode SecurDisc protected information
441 ///
442 SecurDisc,
443
444 /// OSSC
445 ///
446 /// TCG Optical Security Subsystem Class feature
447 Ossc,
448
449 /// Vendor Specific
450 ///
451 /// Vendor-specific feature
452 #[default]
453 VendorSpecific,
454}
455
456impl MmcFeature {
457 /// Parse a feature from a slice pointing to a feature descriptor.
458 fn parse(dtor: &[u8]) -> Option<Self> {
459 if dtor.len() < 4 {
460 error!(
461 len = dtor.len(),
462 "mmc feature descriptor buffer must be atleast 4 bytes",
463 );
464 return None;
465 }
466 let data_len = usize::from(dtor[3]);
467 let expected = data_len + 4;
468 if dtor.len() != expected {
469 debug!(
470 expected,
471 len = dtor.len(),
472 "mmc feature descriptor buffer has insufficient size",
473 );
474 return None;
475 };
476
477 let code = read_u16(dtor);
478 match code {
479 0x0000 => Some(Self::profile_list(dtor)),
480 0x0001 => Self::core(dtor),
481 0x0002 => Some(Self::morphing(dtor)),
482 0x0003 => Self::medium(dtor),
483 0x001e => Some(Self::cd_read(dtor)),
484 0x0103 => Some(Self::cd_audio_external_play(dtor)),
485 0x0106 => Some(Self::dvd_css(dtor)),
486 0x0108 => Self::drive_serial_num(dtor),
487 _ => None,
488 }
489 }
490
491 fn profile_list(dtor: &[u8]) -> Self {
492 let data = &dtor[4..]; // skip the header
493 let profiles = data
494 .chunks_exact(4)
495 .filter_map(|chunk| {
496 let kind = read_u16(chunk);
497 Some(MmcProfile {
498 active: chunk[2] & 0b1 != 0,
499 kind: ProfileKind::try_from(kind)
500 .inspect_err(|err| error!(?err, kind, "invalid profile kind from mmc"))
501 .ok()?,
502 })
503 })
504 .collect();
505
506 MmcFeature::ProfileList { profiles }
507 }
508
509 fn core(dtor: &[u8]) -> Option<Self> {
510 let interface = read_u32(&dtor[4..]);
511 Some(MmcFeature::Core {
512 interface: MmcInterface::try_from(interface)
513 .inspect_err(|err| error!(?err, interface, "got invalid interface value from mmc"))
514 .ok()?,
515 })
516 }
517
518 fn morphing(dtor: &[u8]) -> Self {
519 Self::Morphing {
520 async_events: dtor[4] & 1 != 0,
521 op_chg_events: dtor[4] & 1 << 1 != 0,
522 }
523 }
524
525 fn medium(dtor: &[u8]) -> Option<Self> {
526 let load_mech = dtor[4] >> 5;
527 let load_mech = MmcLoadMech::try_from(load_mech)
528 .inspect_err(|err| error!(?err, load_mech, "got invalid loading mech value from mmc"))
529 .ok()?;
530
531 Some(Self::RemovableMedium {
532 eject: dtor[4] & 1 << 3 != 0,
533 lock: dtor[4] & 1 != 0,
534 prevent_jumper: dtor[4] & 1 << 2 == 0,
535 load_mech,
536 })
537 }
538
539 fn cd_read(data: &[u8]) -> Self {
540 Self::CdRead {
541 active: data[2] & 1 != 0,
542 c2_error: data[4] & 1 != 0,
543 cd_text: data[4] & 1 << 1 != 0,
544 dap: data[4] & 1 << 7 != 0,
545 }
546 }
547
548 fn cd_audio_external_play(dtor: &[u8]) -> Self {
549 Self::CdAudioExternalPlay {
550 active: dtor[2] & 1 != 0,
551 scan: dtor[4] & 1 << 2 != 0,
552 sep_channel_mute: dtor[4] & 1 << 1 != 0,
553 sep_volume: dtor[4] & 1 != 0,
554 volume_levels: read_u16(&dtor[6..]),
555 }
556 }
557
558 fn dvd_css(dtor: &[u8]) -> Self {
559 Self::DvdCss {
560 active: dtor[2] & 1 != 0,
561 version: dtor[7],
562 }
563 }
564
565 fn drive_serial_num(dtor: &[u8]) -> Option<Self> {
566 let current = dtor[2] & 1 != 0;
567 if !current {
568 return None;
569 }
570 let len = usize::from(dtor[3]);
571 Some(Self::DriveSerialNumber {
572 sno: String::from_utf8(dtor[4..4 + len].to_vec())
573 .inspect_err(|err| {
574 error!(?err, "could not make a string from mmc drive serial number")
575 })
576 .ok()?,
577 })
578 }
579}
580
581/// A base set of functions for specific drive/media combination.
582#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
583pub struct MmcProfile {
584 /// Profile's current bit is set
585 pub active: bool,
586 /// Profile type
587 pub kind: ProfileKind,
588}
589// WARNING: Changes to the doc comments can affect the type's display output!
590/// Represents the complete set of MMC feature profiles for optical disc drives.
591/// Each variant corresponds to a specific media type and recording capability.
592#[repr(u16)]
593#[non_exhaustive]
594#[derive(
595 Clone, Copy, Debug, Default, Display, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive,
596)]
597pub enum ProfileKind {
598 /// Non-removable disk
599 NonRemovable = 0x0001,
600
601 /// Removable disk
602 Removable = 0x0002,
603
604 /// Magneto-Optical Erasable disk
605 MoErasable = 0x0003,
606
607 /// Optical Write-Once
608 OpticalWriteOnce = 0x0004,
609
610 /// Advance Storage - Magneto-Optical
611 AsMo = 0x0005,
612
613 /// CD-ROM
614 CdRom = 0x0008,
615
616 /// CD-R
617 CdR = 0x0009,
618
619 /// CD-RW
620 CdRw = 0x000A,
621
622 /// DVD-ROM
623 DvdRom = 0x0010,
624
625 /// DVD-R Sequential Recording
626 DvdRSeqRec = 0x0011,
627
628 /// DVD-RAM
629 DvdRam = 0x0012,
630
631 /// DVD-RW Restricted Overwrite
632 DvdRwRo = 0x0013,
633
634 /// DVD-RW Sequential Recording
635 DvdRwSeqRec = 0x0014,
636
637 /// DVD-R Dual Layer Sequential recording
638 DvdRDlSeqRec = 0x0015,
639
640 /// DVD-R Dual Layer Jump Recording
641 DvdRDlJmpRec = 0x0016,
642
643 /// DVD-RW Dual Layer
644 DvdRwDl = 0x0017,
645
646 /// DVD-Download Disc Recording
647 DvdDownDiscRec = 0x0018,
648
649 /// DVD+RW
650 DvdPlusRw = 0x001A,
651
652 /// DVD+R
653 DvdPlusR = 0x001B,
654
655 /// DDCD-ROM
656 DdcdRom = 0x0020,
657
658 /// DDCD-R
659 DdcdR = 0x0021,
660
661 /// DDCD-RW
662 DdcdRw = 0x0022,
663
664 /// DVD+RW Dual Layer
665 DvdPlusRwDl = 0x002A,
666
667 /// DVD+R Double Layer
668 DvdPlusRDl = 0x002B,
669
670 /// BD-ROM
671 BdRom = 0x0040,
672
673 /// BD-R Sequential Recording Mode
674 BdRSeqRec = 0x0041,
675
676 /// BD-R Random Recording Mode
677 BdRRandRec = 0x0042,
678
679 /// BD-RE
680 BdRw = 0x0043,
681
682 /// HD DVD-ROM
683 HdDvdRom = 0x0050,
684
685 /// HD DVD-R
686 HdDvdR = 0x0051,
687
688 /// HD DVD-RAM
689 HdDvdRam = 0x0052,
690
691 /// HD DVD-RW
692 HdDvdRw = 0x0053,
693
694 /// HD DVD-R Dual Layer
695 HdDvdRDl = 0x0058,
696
697 /// HD DVD-RW Dual Layer
698 HdDvdRwDl = 0x0059,
699
700 /// The Drive does not conform to any Profile
701 #[default]
702 NonConform = 0xFFFF,
703}
704
705// WARNING: Changes to the doc comments can affect the type's display output!
706/// Physical interface standard reported by MMC.
707#[repr(u32)]
708#[non_exhaustive]
709#[derive(
710 Clone, Copy, Debug, Default, Display, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive,
711)]
712pub enum MmcInterface {
713 #[default]
714 /// Unspecified
715 Unspecified = 0x0,
716 /// SCSI
717 Scsi = 0x1,
718 /// ATAPI
719 Atapi = 0x2,
720 /// IEEE 1394
721 Ieee1394 = 0x3,
722 /// IEEE 1394A
723 Ieee1394A = 0x4,
724 /// Fibre Channel
725 FibreChannel = 0x5,
726 /// IEEE 1394B
727 Ieee1394B = 0x6,
728 /// Serial ATAPI
729 SerialAtapi = 0x7,
730 /// USB (both 1.1 and 2.0)
731 Usb = 0x8,
732 /// Vendor Unique
733 VendorUnique = 0xffff,
734}
735
736/// Removable medium info about the drive, reported by MMC.
737#[derive(Clone, Copy, Debug)]
738pub struct MmcMedium {
739 /// The drive is capable of ejecting media via START/STOP commands
740 pub eject: bool,
741 /// The drive is capable of locking media
742 pub lock: bool,
743 /// The drive has a prevent jumper
744 pub prevent_jumper: bool,
745 /// The Loading mechanism type used by the drive
746 pub load_mech: MmcLoadMech,
747}
748
749// WARNING: Changes to the doc comments can affect the type's display output!
750/// Loading mechanism type used by the drive.
751#[repr(u8)]
752#[non_exhaustive]
753#[derive(
754 Clone, Copy, Debug, Default, Display, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive,
755)]
756pub enum MmcLoadMech {
757 /// Caddy/Slot type
758 CaddySlot = 0b000,
759 #[default]
760 /// Tray type
761 Tray = 0b001,
762 /// Pop-up type
763 PopUp = 0b010,
764 /// Embedded changer with individually changeable discs
765 EmbeddedChangerIndividualDiscs = 0b100,
766 /// Embedded changer using a magazine mechanism
767 EmbeddedChangerMagazine = 0b101,
768}
769
770/// Parse a big endian u16 out of the next two bytes.
771fn read_u16(val: &[u8]) -> u16 {
772 let val = *val
773 .first_chunk()
774 .expect("mmc data buffer should be sufficiently large");
775 u16::from_be_bytes(val)
776}
777/// Parse a big endian u32 out of the next four bytes.
778fn read_u32(val: &[u8]) -> u32 {
779 let val = *val
780 .first_chunk()
781 .expect("mmc data buffer should be sufficiently large");
782 u32::from_be_bytes(val)
783}
784
785#[cfg(test)]
786mod tests {
787 use super::*;
788
789 #[test_log::test(test)]
790 #[ignore = "requires a disc drive with mmc"]
791 fn features() {
792 Mmc::new().unwrap().features().unwrap();
793 }
794}