Skip to main content

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    /// Drops the state that belongs to the initiator, called when the I_T nexus
64    /// is lost.
65    ///
66    /// SAM defines several pieces of device state as scoped to the nexus rather
67    /// than to the device: pending sense data, unit attention conditions, and
68    /// the PREVENT ALLOW MEDIUM REMOVAL setting. When the initiator goes away,
69    /// so should they, or the next initiator inherits decisions it never made.
70    ///
71    /// State describing the DEVICE rather than the nexus does not belong here.
72    /// An ejected tray stays ejected and a persistent reservation survives,
73    /// which is what real hardware does across a bus reset.
74    ///
75    /// The default does nothing, for a device holding no nexus-scoped state.
76    fn clear_nexus_state(&self) {}
77}
78
79/// A SCSI request.
80#[derive(Debug)]
81pub struct Request {
82    /// The SCSI CDB (Command Descriptor Block).
83    pub cdb: [u8; 0x10],
84    /// Additional flags from the SCSI request block.
85    ///
86    /// TODO: interpret these OOB flags in storvsp, not in the SCSI implementations.
87    pub srb_flags: u32,
88}
89
90impl Request {
91    /// Returns the SCSI request operation from the CDB.
92    pub fn scsiop(&self) -> ScsiOp {
93        ScsiOp(self.cdb[0])
94    }
95}
96
97/// The result of a SCSI request.
98#[derive(Debug)]
99pub struct ScsiResult {
100    /// The SCSI status.
101    pub scsi_status: ScsiStatus,
102    /// The SRB status.
103    ///
104    /// TODO: move computation of this to storvsp.
105    pub srb_status: SrbStatus,
106    /// The number of bytes that were transferred.
107    pub tx: usize,
108    /// The sense data for a failed request.
109    pub sense_data: Option<scsi_defs::SenseData>,
110}
111
112/// Trait to save/restore SCSI devices.
113pub trait ScsiSaveRestore {
114    /// Save the device state.
115    fn save(&self) -> Result<Option<save_restore::ScsiSavedState>, SaveError>;
116    /// Restore the device state.
117    fn restore(&self, state: &save_restore::ScsiSavedState) -> Result<(), RestoreError>;
118}
119
120pub mod save_restore {
121    //! SCSI device saved state definitions.
122
123    #![expect(missing_docs)]
124
125    use mesh::payload::Protobuf;
126
127    #[derive(Debug, Copy, Clone, Eq, PartialEq, Protobuf)]
128    #[mesh(package = "storage.scsi.common")]
129    pub struct SavedSenseData {
130        #[mesh(1)]
131        pub sense_key: u8,
132        #[mesh(2)]
133        pub additional_sense_code: u8,
134        #[mesh(3)]
135        pub additional_sense_code_qualifier: u8,
136    }
137
138    #[derive(Debug, Copy, Clone, Protobuf)]
139    #[mesh(package = "storage.scsi.disk")]
140    pub struct ScsiDiskSavedState {
141        #[mesh(1)]
142        pub sector_count: u64,
143        #[mesh(2)]
144        pub sense_data: Option<SavedSenseData>,
145    }
146
147    #[derive(Debug, Protobuf, Copy, Clone)]
148    #[mesh(package = "storage.scsi")]
149    pub enum ScsiSavedState {
150        #[mesh(1)]
151        ScsiDvd(ScsiDvdSavedState),
152        #[mesh(2)]
153        ScsiDisk(ScsiDiskSavedState),
154    }
155
156    #[derive(Debug, Protobuf, Copy, Clone)]
157    #[mesh(package = "storage.scsi.dvd")]
158    pub struct ScsiDvdSavedState {
159        #[mesh(1)]
160        pub sense_data: Option<SavedSenseData>,
161        #[mesh(2)]
162        pub persistent: bool,
163        #[mesh(3)]
164        pub prevent: bool,
165        #[mesh(4)]
166        pub drive_state: DriveState,
167        #[mesh(5)]
168        pub pending_medium_event: IsoMediumEvent,
169    }
170
171    #[derive(Debug, Default, PartialEq, Eq, Copy, Clone, inspect::Inspect, Protobuf)]
172    #[expect(clippy::enum_variant_names)]
173    #[mesh(package = "storage.scsi.dvd")]
174    pub enum DriveState {
175        #[mesh(1)]
176        MediumPresentTrayOpen,
177        #[mesh(2)]
178        MediumPresentTrayClosed,
179        #[mesh(3)]
180        MediumNotPresentTrayOpen,
181        #[default]
182        #[mesh(4)]
183        MediumNotPresentTrayClosed,
184    }
185
186    impl DriveState {
187        pub fn tray_open(&self) -> bool {
188            *self == DriveState::MediumPresentTrayOpen
189                || *self == DriveState::MediumNotPresentTrayOpen
190        }
191
192        pub fn medium_present(&self) -> bool {
193            *self == DriveState::MediumPresentTrayOpen
194                || *self == DriveState::MediumPresentTrayClosed
195        }
196    }
197
198    #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Protobuf)]
199    #[mesh(package = "storage.scsi.dvd")]
200    pub enum IsoMediumEvent {
201        #[default]
202        #[mesh(1)]
203        None = 0x00,
204        #[mesh(2)]
205        NoMediaToMedia = 0x01,
206        #[mesh(3)]
207        MediaToNoMedia = 0x02,
208        #[mesh(4)]
209        MediaToMedia = 0x03,
210    }
211}