scsidisk/
lib.rs

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