diff --git a/libcdio-rs/src/mmc.rs b/libcdio-rs/src/mmc.rs index 5c9779a..0d056f6 100644 --- a/libcdio-rs/src/mmc.rs +++ b/libcdio-rs/src/mmc.rs @@ -25,6 +25,7 @@ use std::{ pub use get_config::*; pub use get_event_status::*; +pub use inquiry::*; pub use read_disc_info::*; pub use read_subchannel::*; pub use test_unit_ready::*; @@ -32,6 +33,7 @@ pub use read_toc::*; mod get_config; mod get_event_status; +mod inquiry; mod read_disc_info; mod read_subchannel; mod test_unit_ready; @@ -365,6 +367,7 @@ pub enum OsError { enum MmcCommand { #[allow(unused)] GetConfiguration = 0x46, + Inquiry = 0x12, TestUnitReady = 0x00, ReadDiscInfo = 0x51, ReadToc = 0x43, diff --git a/libcdio-rs/src/mmc/inquiry.rs b/libcdio-rs/src/mmc/inquiry.rs new file mode 100644 index 0000000..9a05afb --- /dev/null +++ b/libcdio-rs/src/mmc/inquiry.rs @@ -0,0 +1,132 @@ +// Copyright (C) 2026 Shiva Kiran Koninty +// +// This file is part of libcdio-rs. +// +// libcdio-rs is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// libcdio-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with libcdio-rs. If not, see . + +//! Routines based on MMC `INQUIRY`. + +use displaydoc::Display; +use thiserror::Error; +use tracing::debug; + +use crate::{ + Mmc, + mmc::{Cdb, MmcCommand, MmcDirection, MmcError}, +}; + +/// Routines based on MMC `INQUIRY`. +impl Mmc { + /// Get the hardware identifiers (Product, Vendor and Revision). + pub fn hardware_identifiers(&self) -> Result { + let buf = self.inquiry(InquiryKind::Standard)?; + let response_data_format = buf[RESPONSE_DATA_FMT_POS] & 0b1111; + if response_data_format != RESPONSE_DATA_FMT_MMC { + return Err(MmcInquiryError::InvalidResponse(format!( + "unsupported non-standard INQUIRY response format, got response data format of {}", + response_data_format, + )) + .into()); + } + let vendor = String::from_utf8_lossy(&buf[VENDOR_ID_START_POS..=VENDOR_ID_END_POS]) + .trim() + .to_string(); + let product = String::from_utf8_lossy(&buf[PRODUCT_ID_START_POS..=PRODUCT_ID_END_POS]) + .trim() + .to_string(); + let revision = String::from_utf8_lossy(&buf[REVISION_START_POS..=REVISION_END_POS]) + .trim() + .to_string(); + + return Ok(HardwareIdentifiers { + product, + vendor, + revision, + }); + + const RESPONSE_DATA_FMT_POS: usize = 3; + const RESPONSE_DATA_FMT_MMC: u8 = 2; + const VENDOR_ID_START_POS: usize = 8; + const VENDOR_ID_END_POS: usize = 15; + const PRODUCT_ID_START_POS: usize = 16; + const PRODUCT_ID_END_POS: usize = 31; + const REVISION_START_POS: usize = 32; + const REVISION_END_POS: usize = 35; + } + + fn inquiry(&self, kind: InquiryKind) -> Result, MmcInquiryError> { + let mut buf = vec![0; ALLOC_LEN]; + let mut cdb = Cdb::default(); + cdb[0] = MmcCommand::Inquiry as u8; + if let InquiryKind::VitalProductData { page_code } = kind { + cdb[1] = EVPD_SET; + cdb[2] = page_code; + } + cdb[3..=4].copy_from_slice(&(buf.len() as u16).to_be_bytes()); + + self.run_command(Some(MmcDirection::Read), buf.as_mut_slice(), cdb)?; + debug!(?buf, len = buf.len()); + + return Ok(buf); + + const ALLOC_LEN: usize = 255; + const EVPD_SET: u8 = 1; + } +} + +/// could not get hardware identifers +#[derive(Debug, Display, Error)] +pub struct GetHardwareIdentifersError { + #[from] + pub source: MmcInquiryError, +} + +/// error from an `INQUIRY` command. +#[derive(Debug, Display, Error)] +pub enum MmcInquiryError { + /// operating system returned an error + Os(#[from] MmcError), + + /// invalid response from mmc command: {0} + InvalidResponse(String), +} + +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +/// Basic hardware identifiers +pub struct HardwareIdentifiers { + pub product: String, + pub vendor: String, + pub revision: String, +} + +#[allow(unused)] +#[derive(Clone, Copy, Debug)] +enum InquiryKind { + Standard, + VitalProductData { page_code: u8 }, +} + +#[cfg(test)] +mod tests { + use tracing::info; + + use super::*; + + #[test_log::test(test)] + #[ignore = "requires a drive with mmc"] + fn hardware_identifers() { + let hardware_identifiers = Mmc::new().unwrap().hardware_identifiers().unwrap(); + info!(?hardware_identifiers); + } +}