Skip to main content

libcdio_rs/mmc/
start_stop_unit.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 `START STOP UNIT`.
19
20use displaydoc::Display;
21use thiserror::Error;
22
23use crate::{
24    Mmc,
25    mmc::{Cdb, MmcCommand, MmcDirection, MmcError},
26};
27
28/// Routines based on MMC `START STOP UNIT`.
29impl Mmc {
30    /// Eject the media, if permitted.
31    ///
32    /// This might require a prior allow eject operation.
33    pub fn eject(&self) -> Result<(), MmcEjectError> {
34        self.start_stop_unit(StartStopOperation::EjectDisc)?;
35        Ok(())
36    }
37
38    /// Close the tray.
39    pub fn close_tray(&self) -> Result<(), MmcCloseTrayError> {
40        self.start_stop_unit(StartStopOperation::LoadStartDisc)?;
41        Ok(())
42    }
43
44    /// Set power state.
45    pub fn set_power_state(&self, state: PowerCondition) -> Result<(), MmcSetPowerStateError> {
46        self.start_stop_unit(StartStopOperation::Power(state))?;
47        Ok(())
48    }
49
50    fn start_stop_unit(&self, operation: StartStopOperation) -> Result<(), MmcStartStopError> {
51        let mut cdb = Cdb::default();
52
53        cdb[0] = MmcCommand::StartStopUnit as u8;
54        cdb[1] = 0; // not using the immediate bit for now
55        if let StartStopOperation::Jump { layer_number } = operation {
56            cdb[3] = layer_number & LAYER_NUM_BITMASK;
57            cdb[4] |= 1 << FORMAT_LAYER_BITPOS;
58        }
59        // Sets the LoEj and Start fields
60        // as described in 6.42.3.1 of MMC-6 2g
61        cdb[4] |= match operation {
62            StartStopOperation::StartDisc => 0b01,
63            StartStopOperation::EjectDisc => 0b10,
64            StartStopOperation::LoadStartDisc | StartStopOperation::Jump { .. } => 0b11,
65            _ => 0b00,
66        };
67        if let StartStopOperation::Power(pow_cond) = operation {
68            cdb[4] |= (pow_cond as u8 & POWER_COND_BITMASK) << POWER_COND_BITPOS;
69        }
70
71        self.run_command(Some(MmcDirection::Write), &mut [], cdb)?;
72
73        return Ok(());
74
75        const LAYER_NUM_BITMASK: u8 = 0b11;
76        const FORMAT_LAYER_BITPOS: usize = 2;
77        const POWER_COND_BITMASK: u8 = 0b1111;
78        const POWER_COND_BITPOS: usize = 4;
79    }
80}
81
82/// could not eject MMC device
83#[derive(Debug, Display, Error)]
84pub struct MmcEjectError {
85    #[from]
86    pub source: MmcStartStopError,
87}
88
89/// could not close tray of the MMC device
90#[derive(Debug, Display, Error)]
91pub struct MmcCloseTrayError {
92    #[from]
93    pub source: MmcStartStopError,
94}
95
96/// could not set power state of MMC device
97#[derive(Debug, Display, Error)]
98pub struct MmcSetPowerStateError {
99    #[from]
100    pub source: MmcStartStopError,
101}
102
103/// error from a `START STOP UNIT` command
104#[derive(Debug, Display, Error)]
105pub struct MmcStartStopError {
106    #[from]
107    pub source: MmcError,
108}
109
110/// Operations of the `START STOP UNIT` command
111#[allow(unused)]
112enum StartStopOperation {
113    StopDisc,
114    StartDisc,
115    EjectDisc,
116    LoadStartDisc,
117
118    /// Change the online format-layer to the specified value for hybrid discs.
119    /// Only the last two bits will be set.
120    Jump {
121        layer_number: u8,
122    },
123
124    /// Place the device in the specified power condition
125    Power(PowerCondition),
126}
127
128/// A power state as defined under MMC `START STOP UNIT`
129#[allow(unused)]
130pub enum PowerCondition {
131    Idle = 0x2,
132    Standby = 0x3,
133    Sleep = 0x5,
134}
135
136// TODO: add manual tests