scsidisk/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! SCSI CDB parser and disk/DVD emulation.
5//!
6//! This crate translates SCSI commands (CDBs) into [`DiskIo`](disk_backend::DiskIo)
7//! calls. It's used by `storvsp` for hard drives and by `ide` (via ATAPI)
8//! for optical drives. It doesn't implement the SCSI transport — that's the
9//! frontend's job.
10//!
11//! # Key types
12//!
13//! - [`SimpleScsiDisk`] — hard drive emulation. Implements
14//!   [`AsyncScsiDisk`], holds a
15//!   [`Disk`], and parses SCSI CDB opcodes. Handles
16//!   READ/WRITE (6/10/12/16), READ_CAPACITY, INQUIRY, MODE_SENSE, UNMAP,
17//!   WRITE_SAME, SYNCHRONIZE_CACHE, and PERSISTENT_RESERVE.
18//! - [`SimpleScsiDvd`](scsidvd::SimpleScsiDvd) — optical drive emulation.
19//!   Manages media state (`Loaded` / `Unloaded`), handles MMC optical commands
20//!   (GET_EVENT_STATUS, GET_CONFIGURATION, READ_TOC, START_STOP_UNIT for eject).
21//!
22//! # Capacity change detection
23//!
24//! On every SCSI command, `SimpleScsiDisk` checks the current sector count
25//! against the last-known value. If the disk resized, it returns
26//! UNIT_ATTENTION with CAPACITY_DATA_CHANGED. The guest retries and re-reads
27//! capacity.
28
29#![expect(missing_docs)]
30#![forbid(unsafe_code)]
31
32pub mod atapi_scsi;
33mod getlbastatus;
34mod inquiry;
35mod reservation;
36pub mod resolver;
37pub mod scsidvd;
38mod unmap;
39
40#[cfg(test)]
41mod tests;
42
43pub use inquiry::INQUIRY_DATA_TEMPLATE;
44
45use disk_backend::Disk;
46use disk_backend::DiskError;
47use disk_backend::UnmapBehavior;
48use guestmem::AccessError;
49use guestmem::MemoryRead;
50use guestmem::MemoryWrite;
51use guid::Guid;
52use inspect::Inspect;
53use parking_lot::Mutex;
54use scsi::AdditionalSenseCode;
55use scsi::ScsiOp;
56use scsi::ScsiStatus;
57use scsi::SenseKey;
58use scsi::srb::SrbStatus;
59use scsi_buffers::RequestBuffers;
60use scsi_core::ASYNC_SCSI_DISK_STACK_SIZE;
61use scsi_core::AsyncScsiDisk;
62use scsi_core::Request;
63use scsi_core::ScsiResult;
64use scsi_core::ScsiSaveRestore;
65use scsi_core::save_restore::SavedSenseData;
66use scsi_core::save_restore::ScsiDiskSavedState;
67use scsi_core::save_restore::ScsiSavedState;
68use scsi_defs as scsi;
69use scsidisk_resources::DiskIdentity;
70use scsidisk_resources::DiskParameters;
71use stackfuture::StackFuture;
72use std::fmt::Debug;
73use std::sync::atomic::AtomicBool;
74use std::sync::atomic::AtomicU64;
75use std::sync::atomic::Ordering;
76use thiserror::Error;
77use tracing::Instrument;
78use tracing_helpers::ErrorValueExt;
79use unmap::validate_lba_range;
80use vmcore::save_restore::RestoreError;
81use vmcore::save_restore::SaveError;
82use zerocopy::FromBytes;
83use zerocopy::FromZeros;
84use zerocopy::IntoBytes;
85
86const UNMAP_RANGE_DESCRIPTOR_COUNT_MAX: u16 = 4096;
87const VHDMP_MAX_WRITE_SAME_LENGTH_BYTES: u64 = 8 * 1024 * 1024; // bytes
88
89impl ScsiSaveRestore for SimpleScsiDisk {
90    fn save(&self) -> Result<Option<ScsiSavedState>, SaveError> {
91        let sense = self.sense_data.get();
92        let sense_data = sense.map(|sense| SavedSenseData {
93            sense_key: sense.header.sense_key.0,
94            additional_sense_code: sense.additional_sense_code.0,
95            additional_sense_code_qualifier: sense.additional_sense_code_qualifier,
96        });
97        Ok(Some(ScsiSavedState::ScsiDisk(ScsiDiskSavedState {
98            sector_count: self.last_sector_count.load(Ordering::Relaxed),
99            sense_data,
100        })))
101    }
102
103    fn restore(&self, state: &ScsiSavedState) -> Result<(), RestoreError> {
104        if let ScsiSavedState::ScsiDisk(disk_state) = state {
105            let ScsiDiskSavedState {
106                sector_count,
107                sense_data,
108            } = *disk_state;
109
110            // restore sense data
111            self.sense_data.set(
112                sense_data
113                    .map(|sense| {
114                        scsi::SenseData::new(
115                            SenseKey(sense.sense_key),
116                            AdditionalSenseCode(sense.additional_sense_code),
117                            sense.additional_sense_code_qualifier,
118                        )
119                    })
120                    .as_ref(),
121            );
122
123            self.last_sector_count
124                .store(sector_count, Ordering::Relaxed);
125            Ok(())
126        } else {
127            Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
128                "saved state didn't match expected format ScsiDiskSavedState"
129            )))
130        }
131    }
132}
133
134pub struct SimpleScsiDisk {
135    disk: Disk,
136    sector_shift: u8,
137    physical_extra_shift: u8,
138    sector_size: u32,
139    sense_data: SenseDataSlot,
140    scsi_parameters: ScsiParameters,
141    support_pr: bool,
142    last_sector_count: AtomicU64,
143}
144
145#[derive(Debug, Clone, Inspect)]
146struct ScsiParameters {
147    disk_id: [u8; 16],
148    physical_sector_size: u32,
149    support_fua: bool,
150    write_cache_enabled: bool,
151    support_odx: bool,
152    support_unmap: bool,
153    support_get_lba_status: bool,
154    maximum_transfer_length: usize,
155    identity: DiskIdentity,
156    serial_number: Vec<u8>,
157    medium_rotation_rate: u16,
158    optimal_unmap_sectors: u32,
159}
160
161impl SimpleScsiDisk {
162    pub fn new(disk: Disk, disk_parameters: DiskParameters) -> Self {
163        let sector_size = disk.sector_size();
164        let sector_shift = sector_size.trailing_zeros() as u8;
165        let mut sector_count = disk.sector_count();
166
167        // Update the reported disk size.
168        if let Some(size) = disk_parameters.scsi_disk_size_in_bytes {
169            sector_count = sector_count.min(size >> sector_shift);
170        }
171
172        // Determine the SCSI parameters from the passed-in disk parameters and
173        // the information from the underlying disk.
174        let scsi_parameters = {
175            let DiskParameters {
176                disk_id,
177                identity,
178                serial_number,
179                medium_rotation_rate,
180                physical_sector_size,
181                fua,
182                write_cache,
183                scsi_disk_size_in_bytes: _,
184                odx,
185                unmap,
186                max_transfer_length,
187                optimal_unmap_sectors,
188                get_lba_status,
189            } = disk_parameters;
190
191            fn nonzero_id(id: [u8; 16]) -> Option<[u8; 16]> {
192                if id == [0; 16] { None } else { Some(id) }
193            }
194
195            // Choose the first non-zero disk ID from the passed in parameters,
196            // the disk, or a new random ID.
197            let disk_id = disk_id
198                .and_then(nonzero_id)
199                .or_else(|| disk.disk_id().and_then(nonzero_id))
200                .unwrap_or_else(|| Guid::new_random().into());
201
202            ScsiParameters {
203                disk_id,
204                physical_sector_size: physical_sector_size
205                    .unwrap_or_else(|| disk.physical_sector_size()),
206                support_fua: fua.unwrap_or_else(|| disk.is_fua_respected()),
207                write_cache_enabled: write_cache.unwrap_or(true),
208                support_odx: odx.unwrap_or(false),
209                support_get_lba_status: get_lba_status,
210                support_unmap: unmap.unwrap_or(disk.unmap_behavior() != UnmapBehavior::Ignored),
211                maximum_transfer_length: max_transfer_length.unwrap_or(8 * 1024 * 1024),
212                identity: identity.unwrap_or_else(DiskIdentity::msft),
213                serial_number,
214                medium_rotation_rate: medium_rotation_rate.unwrap_or(1), // non-rotating media (SSD)
215                optimal_unmap_sectors: optimal_unmap_sectors.unwrap_or(1),
216            }
217        };
218
219        let physical_extra_shift =
220            scsi_parameters.physical_sector_size.trailing_zeros() as u8 - sector_shift;
221        let support_pr = disk.pr().is_some();
222
223        SimpleScsiDisk {
224            disk,
225            sector_shift,
226            physical_extra_shift,
227            sector_size,
228            sense_data: Default::default(),
229            scsi_parameters,
230            support_pr,
231            last_sector_count: AtomicU64::new(sector_count),
232        }
233    }
234}
235
236#[derive(Error, Debug)]
237enum ScsiError {
238    #[error("memory access error")]
239    MemoryAccess(#[source] AccessError),
240    #[error("illegal request, asc: {0:?}")]
241    IllegalRequest(AdditionalSenseCode),
242    #[error("data overrun")]
243    DataOverrun,
244    #[error("srb generic error")]
245    SrbError,
246    #[error("device is write protected")]
247    WriteProtected,
248    #[error("disk io error")]
249    Disk(#[source] DiskError),
250    #[error("pending unit attention")]
251    UnitAttention,
252    #[error("unsupported mode page code: page control {0} page code {1}")]
253    UnsupportedModePageCode(u8, u8),
254    #[error("unsupported vpd page code: {0}")]
255    UnsupportedVpdPageCode(u8),
256    #[error("unsupported service action: {0}")]
257    UnsupportedServiceAction(u8),
258}
259
260struct RequestParameters {
261    tx: usize,
262    offset: u64,
263    fua: bool,
264}
265
266struct WriteSameParameters {
267    lba_count: usize,
268    start_lba: u64,
269    fua: bool,
270    sector_size: usize,
271    tx: usize,
272}
273
274const MODE_CACHING_PAGE_SIZE: usize = size_of::<scsi::ModeCachingPage>();
275const MODE_PARAMETER_HEADER_SIZE: usize = size_of::<scsi::ModeParameterHeader>();
276const MODE_PARAMETER_HEADER10_SIZE: usize = size_of::<scsi::ModeParameterHeader10>();
277const MODE_DATA_LENGTH10: u16 = (MODE_PARAMETER_HEADER10_SIZE + MODE_CACHING_PAGE_SIZE - 2) as u16;
278const MODE_DATA_LENGTH: u8 = (MODE_PARAMETER_HEADER_SIZE + MODE_CACHING_PAGE_SIZE - 1) as u8;
279
280pub fn illegal_request_sense(sense_code: AdditionalSenseCode) -> scsi::SenseData {
281    match sense_code {
282        AdditionalSenseCode::ILLEGAL_COMMAND
283        | AdditionalSenseCode::INVALID_CDB
284        | AdditionalSenseCode::NO_SENSE
285        | AdditionalSenseCode::INVALID_FIELD_PARAMETER_LIST
286        | AdditionalSenseCode::PARAMETER_LIST_LENGTH
287        | AdditionalSenseCode::ILLEGAL_BLOCK => {
288            scsi::SenseData::new(SenseKey::ILLEGAL_REQUEST, sense_code, 0)
289        }
290        _ => unreachable!(),
291    }
292}
293
294impl SimpleScsiDisk {
295    fn handle_request_sense(
296        &self,
297        external_data: &RequestBuffers<'_>,
298        request: &Request,
299        unit_attention: bool,
300    ) -> Result<usize, ScsiError> {
301        let cdb = scsi::CdbInquiry::read_from_prefix(&request.cdb[..])
302            .unwrap()
303            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
304        let allocation_length = cdb.allocation_length.get() as usize;
305
306        let min = size_of::<scsi::SenseDataHeader>();
307        if allocation_length < min || allocation_length > external_data.len() {
308            tracelimit::error_ratelimited!(
309                allocation_length,
310                min,
311                external_data_len = external_data.len(),
312                "srb error"
313            );
314            return Err(ScsiError::SrbError);
315        }
316
317        let sense = if unit_attention {
318            scsi::SenseData::new(
319                SenseKey::UNIT_ATTENTION,
320                AdditionalSenseCode::PARAMETERS_CHANGED,
321                scsi::SCSI_SENSEQ_CAPACITY_DATA_CHANGED,
322            )
323        } else {
324            self.sense_data.take().unwrap_or_else(|| {
325                scsi::SenseData::new(SenseKey::NO_SENSE, AdditionalSenseCode::NO_SENSE, 0x00)
326            })
327        };
328
329        let tx = std::cmp::min(allocation_length, size_of::<scsi::SenseData>());
330        external_data
331            .writer()
332            .write(&sense.as_bytes()[..tx])
333            .map_err(ScsiError::MemoryAccess)?;
334
335        Ok(tx)
336    }
337
338    fn handle_mode_select(
339        &self,
340        external_data: &RequestBuffers<'_>,
341        request: &Request,
342    ) -> Result<usize, ScsiError> {
343        let is_mode_select_10 = request.scsiop() == ScsiOp::MODE_SELECT10;
344        let request_length;
345        let header_size;
346        let is_spbit_set;
347        if is_mode_select_10 {
348            let cdb = scsi::ModeSelect10::read_from_prefix(&request.cdb[..])
349                .unwrap()
350                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
351            request_length = cdb.parameter_list_length.get() as usize;
352            header_size = MODE_PARAMETER_HEADER10_SIZE;
353            is_spbit_set = cdb.flags.spbit();
354        } else {
355            let cdb = scsi::ModeSelect::read_from_prefix(&request.cdb[..])
356                .unwrap()
357                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
358            request_length = cdb.parameter_list_length as usize;
359            header_size = MODE_PARAMETER_HEADER_SIZE;
360            is_spbit_set = cdb.flags.spbit();
361        }
362
363        if request_length == 0 {
364            return Ok(0);
365        }
366
367        // Validate buffer size
368        let min = header_size + MODE_CACHING_PAGE_SIZE;
369        if request_length != external_data.len() || request_length < min {
370            tracelimit::error_ratelimited!(
371                request_length,
372                external_data = external_data.len(),
373                min,
374                "invalid parameter list length"
375            );
376            return Err(ScsiError::IllegalRequest(
377                AdditionalSenseCode::PARAMETER_LIST_LENGTH,
378            ));
379        }
380
381        // Don't support saving pages.
382        if is_spbit_set {
383            tracing::debug!("doesn't support saving pages");
384            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
385        }
386
387        let mut buffer: Vec<u8> = vec![0; request_length];
388        external_data
389            .reader()
390            .read(&mut buffer)
391            .map_err(ScsiError::MemoryAccess)?;
392
393        let block_descriptor_length = if is_mode_select_10 {
394            let temp10 = scsi::ModeParameterHeader10::read_from_prefix(
395                &buffer[..MODE_PARAMETER_HEADER10_SIZE],
396            )
397            .unwrap()
398            .0; // TODO: zerocopy: from-prefix (read_from_prefix): use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
399            usize::from(temp10.block_descriptor_length)
400        } else {
401            let temp =
402                scsi::ModeParameterHeader::read_from_prefix(&buffer[..MODE_PARAMETER_HEADER_SIZE])
403                    .unwrap()
404                    .0; // TODO: zerocopy: from-prefix (read_from_prefix): use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
405            temp.block_descriptor_length as usize
406        };
407
408        // Skip block descriptor.
409        let skipped = header_size + block_descriptor_length;
410        let min = skipped + MODE_CACHING_PAGE_SIZE;
411        if request_length < min {
412            tracelimit::error_ratelimited!(request_length, min, "invalid parameter list length");
413            return Err(ScsiError::IllegalRequest(
414                AdditionalSenseCode::PARAMETER_LIST_LENGTH,
415            ));
416        }
417
418        // Parse ModeCachingPage.
419        let page = scsi::ModeCachingPage::read_from_prefix(
420            &buffer[skipped..skipped + MODE_CACHING_PAGE_SIZE],
421        )
422        .unwrap()
423        .0; // TODO: zerocopy: from-prefix (read_from_prefix): use-rest-of-range, zerocopy: err (https://github.com/microsoft/openvmm/issues/759)
424        if page.page_code != scsi::MODE_PAGE_CACHING
425            || (page.page_length as usize) < MODE_CACHING_PAGE_SIZE
426            || ((page.flags & scsi::MODE_CACHING_WRITE_CACHE_ENABLE == 0)
427                && self.scsi_parameters.write_cache_enabled)
428            || ((page.flags & scsi::MODE_CACHING_WRITE_CACHE_ENABLE != 0)
429                && !self.scsi_parameters.write_cache_enabled)
430        {
431            // Attempts to turn off write caching must be failed, otherwise
432            // storage migration might lead to the initiator believing that it
433            // has write caching turned off when in fact write caching is
434            // turned on, which would be a potential data loss situation.
435            //
436            // Hopefully no initiator will get too annoyed when this fails.
437            // The only other option would be to erroneously report success here
438            // and then still report that write caching is off next time it's
439            // queried, but that still leaves the initiator potentially out of
440            // sync on the fact that write caching is potentially on and there's
441            // nothing we can do about it.  So hopefully reporting failure here
442            // works for all relevant initiators.
443            tracing::debug!(
444                page_code = page.page_code,
445                page_length = page.page_length,
446                flags = page.flags,
447                write_cache_enabled = self.scsi_parameters.write_cache_enabled,
448                "invalid parameter list"
449            );
450            return Err(ScsiError::IllegalRequest(
451                AdditionalSenseCode::INVALID_FIELD_PARAMETER_LIST,
452            ));
453        }
454
455        Ok(request_length)
456    }
457
458    fn handle_mode_sense(
459        &self,
460        external_data: &RequestBuffers<'_>,
461        request: &Request,
462    ) -> Result<usize, ScsiError> {
463        if external_data.is_empty() {
464            return Ok(0);
465        }
466
467        let is_mode_sense_10 = request.scsiop() == ScsiOp::MODE_SENSE10;
468        let page_code;
469        let page_control;
470        let allocation_length;
471        let header_size;
472        if is_mode_sense_10 {
473            let cdb = scsi::ModeSense10::read_from_prefix(&request.cdb[..])
474                .unwrap()
475                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
476            allocation_length = cdb.allocation_length.get() as usize;
477            page_code = cdb.flags2.page_code();
478            page_control = cdb.flags2.pc() << 6;
479            header_size = MODE_PARAMETER_HEADER10_SIZE;
480        } else {
481            let cdb = scsi::ModeSense::read_from_prefix(&request.cdb[..])
482                .unwrap()
483                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
484            allocation_length = cdb.allocation_length as usize;
485            page_code = cdb.flags2.page_code();
486            page_control = cdb.flags2.pc() << 6;
487            header_size = MODE_PARAMETER_HEADER_SIZE;
488        }
489
490        // It is valid to not supply a buffer, just complete immediately.
491        if allocation_length == 0 {
492            return Ok(0);
493        }
494
495        // Verify that the SRB actually supplies the indicated buffer and that we have enough
496        // for a single header (not sure if this is correct).
497        if allocation_length > external_data.len() || allocation_length < header_size {
498            tracelimit::error_ratelimited!(
499                allocation_length,
500                external_data = external_data.len(),
501                header_size,
502                "invalid cdb"
503            );
504            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
505        }
506
507        if page_control == scsi::MODE_CONTROL_SAVED_VALUES
508            || (page_code != scsi::MODE_PAGE_CACHING && page_code != scsi::MODE_PAGE_ALL)
509        {
510            return Err(ScsiError::UnsupportedModePageCode(page_control, page_code));
511        }
512
513        let mut dsp = 0;
514        if self.disk.is_read_only() {
515            dsp |= scsi::MODE_DSP_WRITE_PROTECT;
516        }
517
518        if self.scsi_parameters.support_fua {
519            dsp |= scsi::MODE_DSP_FUA_SUPPORTED;
520        }
521
522        let temp;
523        let temp10;
524        let header = if is_mode_sense_10 {
525            temp10 = scsi::ModeParameterHeader10 {
526                mode_data_length: MODE_DATA_LENGTH10.into(),
527                device_specific_parameter: dsp,
528                ..FromZeros::new_zeroed()
529            };
530            temp10.as_bytes()
531        } else {
532            temp = scsi::ModeParameterHeader {
533                mode_data_length: MODE_DATA_LENGTH,
534                device_specific_parameter: dsp,
535                ..FromZeros::new_zeroed()
536            };
537            temp.as_bytes()
538        };
539
540        let mut page = scsi::ModeCachingPage {
541            page_code: scsi::MODE_PAGE_CACHING,
542            page_length: (MODE_CACHING_PAGE_SIZE - 2) as u8,
543            ..FromZeros::new_zeroed()
544        };
545
546        if (page_control == scsi::MODE_CONTROL_CURRENT_VALUES
547            || page_control == scsi::MODE_CONTROL_DEFAULT_VALUES)
548            && external_data.len() - header_size >= scsi::WRITE_CACHE_ENABLE_BYTE_OFFSET
549        {
550            if self.scsi_parameters.write_cache_enabled {
551                page.flags |= scsi::MODE_CACHING_WRITE_CACHE_ENABLE;
552            }
553        }
554
555        // HEADER10_SIZE > HEADER_SIZE ensures we have enough space.
556        let mut data = [0; MODE_PARAMETER_HEADER10_SIZE + MODE_CACHING_PAGE_SIZE];
557        data[..header_size].copy_from_slice(header);
558        data[header_size..header_size + MODE_CACHING_PAGE_SIZE].copy_from_slice(page.as_bytes());
559        let tx = std::cmp::min(allocation_length, header_size + MODE_CACHING_PAGE_SIZE);
560        external_data
561            .writer()
562            .write(&data[..tx])
563            .map_err(ScsiError::MemoryAccess)?;
564
565        Ok(tx)
566    }
567
568    fn handle_service_action_in16(
569        &self,
570        external_data: &RequestBuffers<'_>,
571        request: &Request,
572        sector_count: u64,
573    ) -> Result<usize, ScsiError> {
574        let cdb = scsi::ServiceActionIn16::read_from_prefix(&request.cdb[..])
575            .unwrap()
576            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
577        match cdb.service_action & 0x1f {
578            scsi::SERVICE_ACTION_READ_CAPACITY16 => {
579                let min = size_of::<scsi::ReadCapacityDataEx>();
580                if external_data.len() < min {
581                    tracelimit::error_ratelimited!(len = external_data.len(), min, "data overrun");
582                    return Err(ScsiError::DataOverrun);
583                }
584
585                let mut data = scsi::ReadCapacity16Data {
586                    ex: scsi::ReadCapacityDataEx {
587                        // This query wants the LBA sector index of the last sector, not the
588                        // number of sectors - hence the minus one.
589                        logical_block_address: (sector_count - 1).into(),
590                        bytes_per_block: (1u32 << self.sector_shift).into(),
591                    },
592                    exponents: self.physical_extra_shift,
593                    ..FromZeros::new_zeroed()
594                };
595
596                if self.scsi_parameters.support_unmap {
597                    // report trim capabilities:
598                    //  - trim is supported
599                    //  - read zero after trim is not supported
600                    data.lowest_aligned_block_msb |= scsi::READ_CAPACITY16_LBPME;
601                }
602
603                let tx = std::cmp::min(external_data.len(), size_of::<scsi::ReadCapacity16Data>());
604                external_data
605                    .writer()
606                    .write(&data.as_bytes()[..tx])
607                    .map_err(ScsiError::MemoryAccess)?;
608
609                Ok(tx)
610            }
611            scsi::SERVICE_ACTION_GET_LBA_STATUS => {
612                if !self.scsi_parameters.support_get_lba_status {
613                    tracing::debug!("doesn't support get lba status");
614                    Err(ScsiError::IllegalRequest(
615                        AdditionalSenseCode::ILLEGAL_COMMAND,
616                    ))
617                } else {
618                    self.handle_get_lba_status(external_data, request, sector_count)
619                }
620            }
621            _ => Err(ScsiError::UnsupportedServiceAction(cdb.service_action)),
622        }
623    }
624
625    fn handle_read_capacity(
626        &self,
627        external_data: &RequestBuffers<'_>,
628        sector_count: u64,
629    ) -> Result<usize, ScsiError> {
630        let tx = size_of::<scsi::ReadCapacityData>();
631        if external_data.len() < tx {
632            tracelimit::error_ratelimited!(len = external_data.len(), tx, "data overrun");
633            return Err(ScsiError::DataOverrun);
634        }
635
636        // This query wants the LBA sector index of the last sector, not the
637        // number of sectors - hence the minus one.
638        // If the VHD is larger than the SCSI structure can support, Report
639        // the largest size possible.
640        let last_lba = std::cmp::min(sector_count - 1, u32::MAX.into());
641        let data = scsi::ReadCapacityData {
642            logical_block_address: (last_lba as u32).into(),
643            bytes_per_block: (1u32 << self.sector_shift).into(),
644        };
645
646        external_data
647            .writer()
648            .write(data.as_bytes())
649            .map_err(ScsiError::MemoryAccess)?;
650
651        Ok(tx)
652    }
653
654    fn handle_verify_validation(
655        &self,
656        request: &Request,
657        sector_count: u64,
658    ) -> Result<usize, ScsiError> {
659        let op = request.scsiop();
660        tracing::debug!("handle_verify_validation");
661        let (start_lba, lba_count) = match op {
662            ScsiOp::VERIFY | ScsiOp::WRITE_VERIFY => {
663                let cdb = scsi::Cdb10::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
664                if cdb.flags.relative_address() {
665                    tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
666                    return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
667                }
668                (
669                    cdb.logical_block.get() as u64,
670                    cdb.transfer_blocks.get() as u64,
671                )
672            }
673            ScsiOp::VERIFY12 | ScsiOp::WRITE_VERIFY12 => {
674                let cdb = scsi::Cdb12::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
675                if cdb.flags.relative_address() {
676                    tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
677                    return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
678                }
679                (
680                    cdb.logical_block.get() as u64,
681                    cdb.transfer_blocks.get() as u64,
682                )
683            }
684            ScsiOp::VERIFY16 | ScsiOp::WRITE_VERIFY16 => {
685                let cdb = scsi::Cdb16::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
686                (cdb.logical_block.get(), cdb.transfer_blocks.get() as u64)
687            }
688            _ => unreachable!(),
689        };
690
691        if !validate_lba_range(sector_count, start_lba, lba_count) {
692            //valiate_lba_range trace errors
693            return Err(ScsiError::IllegalRequest(
694                AdditionalSenseCode::ILLEGAL_BLOCK,
695            ));
696        }
697
698        Ok(0)
699    }
700
701    fn handle_send_diagnostic_validation(&self, request: &Request) -> Result<usize, ScsiError> {
702        tracing::debug!("handle_send_diagnostic_validation");
703        let cdb = scsi::SendDiagnostic::read_from_prefix(&request.cdb[..])
704            .unwrap()
705            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
706        if cdb.flags.self_test_code() == 0
707            && !cdb.flags.page_format()
708            && cdb.parameter_list_length.get() == 0
709        {
710            Ok(0)
711        } else {
712            Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB))
713        }
714    }
715
716    fn handle_control_cdb(
717        &self,
718        external_data: &RequestBuffers<'_>,
719        request: &Request,
720        sector_count: u64,
721    ) -> Result<usize, ScsiError> {
722        let op = request.scsiop();
723        match op {
724            ScsiOp::INQUIRY => self.handle_inquiry(external_data, request, sector_count),
725            ScsiOp::REQUEST_SENSE => self.handle_request_sense(external_data, request, false),
726            ScsiOp::MODE_SENSE | ScsiOp::MODE_SENSE10 => {
727                self.handle_mode_sense(external_data, request)
728            }
729            ScsiOp::TEST_UNIT_READY
730            | ScsiOp::FORMAT_UNIT
731            | ScsiOp::RESERVE_UNIT
732            | ScsiOp::RELEASE_UNIT
733            | ScsiOp::MEDIUM_REMOVAL => Ok(0),
734            ScsiOp::SEND_DIAGNOSTIC => self.handle_send_diagnostic_validation(request),
735            ScsiOp::READ_CAPACITY => self.handle_read_capacity(external_data, sector_count),
736            // It's SCSIOP_READ_CAPACITY16 in vhdmp
737            ScsiOp::SERVICE_ACTION_IN16 => {
738                self.handle_service_action_in16(external_data, request, sector_count)
739            }
740            ScsiOp::MODE_SELECT | ScsiOp::MODE_SELECT10 => {
741                self.handle_mode_select(external_data, request)
742            }
743            ScsiOp::VERIFY
744            | ScsiOp::VERIFY12
745            | ScsiOp::VERIFY16
746            | ScsiOp::WRITE_VERIFY
747            | ScsiOp::WRITE_VERIFY12
748            | ScsiOp::WRITE_VERIFY16 => self.handle_verify_validation(request, sector_count),
749            _ => {
750                tracing::debug!(?op, "illegal command");
751                Err(ScsiError::IllegalRequest(
752                    AdditionalSenseCode::ILLEGAL_COMMAND,
753                ))
754            }
755        }
756    }
757
758    fn validate_data_cdb(
759        &self,
760        external_data: &RequestBuffers<'_>,
761        request: &Request,
762        sector_count: u64,
763    ) -> Result<RequestParameters, ScsiError> {
764        let cdb = scsi::Cdb10::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
765        if cdb.flags.relative_address() {
766            tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
767            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
768        }
769        let len = cdb.transfer_blocks.get() as u64;
770        let offset = cdb.logical_block.get() as u64;
771        let sector_shift = self.sector_shift;
772        let max = external_data.len() >> sector_shift;
773        if len == 0 || len as usize > max {
774            tracelimit::error_ratelimited!(len, max, "illegal block");
775            return Err(ScsiError::IllegalRequest(
776                AdditionalSenseCode::ILLEGAL_BLOCK,
777            ));
778        }
779
780        if sector_count <= offset || sector_count - offset < len {
781            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
782            return Err(ScsiError::IllegalRequest(
783                AdditionalSenseCode::ILLEGAL_BLOCK,
784            ));
785        }
786
787        let fua = cdb.flags.fua();
788        let tx = (len as usize) << sector_shift;
789        Ok(RequestParameters { tx, offset, fua })
790    }
791
792    fn validate_data_cdb6_read_write(
793        &self,
794        external_data: &RequestBuffers<'_>,
795        request: &Request,
796        sector_count: u64,
797    ) -> Result<RequestParameters, ScsiError> {
798        let cdb = scsi::Cdb6ReadWrite::read_from_prefix(&request.cdb[..])
799            .unwrap()
800            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
801        let len = cdb.transfer_blocks as u64;
802        let offset = u32::from_be_bytes([
803            0,
804            cdb.logical_block[0],
805            cdb.logical_block[1],
806            cdb.logical_block[2],
807        ]) as u64;
808        let sector_shift = self.sector_shift;
809        let max = external_data.len() >> sector_shift;
810        if len == 0 || len as usize > max {
811            tracelimit::error_ratelimited!(len, max, "illegal block");
812            return Err(ScsiError::IllegalRequest(
813                AdditionalSenseCode::ILLEGAL_BLOCK,
814            ));
815        }
816
817        if sector_count <= offset || sector_count - offset < len {
818            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
819            return Err(ScsiError::IllegalRequest(
820                AdditionalSenseCode::ILLEGAL_BLOCK,
821            ));
822        }
823
824        let tx = (len as usize) << sector_shift;
825        Ok(RequestParameters {
826            tx,
827            offset,
828            fua: false,
829        })
830    }
831
832    fn validate_data_cdb12(
833        &self,
834        external_data: &RequestBuffers<'_>,
835        request: &Request,
836        sector_count: u64,
837    ) -> Result<RequestParameters, ScsiError> {
838        let cdb = scsi::Cdb12::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
839        if cdb.flags.relative_address() {
840            tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
841            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
842        }
843        let len = cdb.transfer_blocks.get() as u64;
844        let offset = cdb.logical_block.get() as u64;
845        let max = external_data.len() >> self.sector_shift;
846        if len == 0 || len as usize > max {
847            tracelimit::error_ratelimited!(len, max, "illegal block");
848            return Err(ScsiError::IllegalRequest(
849                AdditionalSenseCode::ILLEGAL_BLOCK,
850            ));
851        }
852
853        if sector_count <= offset || sector_count - offset < len {
854            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
855            return Err(ScsiError::IllegalRequest(
856                AdditionalSenseCode::ILLEGAL_BLOCK,
857            ));
858        }
859
860        let fua = cdb.flags.fua();
861        let tx = (len as usize) << self.sector_shift;
862        Ok(RequestParameters { tx, offset, fua })
863    }
864
865    fn validate_data_cdb16(
866        &self,
867        external_data: &RequestBuffers<'_>,
868        request: &Request,
869        sector_count: u64,
870    ) -> Result<RequestParameters, ScsiError> {
871        let cdb = scsi::Cdb16::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
872        let len = cdb.transfer_blocks.get() as u64;
873        let offset = cdb.logical_block.get();
874        let sector_shift = self.sector_shift;
875        let max = external_data.len() >> sector_shift;
876        if len == 0 || len as usize > max {
877            tracelimit::error_ratelimited!(len, max, "illegal block");
878            return Err(ScsiError::IllegalRequest(
879                AdditionalSenseCode::ILLEGAL_BLOCK,
880            ));
881        }
882
883        if sector_count <= offset || sector_count - offset < len {
884            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
885            return Err(ScsiError::IllegalRequest(
886                AdditionalSenseCode::ILLEGAL_BLOCK,
887            ));
888        }
889
890        let fua = cdb.flags.fua();
891        let tx = (len as usize) << sector_shift;
892        Ok(RequestParameters { tx, offset, fua })
893    }
894
895    fn validate_write_same(
896        &self,
897        external_data: &RequestBuffers<'_>,
898        request: &Request,
899        sector_count: u64,
900    ) -> Result<WriteSameParameters, ScsiError> {
901        let op = request.scsiop();
902        let mut p = match op {
903            ScsiOp::WRITE_SAME => {
904                let cdb = scsi::Cdb10::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
905                if cdb.flags.relative_address() {
906                    tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
907                    return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
908                }
909                WriteSameParameters {
910                    start_lba: cdb.logical_block.get() as u64,
911                    lba_count: cdb.transfer_blocks.get() as usize,
912                    fua: cdb.flags.fua(),
913                    sector_size: 0,
914                    tx: 0,
915                }
916            }
917            ScsiOp::WRITE_SAME16 => {
918                let cdb = scsi::Cdb16::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
919                WriteSameParameters {
920                    start_lba: cdb.logical_block.get(),
921                    lba_count: cdb.transfer_blocks.get() as usize,
922                    fua: cdb.flags.fua(),
923                    sector_size: 0,
924                    tx: 0,
925                }
926            }
927            _ => unreachable!(),
928        };
929
930        if !validate_lba_range(sector_count, p.start_lba, p.lba_count.try_into().unwrap()) {
931            //valiate_lba_range trace errors
932            return Err(ScsiError::IllegalRequest(
933                AdditionalSenseCode::ILLEGAL_BLOCK,
934            ));
935        }
936
937        if self.disk.is_read_only() {
938            return Err(ScsiError::WriteProtected);
939        }
940
941        // max length check
942        p.tx = p.lba_count << self.sector_shift;
943        if p.tx > VHDMP_MAX_WRITE_SAME_LENGTH_BYTES.try_into().unwrap()
944            || p.tx > self.scsi_parameters.maximum_transfer_length
945        {
946            tracelimit::error_ratelimited!(p.tx, "transfer length too big");
947            return Err(ScsiError::IllegalRequest(
948                AdditionalSenseCode::ILLEGAL_BLOCK,
949            ));
950        }
951
952        // The size of the supplied data buffer must be at least one sector.
953        p.sector_size = self.sector_size.try_into().unwrap();
954        let external_data_len = external_data.len();
955        if p.lba_count > 0 && external_data_len < p.sector_size {
956            tracelimit::error_ratelimited!(external_data_len, "provided transfer length too small");
957            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
958        }
959
960        Ok(p)
961    }
962
963    fn process_result(&self, result: Result<usize, ScsiError>, op: ScsiOp) -> ScsiResult {
964        let result = result.map_err(|err| {
965            match err {
966                ScsiError::UnsupportedModePageCode(..)
967                | ScsiError::UnsupportedServiceAction(_)
968                | ScsiError::UnsupportedVpdPageCode(_) => tracing::debug!(disk = ?self.scsi_parameters.disk_id, error = err.as_error(), ?op, "scsi_error"),
969                | ScsiError::IllegalRequest(_) => tracing::debug!(disk = ?self.scsi_parameters.disk_id, error = err.as_error(), ?op, "scsi_error"),
970                _ => tracelimit::warn_ratelimited!(disk = ?self.scsi_parameters.disk_id, error = err.as_error(), ?op, "scsi_error"),
971            }
972            err
973        });
974
975        let result = match result {
976            Ok(tx) => ScsiResult {
977                scsi_status: ScsiStatus::GOOD,
978                srb_status: SrbStatus::SUCCESS,
979                tx,
980                sense_data: None,
981            },
982            Err(err) => {
983                match err {
984                    ScsiError::MemoryAccess(_)
985                    | ScsiError::UnsupportedModePageCode(..)
986                    | ScsiError::UnsupportedServiceAction(_)
987                    | ScsiError::UnsupportedVpdPageCode(_)
988                    | ScsiError::SrbError
989                    | ScsiError::Disk(DiskError::InvalidInput)
990                    | ScsiError::Disk(DiskError::MemoryAccess(_)) => ScsiResult {
991                        scsi_status: ScsiStatus::CHECK_CONDITION,
992                        srb_status: SrbStatus::INVALID_REQUEST,
993                        tx: 0,
994                        sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
995                    },
996                    ScsiError::IllegalRequest(sense_code) => ScsiResult {
997                        scsi_status: ScsiStatus::CHECK_CONDITION,
998                        srb_status: SrbStatus::INVALID_REQUEST,
999                        tx: 0,
1000                        sense_data: Some(illegal_request_sense(sense_code)),
1001                    },
1002                    ScsiError::DataOverrun => ScsiResult {
1003                        scsi_status: ScsiStatus::CHECK_CONDITION,
1004                        srb_status: SrbStatus::DATA_OVERRUN,
1005                        tx: 0,
1006                        sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
1007                    },
1008                    ScsiError::UnitAttention => ScsiResult {
1009                        scsi_status: ScsiStatus::CHECK_CONDITION,
1010                        srb_status: SrbStatus::ERROR,
1011                        tx: 0,
1012                        sense_data: Some(scsi::SenseData::new(
1013                            SenseKey::UNIT_ATTENTION,
1014                            AdditionalSenseCode::PARAMETERS_CHANGED,
1015                            scsi::SCSI_SENSEQ_CAPACITY_DATA_CHANGED,
1016                        )),
1017                    },
1018                    ScsiError::WriteProtected | ScsiError::Disk(DiskError::ReadOnly) => {
1019                        ScsiResult {
1020                            scsi_status: ScsiStatus::CHECK_CONDITION,
1021                            srb_status: SrbStatus::ERROR,
1022                            tx: 0,
1023                            sense_data: Some(scsi::SenseData::new(
1024                                SenseKey::DATA_PROTECT,
1025                                AdditionalSenseCode::WRITE_PROTECT,
1026                                0,
1027                            )),
1028                        }
1029                    }
1030                    ScsiError::Disk(err) => {
1031                        match err {
1032                            DiskError::AbortDueToPreemptAndAbort => ScsiResult {
1033                                scsi_status: ScsiStatus::TASK_ABORTED,
1034                                srb_status: SrbStatus::ABORTED,
1035                                tx: 0,
1036                                sense_data: Some(scsi::SenseData::new(
1037                                    SenseKey::ABORTED_COMMAND,
1038                                    AdditionalSenseCode::NO_SENSE,
1039                                    0,
1040                                )),
1041                            },
1042                            DiskError::IllegalBlock => ScsiResult {
1043                                scsi_status: ScsiStatus::CHECK_CONDITION,
1044                                srb_status: SrbStatus::ERROR,
1045                                tx: 0,
1046                                sense_data: Some(scsi::SenseData::new(
1047                                    SenseKey::ILLEGAL_REQUEST,
1048                                    AdditionalSenseCode::ILLEGAL_BLOCK,
1049                                    0,
1050                                )),
1051                            },
1052                            DiskError::Io(_) => ScsiResult {
1053                                scsi_status: ScsiStatus::CHECK_CONDITION,
1054                                srb_status: SrbStatus::ERROR,
1055                                tx: 0,
1056                                sense_data: Some(scsi::SenseData::new(
1057                                    SenseKey::MEDIUM_ERROR,
1058                                    AdditionalSenseCode::NO_SENSE,
1059                                    0,
1060                                )),
1061                            },
1062                            DiskError::MediumError(_, details) => {
1063                                let (sense_code, qualifier) = match details {
1064                                    disk_backend::MediumErrorDetails::ApplicationTagCheckFailed => {
1065                                        (
1066                                            AdditionalSenseCode::UNRECOVERED_ERROR,
1067                                            scsi::SCSI_SENSEQ_LOGICAL_BLOCK_TAG_CHECK_FAILED,
1068                                        )
1069                                    }
1070                                    disk_backend::MediumErrorDetails::GuardCheckFailed => (
1071                                        AdditionalSenseCode::CRC_OR_ECC_ERROR,
1072                                        scsi::SCSI_SENSEQ_LOGICAL_BLOCK_GUARD_CHECK_FAILED,
1073                                    ),
1074                                    disk_backend::MediumErrorDetails::ReferenceTagCheckFailed => (
1075                                        AdditionalSenseCode::CRC_OR_ECC_ERROR,
1076                                        scsi::SCSI_SENSEQ_LOGICAL_BLOCK_REF_TAG_CHECK_FAILED,
1077                                    ),
1078                                    disk_backend::MediumErrorDetails::UnrecoveredReadError => {
1079                                        (AdditionalSenseCode::UNRECOVERED_ERROR, 0)
1080                                    }
1081                                    disk_backend::MediumErrorDetails::WriteFault => {
1082                                        (AdditionalSenseCode::WRITE, 0)
1083                                    }
1084                                };
1085                                ScsiResult {
1086                                    scsi_status: ScsiStatus::CHECK_CONDITION,
1087                                    srb_status: SrbStatus::ERROR,
1088                                    tx: 0,
1089                                    sense_data: Some(scsi::SenseData::new(
1090                                        SenseKey::MEDIUM_ERROR,
1091                                        sense_code,
1092                                        qualifier,
1093                                    )),
1094                                }
1095                            }
1096                            DiskError::ReservationConflict => ScsiResult {
1097                                scsi_status: ScsiStatus::RESERVATION_CONFLICT,
1098                                srb_status: SrbStatus::ERROR,
1099                                tx: 0,
1100                                sense_data: None,
1101                            },
1102                            DiskError::UnsupportedEject => ScsiResult {
1103                                scsi_status: ScsiStatus::CHECK_CONDITION,
1104                                srb_status: SrbStatus::INVALID_REQUEST,
1105                                tx: 0,
1106                                sense_data: Some(illegal_request_sense(
1107                                    AdditionalSenseCode::ILLEGAL_COMMAND,
1108                                )),
1109                            },
1110                            DiskError::InvalidInput
1111                            | DiskError::MemoryAccess(_)
1112                            | DiskError::ReadOnly => unreachable!(), //handled above
1113                        }
1114                    }
1115                }
1116            }
1117        };
1118
1119        self.sense_data.set(result.sense_data.as_ref());
1120        if op == ScsiOp::PERSISTENT_RESERVE_OUT && result.scsi_status != ScsiStatus::GOOD {
1121            tracing::warn!(scsi_result = ?result, "PERSISTENT_RESERVE_OUT failed.");
1122        } else {
1123            tracing::trace!(scsi_result = ?result, ?op, "process_result completed.");
1124        }
1125
1126        result
1127    }
1128
1129    /// Gets the current sector count from the underlying disk.
1130    ///
1131    /// If the sector count has changed since the last call, returns an error so
1132    /// that the caller can propagate unit attention to the guest.
1133    ///
1134    /// For `INQUIRY`, returns the new sector count without error but does not
1135    /// update the last observed one.
1136    fn get_and_update_sector_count(&self, op: ScsiOp) -> Result<u64, u64> {
1137        let current = self.last_sector_count.load(Ordering::Relaxed);
1138        let sector_count = self.disk.sector_count();
1139        // Don't process sector count updates during inquiry (but do report the new sector size).
1140        if sector_count == current || op == ScsiOp::INQUIRY {
1141            return Ok(sector_count);
1142        }
1143        tracing::info!(
1144            sector_count,
1145            old_sector_count = current,
1146            "updating sector count"
1147        );
1148        if self
1149            .last_sector_count
1150            .compare_exchange(current, sector_count, Ordering::SeqCst, Ordering::SeqCst)
1151            .is_err()
1152        {
1153            // Another request already handled the unit attention.
1154            return Ok(sector_count);
1155        }
1156        Err(sector_count)
1157    }
1158}
1159
1160impl SimpleScsiDisk {
1161    async fn handle_data_cdb(
1162        &self,
1163        external_data: &RequestBuffers<'_>,
1164        request: &Request,
1165        sector_count: u64,
1166    ) -> Result<usize, ScsiError> {
1167        let op = request.scsiop();
1168        let is_read;
1169        let p = match op {
1170            ScsiOp::READ | ScsiOp::WRITE => {
1171                is_read = op == ScsiOp::READ;
1172                self.validate_data_cdb(external_data, request, sector_count)?
1173            }
1174            ScsiOp::READ6 | ScsiOp::WRITE6 => {
1175                is_read = op == ScsiOp::READ6;
1176                self.validate_data_cdb6_read_write(external_data, request, sector_count)?
1177            }
1178            ScsiOp::READ12 | ScsiOp::WRITE12 => {
1179                is_read = op == ScsiOp::READ12;
1180                self.validate_data_cdb12(external_data, request, sector_count)?
1181            }
1182            ScsiOp::READ16 | ScsiOp::WRITE16 => {
1183                is_read = op == ScsiOp::READ16;
1184                self.validate_data_cdb16(external_data, request, sector_count)?
1185            }
1186            _ => unreachable!(),
1187        };
1188
1189        // Note that `p.tx` is validated above to be in range.
1190        let external_data = external_data.subrange(0, p.tx);
1191
1192        Ok(if is_read {
1193            self.disk
1194                .read_vectored(&external_data, p.offset)
1195                .await
1196                .map_err(ScsiError::Disk)?;
1197
1198            p.tx
1199        } else {
1200            if self.disk.is_read_only() {
1201                return Err(ScsiError::WriteProtected);
1202            }
1203
1204            self.disk
1205                .write_vectored(&external_data, p.offset, p.fua)
1206                .await
1207                .map_err(ScsiError::Disk)?;
1208
1209            p.tx
1210        })
1211    }
1212
1213    async fn handle_synchronize_cache(&self) -> Result<usize, ScsiError> {
1214        self.disk.sync_cache().await.map_err(ScsiError::Disk)?;
1215        Ok(0)
1216    }
1217
1218    async fn handle_write_same(
1219        &self,
1220        external_data: &RequestBuffers<'_>,
1221        request: &Request,
1222        sector_count: u64,
1223    ) -> Result<usize, ScsiError> {
1224        let p = self.validate_write_same(external_data, request, sector_count)?;
1225        if p.tx > 0 {
1226            // Note that `p.sector_size` is validated above to be in range.
1227            let external_data = external_data.subrange(0, p.sector_size);
1228            // TODO: pass this request through to the disk rather than looping like this.
1229            for offset in p.start_lba..p.start_lba + (p.lba_count as u64) {
1230                self.disk
1231                    .write_vectored(&external_data, offset, p.fua)
1232                    .await
1233                    .map_err(ScsiError::Disk)?;
1234            }
1235        }
1236
1237        Ok(p.tx)
1238    }
1239
1240    async fn handle_start_stop(&self, request: &Request) -> Result<usize, ScsiError> {
1241        let cdb = scsi::StartStop::read_from_prefix(&request.cdb[..])
1242            .unwrap()
1243            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1244        if cdb.immediate & scsi::IMMEDIATE_BIT != 0 {
1245            tracing::debug!("immediate bit is not supported");
1246            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
1247        }
1248
1249        if cdb.flag.start() {
1250            return Ok(0);
1251        };
1252
1253        self.disk.sync_cache().await.map_err(ScsiError::Disk)?;
1254        Ok(0)
1255    }
1256}
1257
1258impl AsyncScsiDisk for SimpleScsiDisk {
1259    fn execute_scsi<'a>(
1260        &'a self,
1261        external_data: &'a RequestBuffers<'a>,
1262        request: &'a Request,
1263    ) -> StackFuture<'a, ScsiResult, { ASYNC_SCSI_DISK_STACK_SIZE }> {
1264        StackFuture::from(async move {
1265            let op = request.scsiop();
1266
1267            let sector_count = match self.get_and_update_sector_count(op) {
1268                Ok(c) => c,
1269                Err(_) => {
1270                    // The sector count has changed. Report unit attention.
1271                    let result = match op {
1272                        ScsiOp::REQUEST_SENSE => {
1273                            self.handle_request_sense(external_data, request, true)
1274                        }
1275                        _ => Err(ScsiError::UnitAttention),
1276                    };
1277                    return self.process_result(result, op);
1278                }
1279            };
1280
1281            let result = match op {
1282                ScsiOp::WRITE
1283                | ScsiOp::WRITE6
1284                | ScsiOp::WRITE12
1285                | ScsiOp::WRITE16
1286                | ScsiOp::READ
1287                | ScsiOp::READ6
1288                | ScsiOp::READ12
1289                | ScsiOp::READ16 => {
1290                    self.handle_data_cdb(external_data, request, sector_count)
1291                        .instrument(tracing::trace_span!("handle_data_cdb_async", ?op,))
1292                        .await
1293                }
1294                ScsiOp::WRITE_SAME | ScsiOp::WRITE_SAME16 => {
1295                    self.handle_write_same(external_data, request, sector_count)
1296                        .instrument(tracing::trace_span!("handle_write_same_async"))
1297                        .await
1298                }
1299                ScsiOp::SYNCHRONIZE_CACHE | ScsiOp::SYNCHRONIZE_CACHE16 => {
1300                    self.handle_synchronize_cache()
1301                        .instrument(tracing::trace_span!("handle_synchronize_cache_async", ?op,))
1302                        .await
1303                }
1304                ScsiOp::START_STOP_UNIT => {
1305                    self.handle_start_stop(request)
1306                        .instrument(tracing::trace_span!("handle_start_stop_async",))
1307                        .await
1308                }
1309                ScsiOp::UNMAP => {
1310                    self.handle_unmap(external_data, request, sector_count)
1311                        .instrument(tracing::debug_span!("handle_unmap_async"))
1312                        .await
1313                }
1314                ScsiOp::PERSISTENT_RESERVE_IN | ScsiOp::PERSISTENT_RESERVE_OUT => {
1315                    self.handle_persistent_reserve(external_data, request)
1316                        .instrument(tracing::trace_span!("handle_persistent_reserve_async", ?op,))
1317                        .await
1318                }
1319                _ => {
1320                    let _span = tracing::trace_span!("handle_control_cdb", ?op,).entered();
1321                    self.handle_control_cdb(external_data, request, sector_count)
1322                }
1323            };
1324
1325            self.process_result(result, op)
1326        })
1327    }
1328}
1329
1330impl Inspect for SimpleScsiDisk {
1331    fn inspect(&self, req: inspect::Request<'_>) {
1332        let mut resp = req.respond();
1333        resp.binary("disk_id", self.scsi_parameters.disk_id)
1334            .field("logical_sector_size", self.sector_size)
1335            .field(
1336                "physical_sector_size",
1337                1usize << self.sector_shift << self.physical_extra_shift,
1338            )
1339            .field(
1340                "sector_count",
1341                self.last_sector_count.load(Ordering::Relaxed),
1342            )
1343            .field("scsi_parameters", &self.scsi_parameters)
1344            .field("pr", self.support_pr)
1345            .field("backend", &self.disk);
1346    }
1347}
1348
1349#[derive(Default, Debug)]
1350struct SenseDataSlot {
1351    is_valid: AtomicBool,
1352    data: Mutex<Option<scsi::SenseData>>,
1353}
1354
1355impl SenseDataSlot {
1356    /// Updates sense data.
1357    fn set(&self, sense_data: Option<&scsi::SenseData>) {
1358        match sense_data {
1359            None => {
1360                // Only clear sense data if it is set to avoid taking the cache
1361                // line exclusive in the common case.
1362                //
1363                // Access with relaxed ordering because sense data state is not
1364                // well defined if there are multiple concurrent IOs anyway.
1365                if self.is_valid.load(Ordering::Relaxed) {
1366                    self.is_valid.store(false, Ordering::Relaxed)
1367                }
1368            }
1369            Some(sense_data) => {
1370                *self.data.lock() = Some(*sense_data);
1371                self.is_valid.store(true, Ordering::Release);
1372            }
1373        }
1374    }
1375
1376    /// Gets sense data without clearing it.
1377    fn get(&self) -> Option<scsi::SenseData> {
1378        if self.is_valid.load(Ordering::Relaxed) {
1379            // Note that this might still be None due to race conditions with
1380            // multiple concurrent IOs.
1381            *self.data.lock()
1382        } else {
1383            None
1384        }
1385    }
1386
1387    /// Gets and clears sense data.
1388    pub(crate) fn take(&self) -> Option<scsi::SenseData> {
1389        if self.is_valid.swap(false, Ordering::Acquire) {
1390            // Note that this might still be None due to race conditions with
1391            // multiple concurrent IOs.
1392            self.data.lock().take()
1393        } else {
1394            None
1395        }
1396    }
1397}