Skip to main content

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}