Skip to main content

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
345        let (request_length, header_size, is_spbit_set) = if is_mode_select_10 {
346            let cdb = scsi::ModeSelect10::read_from_prefix(&request.cdb[..])
347                .unwrap()
348                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
349            (
350                cdb.parameter_list_length.get() as usize,
351                MODE_PARAMETER_HEADER10_SIZE,
352                cdb.flags.spbit(),
353            )
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            (
359                cdb.parameter_list_length as usize,
360                MODE_PARAMETER_HEADER_SIZE,
361                cdb.flags.spbit(),
362            )
363        };
364
365        if request_length == 0 {
366            return Ok(0);
367        }
368
369        // Validate buffer size
370        let min = header_size + MODE_CACHING_PAGE_SIZE;
371        if request_length != external_data.len() || request_length < min {
372            tracelimit::error_ratelimited!(
373                request_length,
374                external_data = external_data.len(),
375                min,
376                "invalid parameter list length"
377            );
378            return Err(ScsiError::IllegalRequest(
379                AdditionalSenseCode::PARAMETER_LIST_LENGTH,
380            ));
381        }
382
383        // Don't support saving pages.
384        if is_spbit_set {
385            tracing::debug!("doesn't support saving pages");
386            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
387        }
388
389        let mut buffer: Vec<u8> = vec![0; request_length];
390        external_data
391            .reader()
392            .read(&mut buffer)
393            .map_err(ScsiError::MemoryAccess)?;
394
395        let block_descriptor_length = if is_mode_select_10 {
396            let temp10 = scsi::ModeParameterHeader10::read_from_prefix(
397                &buffer[..MODE_PARAMETER_HEADER10_SIZE],
398            )
399            .unwrap()
400            .0; // TODO: zerocopy: from-prefix (read_from_prefix): use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
401            usize::from(temp10.block_descriptor_length)
402        } else {
403            let temp =
404                scsi::ModeParameterHeader::read_from_prefix(&buffer[..MODE_PARAMETER_HEADER_SIZE])
405                    .unwrap()
406                    .0; // TODO: zerocopy: from-prefix (read_from_prefix): use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
407            temp.block_descriptor_length as usize
408        };
409
410        // Skip block descriptor.
411        let skipped = header_size + block_descriptor_length;
412        let min = skipped + MODE_CACHING_PAGE_SIZE;
413        if request_length < min {
414            tracelimit::error_ratelimited!(request_length, min, "invalid parameter list length");
415            return Err(ScsiError::IllegalRequest(
416                AdditionalSenseCode::PARAMETER_LIST_LENGTH,
417            ));
418        }
419
420        // Parse ModeCachingPage.
421        let page = scsi::ModeCachingPage::read_from_prefix(
422            &buffer[skipped..skipped + MODE_CACHING_PAGE_SIZE],
423        )
424        .unwrap()
425        .0; // TODO: zerocopy: from-prefix (read_from_prefix): use-rest-of-range, zerocopy: err (https://github.com/microsoft/openvmm/issues/759)
426        if page.page_code != scsi::MODE_PAGE_CACHING
427            || (page.page_length as usize) < MODE_CACHING_PAGE_SIZE
428            || ((page.flags & scsi::MODE_CACHING_WRITE_CACHE_ENABLE == 0)
429                && self.scsi_parameters.write_cache_enabled)
430            || ((page.flags & scsi::MODE_CACHING_WRITE_CACHE_ENABLE != 0)
431                && !self.scsi_parameters.write_cache_enabled)
432        {
433            // Attempts to turn off write caching must be failed, otherwise
434            // storage migration might lead to the initiator believing that it
435            // has write caching turned off when in fact write caching is
436            // turned on, which would be a potential data loss situation.
437            //
438            // Hopefully no initiator will get too annoyed when this fails.
439            // The only other option would be to erroneously report success here
440            // and then still report that write caching is off next time it's
441            // queried, but that still leaves the initiator potentially out of
442            // sync on the fact that write caching is potentially on and there's
443            // nothing we can do about it.  So hopefully reporting failure here
444            // works for all relevant initiators.
445            tracing::debug!(
446                page_code = page.page_code,
447                page_length = page.page_length,
448                flags = page.flags,
449                write_cache_enabled = self.scsi_parameters.write_cache_enabled,
450                "invalid parameter list"
451            );
452            return Err(ScsiError::IllegalRequest(
453                AdditionalSenseCode::INVALID_FIELD_PARAMETER_LIST,
454            ));
455        }
456
457        Ok(request_length)
458    }
459
460    fn handle_mode_sense(
461        &self,
462        external_data: &RequestBuffers<'_>,
463        request: &Request,
464    ) -> Result<usize, ScsiError> {
465        if external_data.is_empty() {
466            return Ok(0);
467        }
468
469        let is_mode_sense_10 = request.scsiop() == ScsiOp::MODE_SENSE10;
470
471        let (allocation_length, page_code, page_control, header_size) = if is_mode_sense_10 {
472            let cdb = scsi::ModeSense10::read_from_prefix(&request.cdb[..])
473                .unwrap()
474                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
475            (
476                cdb.allocation_length.get() as usize,
477                cdb.flags2.page_code(),
478                cdb.flags2.pc() << 6,
479                MODE_PARAMETER_HEADER10_SIZE,
480            )
481        } else {
482            let cdb = scsi::ModeSense::read_from_prefix(&request.cdb[..])
483                .unwrap()
484                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
485            (
486                cdb.allocation_length as usize,
487                cdb.flags2.page_code(),
488                cdb.flags2.pc() << 6,
489                MODE_PARAMETER_HEADER_SIZE,
490            )
491        };
492
493        // It is valid to not supply a buffer, just complete immediately.
494        if allocation_length == 0 {
495            return Ok(0);
496        }
497
498        // Verify that the SRB actually supplies the indicated buffer and that we have enough
499        // for a single header (not sure if this is correct).
500        if allocation_length > external_data.len() || allocation_length < header_size {
501            tracelimit::error_ratelimited!(
502                allocation_length,
503                external_data = external_data.len(),
504                header_size,
505                "invalid cdb"
506            );
507            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
508        }
509
510        if page_control == scsi::MODE_CONTROL_SAVED_VALUES
511            || (page_code != scsi::MODE_PAGE_CACHING && page_code != scsi::MODE_PAGE_ALL)
512        {
513            return Err(ScsiError::UnsupportedModePageCode(page_control, page_code));
514        }
515
516        let mut dsp = 0;
517        if self.disk.is_read_only() {
518            dsp |= scsi::MODE_DSP_WRITE_PROTECT;
519        }
520
521        if self.scsi_parameters.support_fua {
522            dsp |= scsi::MODE_DSP_FUA_SUPPORTED;
523        }
524
525        let temp;
526        let temp10;
527        let header = if is_mode_sense_10 {
528            temp10 = scsi::ModeParameterHeader10 {
529                mode_data_length: MODE_DATA_LENGTH10.into(),
530                device_specific_parameter: dsp,
531                ..FromZeros::new_zeroed()
532            };
533            temp10.as_bytes()
534        } else {
535            temp = scsi::ModeParameterHeader {
536                mode_data_length: MODE_DATA_LENGTH,
537                device_specific_parameter: dsp,
538                ..FromZeros::new_zeroed()
539            };
540            temp.as_bytes()
541        };
542
543        let mut page = scsi::ModeCachingPage {
544            page_code: scsi::MODE_PAGE_CACHING,
545            page_length: (MODE_CACHING_PAGE_SIZE - 2) as u8,
546            ..FromZeros::new_zeroed()
547        };
548
549        if (page_control == scsi::MODE_CONTROL_CURRENT_VALUES
550            || page_control == scsi::MODE_CONTROL_DEFAULT_VALUES)
551            && external_data.len() - header_size >= scsi::WRITE_CACHE_ENABLE_BYTE_OFFSET
552        {
553            if self.scsi_parameters.write_cache_enabled {
554                page.flags |= scsi::MODE_CACHING_WRITE_CACHE_ENABLE;
555            }
556        }
557
558        // HEADER10_SIZE > HEADER_SIZE ensures we have enough space.
559        let mut data = [0; MODE_PARAMETER_HEADER10_SIZE + MODE_CACHING_PAGE_SIZE];
560        data[..header_size].copy_from_slice(header);
561        data[header_size..header_size + MODE_CACHING_PAGE_SIZE].copy_from_slice(page.as_bytes());
562        let tx = std::cmp::min(allocation_length, header_size + MODE_CACHING_PAGE_SIZE);
563        external_data
564            .writer()
565            .write(&data[..tx])
566            .map_err(ScsiError::MemoryAccess)?;
567
568        Ok(tx)
569    }
570
571    fn handle_service_action_in16(
572        &self,
573        external_data: &RequestBuffers<'_>,
574        request: &Request,
575        sector_count: u64,
576    ) -> Result<usize, ScsiError> {
577        let cdb = scsi::ServiceActionIn16::read_from_prefix(&request.cdb[..])
578            .unwrap()
579            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
580        match cdb.service_action & 0x1f {
581            scsi::SERVICE_ACTION_READ_CAPACITY16 => {
582                let min = size_of::<scsi::ReadCapacityDataEx>();
583                if external_data.len() < min {
584                    tracelimit::error_ratelimited!(len = external_data.len(), min, "data overrun");
585                    return Err(ScsiError::DataOverrun);
586                }
587
588                let mut data = scsi::ReadCapacity16Data {
589                    ex: scsi::ReadCapacityDataEx {
590                        // This query wants the LBA sector index of the last sector, not the
591                        // number of sectors - hence the minus one.
592                        logical_block_address: (sector_count - 1).into(),
593                        bytes_per_block: (1u32 << self.sector_shift).into(),
594                    },
595                    exponents: self.physical_extra_shift,
596                    ..FromZeros::new_zeroed()
597                };
598
599                if self.scsi_parameters.support_unmap {
600                    // report trim capabilities:
601                    //  - trim is supported
602                    //  - read zero after trim is not supported
603                    data.lowest_aligned_block_msb |= scsi::READ_CAPACITY16_LBPME;
604                }
605
606                let tx = std::cmp::min(external_data.len(), size_of::<scsi::ReadCapacity16Data>());
607                external_data
608                    .writer()
609                    .write(&data.as_bytes()[..tx])
610                    .map_err(ScsiError::MemoryAccess)?;
611
612                Ok(tx)
613            }
614            scsi::SERVICE_ACTION_GET_LBA_STATUS => {
615                if !self.scsi_parameters.support_get_lba_status {
616                    tracing::debug!("doesn't support get lba status");
617                    Err(ScsiError::IllegalRequest(
618                        AdditionalSenseCode::ILLEGAL_COMMAND,
619                    ))
620                } else {
621                    self.handle_get_lba_status(external_data, request, sector_count)
622                }
623            }
624            _ => Err(ScsiError::UnsupportedServiceAction(cdb.service_action)),
625        }
626    }
627
628    fn handle_read_capacity(
629        &self,
630        external_data: &RequestBuffers<'_>,
631        sector_count: u64,
632    ) -> Result<usize, ScsiError> {
633        let tx = size_of::<scsi::ReadCapacityData>();
634        if external_data.len() < tx {
635            tracelimit::error_ratelimited!(len = external_data.len(), tx, "data overrun");
636            return Err(ScsiError::DataOverrun);
637        }
638
639        // This query wants the LBA sector index of the last sector, not the
640        // number of sectors - hence the minus one.
641        // If the VHD is larger than the SCSI structure can support, Report
642        // the largest size possible.
643        let last_lba = std::cmp::min(sector_count - 1, u32::MAX.into());
644        let data = scsi::ReadCapacityData {
645            logical_block_address: (last_lba as u32).into(),
646            bytes_per_block: (1u32 << self.sector_shift).into(),
647        };
648
649        external_data
650            .writer()
651            .write(data.as_bytes())
652            .map_err(ScsiError::MemoryAccess)?;
653
654        Ok(tx)
655    }
656
657    fn handle_verify_validation(
658        &self,
659        request: &Request,
660        sector_count: u64,
661    ) -> Result<usize, ScsiError> {
662        let op = request.scsiop();
663        tracing::debug!("handle_verify_validation");
664        let (start_lba, lba_count) = match op {
665            ScsiOp::VERIFY | ScsiOp::WRITE_VERIFY => {
666                let cdb = scsi::Cdb10::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
667                if cdb.flags.relative_address() {
668                    tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
669                    return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
670                }
671                (
672                    cdb.logical_block.get() as u64,
673                    cdb.transfer_blocks.get() as u64,
674                )
675            }
676            ScsiOp::VERIFY12 | ScsiOp::WRITE_VERIFY12 => {
677                let cdb = scsi::Cdb12::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
678                if cdb.flags.relative_address() {
679                    tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
680                    return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
681                }
682                (
683                    cdb.logical_block.get() as u64,
684                    cdb.transfer_blocks.get() as u64,
685                )
686            }
687            ScsiOp::VERIFY16 | ScsiOp::WRITE_VERIFY16 => {
688                let cdb = scsi::Cdb16::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
689                (cdb.logical_block.get(), cdb.transfer_blocks.get() as u64)
690            }
691            _ => unreachable!(),
692        };
693
694        if !validate_lba_range(sector_count, start_lba, lba_count) {
695            //valiate_lba_range trace errors
696            return Err(ScsiError::IllegalRequest(
697                AdditionalSenseCode::ILLEGAL_BLOCK,
698            ));
699        }
700
701        Ok(0)
702    }
703
704    fn handle_send_diagnostic_validation(&self, request: &Request) -> Result<usize, ScsiError> {
705        tracing::debug!("handle_send_diagnostic_validation");
706        let cdb = scsi::SendDiagnostic::read_from_prefix(&request.cdb[..])
707            .unwrap()
708            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
709        if cdb.flags.self_test_code() == 0
710            && !cdb.flags.page_format()
711            && cdb.parameter_list_length.get() == 0
712        {
713            Ok(0)
714        } else {
715            Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB))
716        }
717    }
718
719    fn handle_control_cdb(
720        &self,
721        external_data: &RequestBuffers<'_>,
722        request: &Request,
723        sector_count: u64,
724    ) -> Result<usize, ScsiError> {
725        let op = request.scsiop();
726        match op {
727            ScsiOp::INQUIRY => self.handle_inquiry(external_data, request, sector_count),
728            ScsiOp::REQUEST_SENSE => self.handle_request_sense(external_data, request, false),
729            ScsiOp::MODE_SENSE | ScsiOp::MODE_SENSE10 => {
730                self.handle_mode_sense(external_data, request)
731            }
732            ScsiOp::TEST_UNIT_READY
733            | ScsiOp::FORMAT_UNIT
734            | ScsiOp::RESERVE_UNIT
735            | ScsiOp::RELEASE_UNIT
736            | ScsiOp::MEDIUM_REMOVAL => Ok(0),
737            ScsiOp::SEND_DIAGNOSTIC => self.handle_send_diagnostic_validation(request),
738            ScsiOp::READ_CAPACITY => self.handle_read_capacity(external_data, sector_count),
739            // It's SCSIOP_READ_CAPACITY16 in vhdmp
740            ScsiOp::SERVICE_ACTION_IN16 => {
741                self.handle_service_action_in16(external_data, request, sector_count)
742            }
743            ScsiOp::MODE_SELECT | ScsiOp::MODE_SELECT10 => {
744                self.handle_mode_select(external_data, request)
745            }
746            ScsiOp::VERIFY
747            | ScsiOp::VERIFY12
748            | ScsiOp::VERIFY16
749            | ScsiOp::WRITE_VERIFY
750            | ScsiOp::WRITE_VERIFY12
751            | ScsiOp::WRITE_VERIFY16 => self.handle_verify_validation(request, sector_count),
752            _ => {
753                tracing::debug!(?op, "illegal command");
754                Err(ScsiError::IllegalRequest(
755                    AdditionalSenseCode::ILLEGAL_COMMAND,
756                ))
757            }
758        }
759    }
760
761    fn validate_data_cdb(
762        &self,
763        external_data: &RequestBuffers<'_>,
764        request: &Request,
765        sector_count: u64,
766    ) -> Result<RequestParameters, ScsiError> {
767        let cdb = scsi::Cdb10::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
768        if cdb.flags.relative_address() {
769            tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
770            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
771        }
772        let len = cdb.transfer_blocks.get() as u64;
773        let offset = cdb.logical_block.get() as u64;
774        let sector_shift = self.sector_shift;
775        let max = external_data.len() >> sector_shift;
776        if len == 0 || len as usize > max {
777            tracelimit::error_ratelimited!(len, max, "illegal block");
778            return Err(ScsiError::IllegalRequest(
779                AdditionalSenseCode::ILLEGAL_BLOCK,
780            ));
781        }
782
783        if sector_count <= offset || sector_count - offset < len {
784            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
785            return Err(ScsiError::IllegalRequest(
786                AdditionalSenseCode::ILLEGAL_BLOCK,
787            ));
788        }
789
790        let fua = cdb.flags.fua();
791        let tx = (len as usize) << sector_shift;
792        Ok(RequestParameters { tx, offset, fua })
793    }
794
795    fn validate_data_cdb6_read_write(
796        &self,
797        external_data: &RequestBuffers<'_>,
798        request: &Request,
799        sector_count: u64,
800    ) -> Result<RequestParameters, ScsiError> {
801        let cdb = scsi::Cdb6ReadWrite::read_from_prefix(&request.cdb[..])
802            .unwrap()
803            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
804        let len = cdb.transfer_blocks as u64;
805        let offset = u32::from_be_bytes([
806            0,
807            cdb.logical_block[0],
808            cdb.logical_block[1],
809            cdb.logical_block[2],
810        ]) as u64;
811        let sector_shift = self.sector_shift;
812        let max = external_data.len() >> sector_shift;
813        if len == 0 || len as usize > max {
814            tracelimit::error_ratelimited!(len, max, "illegal block");
815            return Err(ScsiError::IllegalRequest(
816                AdditionalSenseCode::ILLEGAL_BLOCK,
817            ));
818        }
819
820        if sector_count <= offset || sector_count - offset < len {
821            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
822            return Err(ScsiError::IllegalRequest(
823                AdditionalSenseCode::ILLEGAL_BLOCK,
824            ));
825        }
826
827        let tx = (len as usize) << sector_shift;
828        Ok(RequestParameters {
829            tx,
830            offset,
831            fua: false,
832        })
833    }
834
835    fn validate_data_cdb12(
836        &self,
837        external_data: &RequestBuffers<'_>,
838        request: &Request,
839        sector_count: u64,
840    ) -> Result<RequestParameters, ScsiError> {
841        let cdb = scsi::Cdb12::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
842        if cdb.flags.relative_address() {
843            tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
844            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
845        }
846        let len = cdb.transfer_blocks.get() as u64;
847        let offset = cdb.logical_block.get() as u64;
848        let max = external_data.len() >> self.sector_shift;
849        if len == 0 || len as usize > max {
850            tracelimit::error_ratelimited!(len, max, "illegal block");
851            return Err(ScsiError::IllegalRequest(
852                AdditionalSenseCode::ILLEGAL_BLOCK,
853            ));
854        }
855
856        if sector_count <= offset || sector_count - offset < len {
857            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
858            return Err(ScsiError::IllegalRequest(
859                AdditionalSenseCode::ILLEGAL_BLOCK,
860            ));
861        }
862
863        let fua = cdb.flags.fua();
864        let tx = (len as usize) << self.sector_shift;
865        Ok(RequestParameters { tx, offset, fua })
866    }
867
868    fn validate_data_cdb16(
869        &self,
870        external_data: &RequestBuffers<'_>,
871        request: &Request,
872        sector_count: u64,
873    ) -> Result<RequestParameters, ScsiError> {
874        let cdb = scsi::Cdb16::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
875        let len = cdb.transfer_blocks.get() as u64;
876        let offset = cdb.logical_block.get();
877        let sector_shift = self.sector_shift;
878        let max = external_data.len() >> sector_shift;
879        if len == 0 || len as usize > max {
880            tracelimit::error_ratelimited!(len, max, "illegal block");
881            return Err(ScsiError::IllegalRequest(
882                AdditionalSenseCode::ILLEGAL_BLOCK,
883            ));
884        }
885
886        if sector_count <= offset || sector_count - offset < len {
887            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
888            return Err(ScsiError::IllegalRequest(
889                AdditionalSenseCode::ILLEGAL_BLOCK,
890            ));
891        }
892
893        let fua = cdb.flags.fua();
894        let tx = (len as usize) << sector_shift;
895        Ok(RequestParameters { tx, offset, fua })
896    }
897
898    fn validate_write_same(
899        &self,
900        external_data: &RequestBuffers<'_>,
901        request: &Request,
902        sector_count: u64,
903    ) -> Result<WriteSameParameters, ScsiError> {
904        let op = request.scsiop();
905        let mut p = match op {
906            ScsiOp::WRITE_SAME => {
907                let cdb = scsi::Cdb10::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
908                if cdb.flags.relative_address() {
909                    tracing::debug!(flags = ?cdb.flags, "doesn't support relative address");
910                    return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
911                }
912                WriteSameParameters {
913                    start_lba: cdb.logical_block.get() as u64,
914                    lba_count: cdb.transfer_blocks.get() as usize,
915                    fua: cdb.flags.fua(),
916                    sector_size: 0,
917                    tx: 0,
918                }
919            }
920            ScsiOp::WRITE_SAME16 => {
921                let cdb = scsi::Cdb16::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
922                WriteSameParameters {
923                    start_lba: cdb.logical_block.get(),
924                    lba_count: cdb.transfer_blocks.get() as usize,
925                    fua: cdb.flags.fua(),
926                    sector_size: 0,
927                    tx: 0,
928                }
929            }
930            _ => unreachable!(),
931        };
932
933        if !validate_lba_range(sector_count, p.start_lba, p.lba_count.try_into().unwrap()) {
934            //valiate_lba_range trace errors
935            return Err(ScsiError::IllegalRequest(
936                AdditionalSenseCode::ILLEGAL_BLOCK,
937            ));
938        }
939
940        if self.disk.is_read_only() {
941            return Err(ScsiError::WriteProtected);
942        }
943
944        // max length check
945        p.tx = p.lba_count << self.sector_shift;
946        if p.tx > VHDMP_MAX_WRITE_SAME_LENGTH_BYTES.try_into().unwrap()
947            || p.tx > self.scsi_parameters.maximum_transfer_length
948        {
949            tracelimit::error_ratelimited!(p.tx, "transfer length too big");
950            return Err(ScsiError::IllegalRequest(
951                AdditionalSenseCode::ILLEGAL_BLOCK,
952            ));
953        }
954
955        // The size of the supplied data buffer must be at least one sector.
956        p.sector_size = self.sector_size.try_into().unwrap();
957        let external_data_len = external_data.len();
958        if p.lba_count > 0 && external_data_len < p.sector_size {
959            tracelimit::error_ratelimited!(external_data_len, "provided transfer length too small");
960            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
961        }
962
963        Ok(p)
964    }
965
966    fn process_result(&self, result: Result<usize, ScsiError>, op: ScsiOp) -> ScsiResult {
967        let result = result.map_err(|err| {
968            match err {
969                ScsiError::UnsupportedModePageCode(..)
970                | ScsiError::UnsupportedServiceAction(_)
971                | ScsiError::UnsupportedVpdPageCode(_) => tracing::debug!(disk = ?self.scsi_parameters.disk_id, error = err.as_error(), ?op, "scsi_error"),
972                | ScsiError::IllegalRequest(_) => tracing::debug!(disk = ?self.scsi_parameters.disk_id, error = err.as_error(), ?op, "scsi_error"),
973                _ => tracelimit::warn_ratelimited!(disk = ?self.scsi_parameters.disk_id, error = err.as_error(), ?op, "scsi_error"),
974            }
975            err
976        });
977
978        let result = match result {
979            Ok(tx) => ScsiResult {
980                scsi_status: ScsiStatus::GOOD,
981                srb_status: SrbStatus::SUCCESS,
982                tx,
983                sense_data: None,
984            },
985            Err(err) => {
986                match err {
987                    ScsiError::MemoryAccess(_)
988                    | ScsiError::UnsupportedModePageCode(..)
989                    | ScsiError::UnsupportedServiceAction(_)
990                    | ScsiError::UnsupportedVpdPageCode(_)
991                    | ScsiError::SrbError
992                    | ScsiError::Disk(DiskError::InvalidInput)
993                    | ScsiError::Disk(DiskError::MemoryAccess(_)) => ScsiResult {
994                        scsi_status: ScsiStatus::CHECK_CONDITION,
995                        srb_status: SrbStatus::INVALID_REQUEST,
996                        tx: 0,
997                        sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
998                    },
999                    ScsiError::IllegalRequest(sense_code) => ScsiResult {
1000                        scsi_status: ScsiStatus::CHECK_CONDITION,
1001                        srb_status: SrbStatus::INVALID_REQUEST,
1002                        tx: 0,
1003                        sense_data: Some(illegal_request_sense(sense_code)),
1004                    },
1005                    ScsiError::DataOverrun => ScsiResult {
1006                        scsi_status: ScsiStatus::CHECK_CONDITION,
1007                        srb_status: SrbStatus::DATA_OVERRUN,
1008                        tx: 0,
1009                        sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
1010                    },
1011                    ScsiError::UnitAttention => ScsiResult {
1012                        scsi_status: ScsiStatus::CHECK_CONDITION,
1013                        srb_status: SrbStatus::ERROR,
1014                        tx: 0,
1015                        sense_data: Some(scsi::SenseData::new(
1016                            SenseKey::UNIT_ATTENTION,
1017                            AdditionalSenseCode::PARAMETERS_CHANGED,
1018                            scsi::SCSI_SENSEQ_CAPACITY_DATA_CHANGED,
1019                        )),
1020                    },
1021                    ScsiError::WriteProtected | ScsiError::Disk(DiskError::ReadOnly) => {
1022                        ScsiResult {
1023                            scsi_status: ScsiStatus::CHECK_CONDITION,
1024                            srb_status: SrbStatus::ERROR,
1025                            tx: 0,
1026                            sense_data: Some(scsi::SenseData::new(
1027                                SenseKey::DATA_PROTECT,
1028                                AdditionalSenseCode::WRITE_PROTECT,
1029                                0,
1030                            )),
1031                        }
1032                    }
1033                    ScsiError::Disk(err) => {
1034                        match err {
1035                            DiskError::AbortDueToPreemptAndAbort => ScsiResult {
1036                                scsi_status: ScsiStatus::TASK_ABORTED,
1037                                srb_status: SrbStatus::ABORTED,
1038                                tx: 0,
1039                                sense_data: Some(scsi::SenseData::new(
1040                                    SenseKey::ABORTED_COMMAND,
1041                                    AdditionalSenseCode::NO_SENSE,
1042                                    0,
1043                                )),
1044                            },
1045                            DiskError::IllegalBlock => ScsiResult {
1046                                scsi_status: ScsiStatus::CHECK_CONDITION,
1047                                srb_status: SrbStatus::ERROR,
1048                                tx: 0,
1049                                sense_data: Some(scsi::SenseData::new(
1050                                    SenseKey::ILLEGAL_REQUEST,
1051                                    AdditionalSenseCode::ILLEGAL_BLOCK,
1052                                    0,
1053                                )),
1054                            },
1055                            DiskError::Io(_) => ScsiResult {
1056                                scsi_status: ScsiStatus::CHECK_CONDITION,
1057                                srb_status: SrbStatus::ERROR,
1058                                tx: 0,
1059                                sense_data: Some(scsi::SenseData::new(
1060                                    SenseKey::MEDIUM_ERROR,
1061                                    AdditionalSenseCode::NO_SENSE,
1062                                    0,
1063                                )),
1064                            },
1065                            DiskError::MediumError(_, details) => {
1066                                let (sense_code, qualifier) = match details {
1067                                    disk_backend::MediumErrorDetails::ApplicationTagCheckFailed => {
1068                                        (
1069                                            AdditionalSenseCode::UNRECOVERED_ERROR,
1070                                            scsi::SCSI_SENSEQ_LOGICAL_BLOCK_TAG_CHECK_FAILED,
1071                                        )
1072                                    }
1073                                    disk_backend::MediumErrorDetails::GuardCheckFailed => (
1074                                        AdditionalSenseCode::CRC_OR_ECC_ERROR,
1075                                        scsi::SCSI_SENSEQ_LOGICAL_BLOCK_GUARD_CHECK_FAILED,
1076                                    ),
1077                                    disk_backend::MediumErrorDetails::ReferenceTagCheckFailed => (
1078                                        AdditionalSenseCode::CRC_OR_ECC_ERROR,
1079                                        scsi::SCSI_SENSEQ_LOGICAL_BLOCK_REF_TAG_CHECK_FAILED,
1080                                    ),
1081                                    disk_backend::MediumErrorDetails::UnrecoveredReadError => {
1082                                        (AdditionalSenseCode::UNRECOVERED_ERROR, 0)
1083                                    }
1084                                    disk_backend::MediumErrorDetails::WriteFault => {
1085                                        (AdditionalSenseCode::WRITE, 0)
1086                                    }
1087                                };
1088                                ScsiResult {
1089                                    scsi_status: ScsiStatus::CHECK_CONDITION,
1090                                    srb_status: SrbStatus::ERROR,
1091                                    tx: 0,
1092                                    sense_data: Some(scsi::SenseData::new(
1093                                        SenseKey::MEDIUM_ERROR,
1094                                        sense_code,
1095                                        qualifier,
1096                                    )),
1097                                }
1098                            }
1099                            DiskError::ReservationConflict => ScsiResult {
1100                                scsi_status: ScsiStatus::RESERVATION_CONFLICT,
1101                                srb_status: SrbStatus::ERROR,
1102                                tx: 0,
1103                                sense_data: None,
1104                            },
1105                            DiskError::UnsupportedEject => ScsiResult {
1106                                scsi_status: ScsiStatus::CHECK_CONDITION,
1107                                srb_status: SrbStatus::INVALID_REQUEST,
1108                                tx: 0,
1109                                sense_data: Some(illegal_request_sense(
1110                                    AdditionalSenseCode::ILLEGAL_COMMAND,
1111                                )),
1112                            },
1113                            DiskError::InvalidInput
1114                            | DiskError::MemoryAccess(_)
1115                            | DiskError::ReadOnly => unreachable!(), //handled above
1116                        }
1117                    }
1118                }
1119            }
1120        };
1121
1122        self.sense_data.set(result.sense_data.as_ref());
1123        if op == ScsiOp::PERSISTENT_RESERVE_OUT && result.scsi_status != ScsiStatus::GOOD {
1124            tracing::warn!(scsi_result = ?result, "PERSISTENT_RESERVE_OUT failed.");
1125        } else {
1126            tracing::trace!(scsi_result = ?result, ?op, "process_result completed.");
1127        }
1128
1129        result
1130    }
1131
1132    /// Gets the current sector count from the underlying disk.
1133    ///
1134    /// If the sector count has changed since the last call, returns an error so
1135    /// that the caller can propagate unit attention to the guest.
1136    ///
1137    /// For `INQUIRY`, returns the new sector count without error but does not
1138    /// update the last observed one.
1139    fn get_and_update_sector_count(&self, op: ScsiOp) -> Result<u64, u64> {
1140        let current = self.last_sector_count.load(Ordering::Relaxed);
1141        let sector_count = self.disk.sector_count();
1142        // Don't process sector count updates during inquiry (but do report the new sector size).
1143        if sector_count == current || op == ScsiOp::INQUIRY {
1144            return Ok(sector_count);
1145        }
1146        tracing::info!(
1147            sector_count,
1148            old_sector_count = current,
1149            "updating sector count"
1150        );
1151        if self
1152            .last_sector_count
1153            .compare_exchange(current, sector_count, Ordering::SeqCst, Ordering::SeqCst)
1154            .is_err()
1155        {
1156            // Another request already handled the unit attention.
1157            return Ok(sector_count);
1158        }
1159        Err(sector_count)
1160    }
1161}
1162
1163impl SimpleScsiDisk {
1164    async fn handle_data_cdb(
1165        &self,
1166        external_data: &RequestBuffers<'_>,
1167        request: &Request,
1168        sector_count: u64,
1169    ) -> Result<usize, ScsiError> {
1170        let op = request.scsiop();
1171        let is_read;
1172        let p = match op {
1173            ScsiOp::READ | ScsiOp::WRITE => {
1174                is_read = op == ScsiOp::READ;
1175                self.validate_data_cdb(external_data, request, sector_count)?
1176            }
1177            ScsiOp::READ6 | ScsiOp::WRITE6 => {
1178                is_read = op == ScsiOp::READ6;
1179                self.validate_data_cdb6_read_write(external_data, request, sector_count)?
1180            }
1181            ScsiOp::READ12 | ScsiOp::WRITE12 => {
1182                is_read = op == ScsiOp::READ12;
1183                self.validate_data_cdb12(external_data, request, sector_count)?
1184            }
1185            ScsiOp::READ16 | ScsiOp::WRITE16 => {
1186                is_read = op == ScsiOp::READ16;
1187                self.validate_data_cdb16(external_data, request, sector_count)?
1188            }
1189            _ => unreachable!(),
1190        };
1191
1192        // Note that `p.tx` is validated above to be in range.
1193        let external_data = external_data.subrange(0, p.tx);
1194
1195        Ok(if is_read {
1196            self.disk
1197                .read_vectored(&external_data, p.offset)
1198                .await
1199                .map_err(ScsiError::Disk)?;
1200
1201            p.tx
1202        } else {
1203            if self.disk.is_read_only() {
1204                return Err(ScsiError::WriteProtected);
1205            }
1206
1207            self.disk
1208                .write_vectored(&external_data, p.offset, p.fua)
1209                .await
1210                .map_err(ScsiError::Disk)?;
1211
1212            p.tx
1213        })
1214    }
1215
1216    async fn handle_synchronize_cache(&self) -> Result<usize, ScsiError> {
1217        self.disk.sync_cache().await.map_err(ScsiError::Disk)?;
1218        Ok(0)
1219    }
1220
1221    async fn handle_write_same(
1222        &self,
1223        external_data: &RequestBuffers<'_>,
1224        request: &Request,
1225        sector_count: u64,
1226    ) -> Result<usize, ScsiError> {
1227        let p = self.validate_write_same(external_data, request, sector_count)?;
1228        if p.tx > 0 {
1229            // Note that `p.sector_size` is validated above to be in range.
1230            let external_data = external_data.subrange(0, p.sector_size);
1231            // TODO: pass this request through to the disk rather than looping like this.
1232            for offset in p.start_lba..p.start_lba + (p.lba_count as u64) {
1233                self.disk
1234                    .write_vectored(&external_data, offset, p.fua)
1235                    .await
1236                    .map_err(ScsiError::Disk)?;
1237            }
1238        }
1239
1240        Ok(p.tx)
1241    }
1242
1243    async fn handle_start_stop(&self, request: &Request) -> Result<usize, ScsiError> {
1244        let cdb = scsi::StartStop::read_from_prefix(&request.cdb[..])
1245            .unwrap()
1246            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1247        if cdb.immediate & scsi::IMMEDIATE_BIT != 0 {
1248            tracing::debug!("immediate bit is not supported");
1249            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
1250        }
1251
1252        if cdb.flag.start() {
1253            return Ok(0);
1254        };
1255
1256        self.disk.sync_cache().await.map_err(ScsiError::Disk)?;
1257        Ok(0)
1258    }
1259}
1260
1261impl AsyncScsiDisk for SimpleScsiDisk {
1262    fn execute_scsi<'a>(
1263        &'a self,
1264        external_data: &'a RequestBuffers<'a>,
1265        request: &'a Request,
1266    ) -> StackFuture<'a, ScsiResult, { ASYNC_SCSI_DISK_STACK_SIZE }> {
1267        StackFuture::from(async move {
1268            let op = request.scsiop();
1269
1270            let sector_count = match self.get_and_update_sector_count(op) {
1271                Ok(c) => c,
1272                Err(_) => {
1273                    // The sector count has changed. Report unit attention.
1274                    let result = match op {
1275                        ScsiOp::REQUEST_SENSE => {
1276                            self.handle_request_sense(external_data, request, true)
1277                        }
1278                        _ => Err(ScsiError::UnitAttention),
1279                    };
1280                    return self.process_result(result, op);
1281                }
1282            };
1283
1284            let result = match op {
1285                ScsiOp::WRITE
1286                | ScsiOp::WRITE6
1287                | ScsiOp::WRITE12
1288                | ScsiOp::WRITE16
1289                | ScsiOp::READ
1290                | ScsiOp::READ6
1291                | ScsiOp::READ12
1292                | ScsiOp::READ16 => {
1293                    self.handle_data_cdb(external_data, request, sector_count)
1294                        .instrument(tracing::trace_span!("handle_data_cdb_async", ?op,))
1295                        .await
1296                }
1297                ScsiOp::WRITE_SAME | ScsiOp::WRITE_SAME16 => {
1298                    self.handle_write_same(external_data, request, sector_count)
1299                        .instrument(tracing::trace_span!("handle_write_same_async"))
1300                        .await
1301                }
1302                ScsiOp::SYNCHRONIZE_CACHE | ScsiOp::SYNCHRONIZE_CACHE16 => {
1303                    self.handle_synchronize_cache()
1304                        .instrument(tracing::trace_span!("handle_synchronize_cache_async", ?op,))
1305                        .await
1306                }
1307                ScsiOp::START_STOP_UNIT => {
1308                    self.handle_start_stop(request)
1309                        .instrument(tracing::trace_span!("handle_start_stop_async",))
1310                        .await
1311                }
1312                ScsiOp::UNMAP => {
1313                    self.handle_unmap(external_data, request, sector_count)
1314                        .instrument(tracing::debug_span!("handle_unmap_async"))
1315                        .await
1316                }
1317                ScsiOp::PERSISTENT_RESERVE_IN | ScsiOp::PERSISTENT_RESERVE_OUT => {
1318                    self.handle_persistent_reserve(external_data, request)
1319                        .instrument(tracing::trace_span!("handle_persistent_reserve_async", ?op,))
1320                        .await
1321                }
1322                _ => {
1323                    let _span = tracing::trace_span!("handle_control_cdb", ?op,).entered();
1324                    self.handle_control_cdb(external_data, request, sector_count)
1325                }
1326            };
1327
1328            self.process_result(result, op)
1329        })
1330    }
1331}
1332
1333impl Inspect for SimpleScsiDisk {
1334    fn inspect(&self, req: inspect::Request<'_>) {
1335        let mut resp = req.respond();
1336        resp.binary("disk_id", self.scsi_parameters.disk_id)
1337            .field("logical_sector_size", self.sector_size)
1338            .field(
1339                "physical_sector_size",
1340                1usize << self.sector_shift << self.physical_extra_shift,
1341            )
1342            .field(
1343                "sector_count",
1344                self.last_sector_count.load(Ordering::Relaxed),
1345            )
1346            .field("scsi_parameters", &self.scsi_parameters)
1347            .field("pr", self.support_pr)
1348            .field("backend", &self.disk);
1349    }
1350}
1351
1352#[derive(Default, Debug)]
1353struct SenseDataSlot {
1354    is_valid: AtomicBool,
1355    data: Mutex<Option<scsi::SenseData>>,
1356}
1357
1358impl SenseDataSlot {
1359    /// Updates sense data.
1360    fn set(&self, sense_data: Option<&scsi::SenseData>) {
1361        match sense_data {
1362            None => {
1363                // Only clear sense data if it is set to avoid taking the cache
1364                // line exclusive in the common case.
1365                //
1366                // Access with relaxed ordering because sense data state is not
1367                // well defined if there are multiple concurrent IOs anyway.
1368                if self.is_valid.load(Ordering::Relaxed) {
1369                    self.is_valid.store(false, Ordering::Relaxed)
1370                }
1371            }
1372            Some(sense_data) => {
1373                *self.data.lock() = Some(*sense_data);
1374                self.is_valid.store(true, Ordering::Release);
1375            }
1376        }
1377    }
1378
1379    /// Gets sense data without clearing it.
1380    fn get(&self) -> Option<scsi::SenseData> {
1381        if self.is_valid.load(Ordering::Relaxed) {
1382            // Note that this might still be None due to race conditions with
1383            // multiple concurrent IOs.
1384            *self.data.lock()
1385        } else {
1386            None
1387        }
1388    }
1389
1390    /// Gets and clears sense data.
1391    pub(crate) fn take(&self) -> Option<scsi::SenseData> {
1392        if self.is_valid.swap(false, Ordering::Acquire) {
1393            // Note that this might still be None due to race conditions with
1394            // multiple concurrent IOs.
1395            self.data.lock().take()
1396        } else {
1397            None
1398        }
1399    }
1400}