Skip to main content

libcdio_rs/mmc/
inquiry.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//! Routines based on MMC `INQUIRY`.
19//! This command is described in SPC-3.
20
21use displaydoc::Display;
22use thiserror::Error;
23use tracing::debug;
24
25use crate::{
26    Mmc,
27    mmc::{Cdb, MmcCommand, MmcDirection, MmcError},
28};
29
30/// Routines based on MMC `INQUIRY`.
31impl Mmc {
32    /// Get the hardware identifiers (Product, Vendor and Revision).
33    pub fn hardware_identifiers(&self) -> Result<HardwareIdentifiers, GetHardwareIdentifiersError> {
34        let buf = self.inquiry(InquiryKind::Standard)?;
35        let response_data_format = buf[RESPONSE_DATA_FMT_POS] & 0b1111;
36        if response_data_format != RESPONSE_DATA_FMT_MMC {
37            return Err(MmcInquiryError::InvalidResponse(format!(
38                "unsupported non-standard INQUIRY response format, got response data format of {}",
39                response_data_format,
40            ))
41            .into());
42        }
43        let vendor = String::from_utf8_lossy(&buf[VENDOR_ID_START_POS..=VENDOR_ID_END_POS])
44            .trim()
45            .to_string();
46        let product = String::from_utf8_lossy(&buf[PRODUCT_ID_START_POS..=PRODUCT_ID_END_POS])
47            .trim()
48            .to_string();
49        let revision = String::from_utf8_lossy(&buf[REVISION_START_POS..=REVISION_END_POS])
50            .trim()
51            .to_string();
52
53        return Ok(HardwareIdentifiers {
54            product,
55            vendor,
56            revision,
57        });
58
59        const RESPONSE_DATA_FMT_POS: usize = 3;
60        const RESPONSE_DATA_FMT_MMC: u8 = 2;
61        const VENDOR_ID_START_POS: usize = 8;
62        const VENDOR_ID_END_POS: usize = 15;
63        const PRODUCT_ID_START_POS: usize = 16;
64        const PRODUCT_ID_END_POS: usize = 31;
65        const REVISION_START_POS: usize = 32;
66        const REVISION_END_POS: usize = 35;
67    }
68
69    fn inquiry(&self, kind: InquiryKind) -> Result<Vec<u8>, MmcInquiryError> {
70        let mut buf = vec![0; ALLOC_LEN];
71        let mut cdb = Cdb::default();
72        cdb[0] = MmcCommand::Inquiry as u8;
73        if let InquiryKind::VitalProductData { page_code } = kind {
74            cdb[1] = EVPD_SET;
75            cdb[2] = page_code;
76        }
77        cdb[3..=4].copy_from_slice(&(buf.len() as u16).to_be_bytes());
78
79        self.run_command(Some(MmcDirection::Read), buf.as_mut_slice(), cdb)?;
80        debug!(?buf, len = buf.len());
81
82        return Ok(buf);
83
84        const ALLOC_LEN: usize = 255;
85        const EVPD_SET: u8 = 1;
86    }
87
88    /// Does the device support MMC.
89    ///
90    /// Per SCSI, a device that responds to an SPC `INQUIRY` with a
91    /// `Peripheral device type` of `CD/DVD device` is expected
92    /// to implement MMC.
93    pub(crate) fn is_mmc_device(&self) -> Result<bool, MmcInquiryError> {
94        let response = self.inquiry(InquiryKind::Standard)?;
95        let peripheral_device_type = response[0] & PERIPHERAL_DEVICE_TYPE_BITMASK;
96        return Ok(peripheral_device_type == PERIPHERAL_DEVICE_TYPE_CD_DVD);
97
98        const PERIPHERAL_DEVICE_TYPE_BITMASK: u8 = 0b11111;
99        const PERIPHERAL_DEVICE_TYPE_CD_DVD: u8 = 0x05;
100    }
101}
102
103/// could not get hardware identifiers
104#[derive(Debug, Display, Error)]
105pub struct GetHardwareIdentifiersError {
106    #[from]
107    pub source: MmcInquiryError,
108}
109
110/// error from an `INQUIRY` command.
111#[derive(Debug, Display, Error)]
112pub enum MmcInquiryError {
113    /// operating system returned an error
114    Os(#[from] MmcError),
115
116    /// invalid response from mmc command: {0}
117    InvalidResponse(String),
118}
119
120#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
121/// Basic hardware identifiers
122pub struct HardwareIdentifiers {
123    pub product: String,
124    pub vendor: String,
125    pub revision: String,
126}
127
128#[allow(unused)]
129#[derive(Clone, Copy, Debug)]
130enum InquiryKind {
131    Standard,
132    VitalProductData { page_code: u8 },
133}
134
135#[cfg(test)]
136mod tests {
137    use tracing::info;
138
139    use super::*;
140
141    #[test_log::test(test)]
142    #[ignore = "requires a drive with mmc"]
143    fn hardware_identifiers() {
144        let hardware_identifiers = Mmc::new().unwrap().hardware_identifiers().unwrap();
145        info!(?hardware_identifiers);
146    }
147}