scsidisk/
inquiry.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use super::ScsiError;
5use super::SimpleScsiDisk;
6use crate::UNMAP_RANGE_DESCRIPTOR_COUNT_MAX;
7use crate::VHDMP_MAX_WRITE_SAME_LENGTH_BYTES;
8use crate::scsi;
9use guestmem::MemoryWrite;
10use guid::Guid;
11use scsi::AdditionalSenseCode;
12use scsi_buffers::RequestBuffers;
13use scsi_core::Request;
14use zerocopy::FromBytes;
15use zerocopy::FromZeros;
16use zerocopy::Immutable;
17use zerocopy::IntoBytes;
18use zerocopy::KnownLayout;
19
20type U16BE = zerocopy::byteorder::U16<zerocopy::byteorder::BigEndian>;
21type U32BE = zerocopy::byteorder::U32<zerocopy::byteorder::BigEndian>;
22type U64BE = zerocopy::byteorder::U64<zerocopy::byteorder::BigEndian>;
23
24#[repr(C)]
25#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
26struct VhdmpVpdWindowsBlockDeviceRodLimitsEcopDescriptorHeader {
27    pub ecop_descriptor_type: U16BE,
28    pub ecop_descriptor_length: U16BE,
29}
30
31#[repr(C)]
32#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
33struct VhdmpVpdWindowsBlockDeviceRodLimitsEcopDescriptor {
34    pub header: VhdmpVpdWindowsBlockDeviceRodLimitsEcopDescriptorHeader,
35    pub microsoft_signature: u8,                  // 0x4D --> 'M'
36    pub microsoft_command_id_and_versionsion: u8, // 0x1F
37    pub populate_token_and_write_using_token_command_op_code: u8, // 0x83
38    pub receive_rod_token_information_command_op_code: u8, // 0x84
39    pub reserved1: [u8; 2],
40    pub maximum_range_descriptors: U16BE,
41    pub maximum_inactivity_timer: U32BE,
42    pub default_inactivity_timer: U32BE,
43    pub maximum_token_transfer_size: U64BE,
44    pub optimal_transfer_count: U64BE,
45}
46
47pub const INQUIRY_DATA_TEMPLATE: scsi::InquiryData = scsi::InquiryData {
48    header: scsi::InquiryDataHeader {
49        // READ_ONLY_DIRECT_ACCESS_DEVICE is interpreted as CD-ROM, so despite
50        // the fact that this is a read-only surface, don't report
51        // READ_ONLY_DIRECT_ACCESS_DEVICE.
52        device_type: scsi::DIRECT_ACCESS_DEVICE,
53        // This is a virtual hard drive, so the media is never removable from
54        // the device.  The device itself may be removable, depending on whether
55        // the device is the boot drive or not.  The device itself may be hot
56        // removable or not, depending on whether any of the backing stores are
57        // hot removable.
58        flags2: scsi::InquiryDataFlag2::new()
59            .with_device_type_modifier(0x00)
60            .with_removable_media(false),
61        versions: scsi::T10_VERSION_SPC3,
62        flags3: scsi::InquiryDataFlag3::new()
63            .with_response_data_format(scsi::T10_RESPONSE_DATA_SPC3)
64            .with_aerc(false)
65            .with_hi_support(false)
66            .with_norm_aca(false)
67            .with_reserved_bit(false),
68        additional_length: (size_of::<scsi::InquiryData>() - size_of::<scsi::InquiryDataHeader>())
69            as u8,
70    },
71    reserved: [0; 2],
72    misc: 0,
73    vendor_id: *b"Msft    ",
74    product_id: *b"Virtual Disk    ",
75    product_revision_level: *b"1.0 ",
76    vendor_specific: [0; 20],
77    reserved3: [0; 2],
78    version_descriptors: [0; 8],
79    reserved4: [0; 30],
80};
81
82/// Writes a VPD page.
83///
84/// Assumes that allocation_length is already validated to be at least
85/// `size_of::<scsi::VpdPageHeader>()`.
86fn write_vpd_page<T: ?Sized + IntoBytes + Immutable + KnownLayout>(
87    external_data: &RequestBuffers<'_>,
88    allocation_length: usize,
89    page_code: u8,
90    page_data: &T,
91) -> Result<usize, ScsiError> {
92    let header = scsi::VpdPageHeader {
93        device_type: scsi::DIRECT_ACCESS_DEVICE,
94        page_code,
95        reserved: 0,
96        page_length: size_of_val(page_data).try_into().unwrap(),
97    };
98
99    let tx = std::cmp::min(
100        allocation_length,
101        size_of_val(&header) + size_of_val(page_data),
102    );
103
104    let mut writer = external_data.writer();
105    writer
106        .write(header.as_bytes())
107        .map_err(ScsiError::MemoryAccess)?;
108
109    writer
110        .write(&page_data.as_bytes()[..tx - size_of_val(&header)])
111        .map_err(ScsiError::MemoryAccess)?;
112
113    Ok(tx)
114}
115
116impl SimpleScsiDisk {
117    fn handle_vpd_supported_pages(
118        &self,
119        external_data: &RequestBuffers<'_>,
120        allocation_length: usize,
121    ) -> Result<usize, ScsiError> {
122        let mut supported_pages = vec![
123            scsi::VPD_SUPPORTED_PAGES,                // support this page
124            scsi::VPD_DEVICE_IDENTIFIERS,             // support identifiers page
125            scsi::VPD_MSFT_PAGING_EXTENT_PROPERTIES,  // Microsoft Paging Extent Properties
126            scsi::VPD_MSFT_VIRTUAL_DEVICE_PROPERTIES, // Microsoft Virtual Device Properties
127        ];
128
129        if self.scsi_parameters.support_odx {
130            supported_pages.push(scsi::VPD_THIRD_PARTY_COPY);
131        }
132
133        if self.scsi_parameters.support_unmap {
134            supported_pages.extend([
135                scsi::VPD_BLOCK_DEVICE_CHARACTERISTICS,
136                scsi::VPD_BLOCK_LIMITS,
137                scsi::VPD_LOGICAL_BLOCK_PROVISIONING,
138            ]);
139        }
140
141        if !self.scsi_parameters.serial_number.is_empty() {
142            supported_pages.push(scsi::VPD_SERIAL_NUMBER);
143        }
144
145        supported_pages.sort_unstable();
146
147        write_vpd_page(
148            external_data,
149            allocation_length,
150            scsi::VPD_SUPPORTED_PAGES,
151            supported_pages.as_slice(),
152        )
153    }
154
155    fn handle_vpd_serial_number(
156        &self,
157        external_data: &RequestBuffers<'_>,
158        allocation_length: usize,
159    ) -> Result<usize, ScsiError> {
160        write_vpd_page(
161            external_data,
162            allocation_length,
163            scsi::VPD_SERIAL_NUMBER,
164            self.scsi_parameters.serial_number.as_slice(),
165        )
166    }
167
168    fn handle_vpd_device_identifiers(
169        &self,
170        external_data: &RequestBuffers<'_>,
171        allocation_length: usize,
172    ) -> Result<usize, ScsiError> {
173        #[repr(C)]
174        #[derive(IntoBytes, Immutable, KnownLayout)]
175        struct Ids {
176            t10_id: scsi::VpdT10Id,
177            // NAA ID page - need this for compliance
178            // as we move from BusTypeScsi to BusTypeSas.
179            naa_id: scsi::VpdNaaId,
180        }
181
182        // Construct a full local copy and transfer as much as possible.
183        // Start with the template and initialize the T10 and NAA
184        // Ids appropriately.
185        let mut page = Ids {
186            t10_id: scsi::VpdT10Id {
187                header: scsi::VpdIdentificationDescriptor {
188                    code_set: scsi::VPD_CODE_SET_BINARY,
189                    identifiertype: scsi::VPD_IDENTIFIER_TYPE_VENDOR_ID, //VpdAssocDevice = 0
190                    reserved3: 0x00,
191                    identifier_length: (size_of::<scsi::VpdT10Id>()
192                        - size_of::<scsi::VpdIdentificationDescriptor>())
193                        as u8,
194                },
195                vendor_id: self.scsi_parameters.identity.vendor_id.into(),
196                context_guid: self.scsi_parameters.disk_id,
197            },
198            naa_id: scsi::VpdNaaId {
199                header: scsi::VpdIdentificationDescriptor {
200                    code_set: scsi::VPD_CODE_SET_BINARY,
201                    identifiertype: scsi::VPD_IDENTIFIER_TYPE_FCPH_NAME, //VpdAssocDevice = 0
202                    reserved3: 0x00,
203                    identifier_length: (size_of::<scsi::VpdNaaId>()
204                        - size_of::<scsi::VpdIdentificationDescriptor>())
205                        as u8,
206                },
207                ouid_msb: 0x60, // 6(NAA), 0 (OuidMSB MSFT OUID used = 00-22-48 (hex))
208                ouid_middle: [0x02, 0x24],
209                ouid_lsb: 0x80,
210                vendor_specific_id: [0; 12],
211            },
212        };
213
214        // Best effort uniqueness:
215        // Where possible we use version 4 UUID for the T10Id guid,
216        // which has the format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
217        // where x is any hexadecimal digit and y is one of 8, 9, a or b
218        //
219        // Use the first and last 6 bytes of the T10Id's ContextGuid
220        // which are the most random bytes.
221        let id_split_size = page.naa_id.vendor_specific_id.len() / 2;
222        page.naa_id.vendor_specific_id[..id_split_size]
223            .copy_from_slice(&self.scsi_parameters.disk_id.as_bytes()[..id_split_size]);
224        page.naa_id.vendor_specific_id[id_split_size..]
225            .copy_from_slice(&page.t10_id.context_guid[10..]);
226
227        write_vpd_page(
228            external_data,
229            allocation_length,
230            scsi::VPD_DEVICE_IDENTIFIERS,
231            &page,
232        )
233    }
234
235    fn handle_vpd_block_limits(
236        &self,
237        external_data: &RequestBuffers<'_>,
238        allocation_length: usize,
239    ) -> Result<usize, ScsiError> {
240        let max_write_same_length = (self.scsi_parameters.maximum_transfer_length as u64)
241            .min(VHDMP_MAX_WRITE_SAME_LENGTH_BYTES)
242            >> self.sector_shift;
243
244        // Since we are only here if unmap is supported, ensure the reported
245        // granularity is non-zero (or the guest will think that unmap is not
246        // supported).
247        let optimal_unmap_granularity = self.scsi_parameters.optimal_unmap_sectors.max(1);
248
249        let page = scsi::VpdBlockLimitsDescriptor {
250            reserved0: 0x00,
251            max_compare_and_write_length: 0, // don't support compare_and_write
252            max_unmap_lba_count: u32::MAX.into(),
253            max_unmap_block_descriptor_count: u32::from(UNMAP_RANGE_DESCRIPTOR_COUNT_MAX).into(),
254            optimal_unmap_granularity: optimal_unmap_granularity.into(),
255            unmap_granularity_alignment: [0x80, 0x00, 0x00, 0x00], // UGAValid = 1
256            max_write_same_length: max_write_same_length.into(),
257            ..FromZeros::new_zeroed()
258        };
259
260        tracing::debug!(
261            max_lba_count = page.max_unmap_lba_count.get(), // TODO: didn't update?
262            max_block_descriptor_count = page.max_unmap_block_descriptor_count.get(),
263            optimal_unmap_granularity,
264            "handle_vpd_block_limits unmap properties",
265        );
266
267        write_vpd_page(
268            external_data,
269            allocation_length,
270            scsi::VPD_BLOCK_LIMITS,
271            &page,
272        )
273    }
274
275    fn handle_vpd_block_device_characteristics(
276        &self,
277        external_data: &RequestBuffers<'_>,
278        allocation_length: usize,
279    ) -> Result<usize, ScsiError> {
280        let page = scsi::VpdBlockDeviceCharacteristicsPage {
281            medium_rotation_rate: self.scsi_parameters.medium_rotation_rate.into(),
282            ..FromZeros::new_zeroed()
283        };
284
285        write_vpd_page(
286            external_data,
287            allocation_length,
288            scsi::VPD_BLOCK_DEVICE_CHARACTERISTICS,
289            &page,
290        )
291    }
292
293    fn handle_vpd_third_party_copy(
294        &self,
295        external_data: &RequestBuffers<'_>,
296        allocation_length: usize,
297    ) -> Result<usize, ScsiError> {
298        // Before increasing this value, consider what happens in the host when max Q
299        // depth of offload writes from various VMs is pending in the hosts's adapter.
300        // Say a first VM is issuing a single write, and happens to get in line behind
301        // a bunch of offload writes from other VMs.  Those offload writes can take up
302        // to 4 seconds each in theory, but because of this length limit, will tend to
303        // take less time than that.  Even so, assume a Q of 256 64MB offload writes,
304        // and assume that the offload writes correspond to physical writes in the
305        // array.  Now assume that the array can physically write at 1GB per second.
306        // This queue of 256 64MB writes represents 16GB of data to be written, which
307        // will take 16.4 seconds to write assuming no overhead.  That's already a bit
308        // too much, as it's more than the first VM's 10 second SCSI timeout for a small
309        // normal write.  The Storage QoS Filter is meant to help avoid putting all 16GB worth
310        // of writes in flight to the HW at once, but the filter can have incorrect
311        // low estimates for IO time cost sometimes.
312        //
313        // Generally, one SCSI reset in the guest is ok, but too many in quick
314        // succession can start to propagate errors up to the workload in the VM.  Also,
315        // VM SCSI reset will still actually complete all the IOs outstanding with their
316        // normal completion status, so there's no way for a bunch more offload IOs to
317        // "sneak in" ahead of the normal write that caused the timeout - that normal
318        // write will still get a chance to complete with success despite a first SCSI
319        // timeout having triggered in the guest.
320        //
321        // This value is already as big as I'm comfortable with given the current
322        // environment (as of Feb 2012), so if you want to increase it further, first
323        // make sure the previous paragraphs won't bite.
324        const VHDMP_MAX_BYTES_PER_OFFLOAD: u64 = 64 * 1024 * 1024;
325
326        // Make local copy of default page. Struct copy.
327        let page = VhdmpVpdWindowsBlockDeviceRodLimitsEcopDescriptor {
328            header: VhdmpVpdWindowsBlockDeviceRodLimitsEcopDescriptorHeader {
329                ecop_descriptor_type: 0.into(),
330                ecop_descriptor_length: ((size_of::<
331                    VhdmpVpdWindowsBlockDeviceRodLimitsEcopDescriptor,
332                >() - size_of::<
333                    VhdmpVpdWindowsBlockDeviceRodLimitsEcopDescriptorHeader,
334                >()) as u16)
335                    .into(),
336            },
337            microsoft_signature: 0x4D,                  // 0x4D --> 'M'
338            microsoft_command_id_and_versionsion: 0x1F, // 0x1F
339            populate_token_and_write_using_token_command_op_code: 0x83, // scsi::SCSIOP_EXTENDED_COPY
340            receive_rod_token_information_command_op_code: 0x84, // scsi::SCSIOP_RECEIVE_ROD_TOKEN_INFORMATION
341            reserved1: [0; 2],
342            maximum_range_descriptors: 8.into(),
343            maximum_inactivity_timer: 0.into(), //do not report, since don't know
344            default_inactivity_timer: 0.into(),
345            maximum_token_transfer_size: (VHDMP_MAX_BYTES_PER_OFFLOAD >> self.sector_shift).into(),
346            optimal_transfer_count: (VHDMP_MAX_BYTES_PER_OFFLOAD >> self.sector_shift).into(),
347        };
348
349        write_vpd_page(
350            external_data,
351            allocation_length,
352            scsi::VPD_THIRD_PARTY_COPY,
353            &page,
354        )
355    }
356
357    fn handle_vpd_msft_virtual_device_properties(
358        &self,
359        external_data: &RequestBuffers<'_>,
360        allocation_length: usize,
361    ) -> Result<usize, ScsiError> {
362        const VPD_MSFT_VIRTUAL_DEVICE_PROPERTIES_PAGE_SIGNATURE: Guid =
363            guid::guid!("89a98f15-c928-4d8b-94cd-ef51faa99d33");
364
365        let page = scsi::VpdMsftVirtualDevicePropertiesPage {
366            version: 1, // Version, leave at 1 to maintain back-compatibility with downlevel guest OSes
367            flags: 0,   //LBPRZ, DisableIoRetries, Spaces
368            reserved: [0; 2],
369            signature: VPD_MSFT_VIRTUAL_DEVICE_PROPERTIES_PAGE_SIGNATURE.into(),
370        };
371
372        write_vpd_page(
373            external_data,
374            allocation_length,
375            scsi::VPD_MSFT_VIRTUAL_DEVICE_PROPERTIES,
376            &page,
377        )
378    }
379
380    fn handle_vpd_msft_paging_extent_properties(
381        &self,
382        external_data: &RequestBuffers<'_>,
383        allocation_length: usize,
384    ) -> Result<usize, ScsiError> {
385        let page = scsi::VpdMsftPagingExtentPropertiesPage {
386            version: 1,
387            mode_select_extension: 1,
388            reserved: [0; 2],
389        };
390
391        write_vpd_page(
392            external_data,
393            allocation_length,
394            scsi::VPD_MSFT_PAGING_EXTENT_PROPERTIES,
395            &page,
396        )
397    }
398
399    fn handle_vpd_logical_block_provisioning(
400        &self,
401        external_data: &RequestBuffers<'_>,
402        allocation_length: usize,
403        sector_count: u64,
404    ) -> Result<usize, ScsiError> {
405        // Build the VPD page with the proper threshold exponent value
406        // for our VHD disk size
407        //
408        // the threshold exponent is calculated according to this:
409        //     ((LastLBA + 1) / 2^(threshold exponent)) < 2^32
410        //
411        // and the exponent must be at least 1
412        let threshold_exponent = (sector_count >> 32)
413            .next_power_of_two()
414            .trailing_zeros()
415            .max(1) as u8;
416
417        let mut page = scsi::VpdLogicalBlockProvisioningPage {
418            threshold_exponent,
419            flags: 0,
420            provisioning_type: scsi::PROVISIONING_TYPE_THIN,
421            reserved2: 0,
422        };
423
424        if self.scsi_parameters.support_unmap {
425            page.flags = 0x80;
426        }
427
428        write_vpd_page(
429            external_data,
430            allocation_length,
431            scsi::VPD_LOGICAL_BLOCK_PROVISIONING,
432            &page,
433        )
434    }
435
436    fn handle_no_vpd_page(
437        &self,
438        external_data: &RequestBuffers<'_>,
439        allocation_length: usize,
440    ) -> Result<usize, ScsiError> {
441        // Determine the number of bytes to transfer. We will fail if
442        // we cannot transfer the page header, and we will not transfer
443        // more than the full page. We will allow a partial page
444        // transfer.
445        if allocation_length < size_of::<scsi::InquiryDataHeader>() {
446            return Err(ScsiError::SrbError);
447        }
448
449        let page = scsi::InquiryData {
450            misc: 0x02, // CommandQueue = 1
451            vendor_id: self.scsi_parameters.identity.vendor_id.into(),
452            product_id: self.scsi_parameters.identity.product_id.into(),
453            product_revision_level: self.scsi_parameters.identity.product_revision_level.into(),
454            ..INQUIRY_DATA_TEMPLATE
455        };
456
457        let tx = std::cmp::min(allocation_length, size_of_val(&page));
458        external_data
459            .writer()
460            .write(&page.as_bytes()[..tx])
461            .map_err(ScsiError::MemoryAccess)?;
462
463        Ok(tx)
464    }
465
466    pub(crate) fn handle_inquiry(
467        &self,
468        external_data: &RequestBuffers<'_>,
469        request: &Request,
470        sector_count: u64,
471    ) -> Result<usize, ScsiError> {
472        let cdb = scsi::CdbInquiry::read_from_prefix(&request.cdb[..])
473            .unwrap()
474            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
475
476        let allocation_length = cdb.allocation_length.get() as usize;
477        if external_data.len() < allocation_length {
478            return Err(ScsiError::SrbError);
479        }
480
481        //  We don't support the command support data bit - it's deprecated
482        if cdb.flags.csd() {
483            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
484        }
485
486        // Setting the page code without requesting product data is an error.
487        let enable_vpd = cdb.flags.vpd();
488        if cdb.page_code != 0 && !enable_vpd {
489            return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
490        }
491
492        if enable_vpd {
493            if allocation_length < size_of::<scsi::VpdPageHeader>() {
494                return Err(ScsiError::SrbError);
495            }
496
497            match cdb.page_code {
498                scsi::VPD_SUPPORTED_PAGES => {
499                    self.handle_vpd_supported_pages(external_data, allocation_length)
500                }
501                scsi::VPD_SERIAL_NUMBER if !self.scsi_parameters.serial_number.is_empty() => {
502                    self.handle_vpd_serial_number(external_data, allocation_length)
503                }
504                scsi::VPD_DEVICE_IDENTIFIERS => {
505                    self.handle_vpd_device_identifiers(external_data, allocation_length)
506                }
507                scsi::VPD_BLOCK_LIMITS if self.scsi_parameters.support_unmap => {
508                    self.handle_vpd_block_limits(external_data, allocation_length)
509                }
510                scsi::VPD_BLOCK_DEVICE_CHARACTERISTICS if self.scsi_parameters.support_unmap => {
511                    self.handle_vpd_block_device_characteristics(external_data, allocation_length)
512                }
513                scsi::VPD_LOGICAL_BLOCK_PROVISIONING if self.scsi_parameters.support_unmap => self
514                    .handle_vpd_logical_block_provisioning(
515                        external_data,
516                        allocation_length,
517                        sector_count,
518                    ),
519                scsi::VPD_THIRD_PARTY_COPY if self.scsi_parameters.support_odx => {
520                    self.handle_vpd_third_party_copy(external_data, allocation_length)
521                }
522                scsi::VPD_MSFT_VIRTUAL_DEVICE_PROPERTIES => {
523                    self.handle_vpd_msft_virtual_device_properties(external_data, allocation_length)
524                }
525                scsi::VPD_MSFT_PAGING_EXTENT_PROPERTIES => {
526                    self.handle_vpd_msft_paging_extent_properties(external_data, allocation_length)
527                }
528                n => Err(ScsiError::UnsupportedVpdPageCode(n)),
529            }
530        } else {
531            self.handle_no_vpd_page(external_data, allocation_length)
532        }
533    }
534}