scsi_core/
lib.rs

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