scsi_core/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Core SCSI traits and types for the OpenVMM storage stack.
5//!
6//! Defines the [`AsyncScsiDisk`] trait — the interface between SCSI transport
7//! layers (`storvsp`, IDE/ATAPI) and SCSI device implementations
8//! (`SimpleScsiDisk`, `SimpleScsiDvd`). Also defines
9//! [`Request`], [`ScsiResult`], and save/restore types for SCSI devices.
10//!
11//! Implementations must fit within [`ASYNC_SCSI_DISK_STACK_SIZE`] or the
12//! returned future will be heap-allocated via `StackFuture::from_or_box`.
13
14#![forbid(unsafe_code)]
15
16use inspect::Inspect;
17use scsi_buffers::RequestBuffers;
18use scsi_defs::ScsiOp;
19use scsi_defs::ScsiStatus;
20use scsi_defs::srb::SrbStatus;
21use stackfuture::StackFuture;
22use std::sync::Arc;
23use vm_resource::CanResolveTo;
24use vm_resource::kind::ScsiDeviceHandleKind;
25use vmcore::save_restore::RestoreError;
26use vmcore::save_restore::SaveError;
27use vmcore::vm_task::VmTaskDriverSource;
28
29impl CanResolveTo<ResolvedScsiDevice> for ScsiDeviceHandleKind {
30    type Input<'a> = ResolveScsiDeviceHandleParams<'a>;
31}
32
33/// A resolved [`AsyncScsiDisk`].
34pub struct ResolvedScsiDevice(pub Arc<dyn AsyncScsiDisk>);
35
36impl<T: 'static + AsyncScsiDisk> From<T> for ResolvedScsiDevice {
37    fn from(value: T) -> Self {
38        Self(Arc::new(value))
39    }
40}
41
42/// Parameters used when ersolving [`ScsiDeviceHandleKind`].
43pub struct ResolveScsiDeviceHandleParams<'a> {
44    /// The VM task driver source.
45    pub driver_source: &'a VmTaskDriverSource,
46}
47
48/// The amount of space reserved for an AsyncScsiDisk-returned future
49///
50/// This was chosen by running `cargo test --package scsidisk --lib -- --exact --nocapture` and looking at the required
51/// size that was given in the failure message
52pub const ASYNC_SCSI_DISK_STACK_SIZE: usize = 1256 + 336;
53
54/// Trait for issuing SCSI device requests.
55pub trait AsyncScsiDisk: Send + Sync + Inspect + ScsiSaveRestore {
56    /// Executes a SCSI request.
57    fn execute_scsi<'a>(
58        &'a self,
59        external_data: &'a RequestBuffers<'a>,
60        request: &'a Request,
61    ) -> StackFuture<'a, ScsiResult, { ASYNC_SCSI_DISK_STACK_SIZE }>;
62}
63
64/// A SCSI request.
65#[derive(Debug)]
66pub struct Request {
67    /// The SCSI CDB (Command Descriptor Block).
68    pub cdb: [u8; 0x10],
69    /// Additional flags from the SCSI request block.
70    ///
71    /// TODO: interpret these OOB flags in storvsp, not in the SCSI implementations.
72    pub srb_flags: u32,
73}
74
75impl Request {
76    /// Returns the SCSI request operation from the CDB.
77    pub fn scsiop(&self) -> ScsiOp {
78        ScsiOp(self.cdb[0])
79    }
80}
81
82/// The result of a SCSI request.
83#[derive(Debug)]
84pub struct ScsiResult {
85    /// The SCSI status.
86    pub scsi_status: ScsiStatus,
87    /// The SRB status.
88    ///
89    /// TODO: move computation of this to storvsp.
90    pub srb_status: SrbStatus,
91    /// The number of bytes that were transferred.
92    pub tx: usize,
93    /// The sense data for a failed request.
94    pub sense_data: Option<scsi_defs::SenseData>,
95}
96
97/// Trait to save/restore SCSI devices.
98pub trait ScsiSaveRestore {
99    /// Save the device state.
100    fn save(&self) -> Result<Option<save_restore::ScsiSavedState>, SaveError>;
101    /// Restore the device state.
102    fn restore(&self, state: &save_restore::ScsiSavedState) -> Result<(), RestoreError>;
103}
104
105pub mod save_restore {
106    //! SCSI device saved state definitions.
107
108    #![expect(missing_docs)]
109
110    use mesh::payload::Protobuf;
111
112    #[derive(Debug, Copy, Clone, Eq, PartialEq, Protobuf)]
113    #[mesh(package = "storage.scsi.common")]
114    pub struct SavedSenseData {
115        #[mesh(1)]
116        pub sense_key: u8,
117        #[mesh(2)]
118        pub additional_sense_code: u8,
119        #[mesh(3)]
120        pub additional_sense_code_qualifier: u8,
121    }
122
123    #[derive(Debug, Copy, Clone, Protobuf)]
124    #[mesh(package = "storage.scsi.disk")]
125    pub struct ScsiDiskSavedState {
126        #[mesh(1)]
127        pub sector_count: u64,
128        #[mesh(2)]
129        pub sense_data: Option<SavedSenseData>,
130    }
131
132    #[derive(Debug, Protobuf, Copy, Clone)]
133    #[mesh(package = "storage.scsi")]
134    pub enum ScsiSavedState {
135        #[mesh(1)]
136        ScsiDvd(ScsiDvdSavedState),
137        #[mesh(2)]
138        ScsiDisk(ScsiDiskSavedState),
139    }
140
141    #[derive(Debug, Protobuf, Copy, Clone)]
142    #[mesh(package = "storage.scsi.dvd")]
143    pub struct ScsiDvdSavedState {
144        #[mesh(1)]
145        pub sense_data: Option<SavedSenseData>,
146        #[mesh(2)]
147        pub persistent: bool,
148        #[mesh(3)]
149        pub prevent: bool,
150        #[mesh(4)]
151        pub drive_state: DriveState,
152        #[mesh(5)]
153        pub pending_medium_event: IsoMediumEvent,
154    }
155
156    #[derive(Debug, Default, PartialEq, Eq, Copy, Clone, inspect::Inspect, Protobuf)]
157    #[expect(clippy::enum_variant_names)]
158    #[mesh(package = "storage.scsi.dvd")]
159    pub enum DriveState {
160        #[mesh(1)]
161        MediumPresentTrayOpen,
162        #[mesh(2)]
163        MediumPresentTrayClosed,
164        #[mesh(3)]
165        MediumNotPresentTrayOpen,
166        #[default]
167        #[mesh(4)]
168        MediumNotPresentTrayClosed,
169    }
170
171    impl DriveState {
172        pub fn tray_open(&self) -> bool {
173            *self == DriveState::MediumPresentTrayOpen
174                || *self == DriveState::MediumNotPresentTrayOpen
175        }
176
177        pub fn medium_present(&self) -> bool {
178            *self == DriveState::MediumPresentTrayOpen
179                || *self == DriveState::MediumPresentTrayClosed
180        }
181    }
182
183    #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Protobuf)]
184    #[mesh(package = "storage.scsi.dvd")]
185    pub enum IsoMediumEvent {
186        #[default]
187        #[mesh(1)]
188        None = 0x00,
189        #[mesh(2)]
190        NoMediaToMedia = 0x01,
191        #[mesh(3)]
192        MediaToNoMedia = 0x02,
193        #[mesh(4)]
194        MediaToMedia = 0x03,
195    }
196}