1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
34//! Support for the SCSI "Get LBA Status" command.
5//!
6//! Currently, this command just returns that all blocks are "mapped".
78use super::ScsiError;
9use super::SimpleScsiDisk;
10use disk_backend::Disk;
11use disk_backend::DiskError;
12use guestmem::MemoryWrite;
13use scsi::AdditionalSenseCode;
14use scsi_buffers::RequestBuffers;
15use scsi_core::Request;
16use scsi_defs as scsi;
17use zerocopy::FromBytes;
18use zerocopy::FromZeros;
19use zerocopy::IntoBytes;
2021/// Result of a get LBA status request.
22#[derive(Debug, Default, Copy, Clone)]
23struct DeviceBlockIndexInfo {
24/// The size of the first partial block.
25first_partial_block_size: u32,
26/// The index of the first full block.
27first_full_block_index: u32,
28/// The number of blocks.
29block_count: u32,
30/// The size of the last partial block.
31#[expect(dead_code)]
32last_partial_block_size: u32,
33/// The number of LBAs per block.
34lba_per_block: u64,
35}
3637/// The LBA status of a block.
38#[derive(Debug, Copy, Clone, PartialEq, Eq)]
39enum LbaStatus {
40/// The block is mapped.
41Mapped,
42/// The block is deallocated.
43#[expect(dead_code)]
44Deallocated,
45/// The block is anchored.
46#[expect(dead_code)]
47Anchored,
48}
4950/// Returns the block index information for the given file offset.
51fn file_offset_to_device_block_index_and_length(
52 disk: &Disk,
53 _start_offset: u64,
54 _get_lba_status_range_length: u64,
55 _block_size: u64,
56) -> DeviceBlockIndexInfo {
57let sector_size = disk.sector_size() as u64;
58let sector_count = disk.sector_count();
59let disk_size = sector_size * sector_count;
6061// Treat fully allocation disk or fixed disk as one large block and just return
62 // enough descriptors from the LBA requested till the last LBA on disk.
63 //
64 // LbaPerBlock is a ULONG and technically with MAXULONG * 512 byte sectors,
65 // we can get upto 1.99 TB. The LBA descriptor also holds a ULONG
66 // LogicalBlockCount and can have an issue for larger than 2TB disks.
67let lba_per_block = std::cmp::min(sector_count, u32::MAX.into());
68let block_size_large = lba_per_block * sector_size;
69let block_count = disk_size.div_ceil(block_size_large) as u32;
70 DeviceBlockIndexInfo {
71 first_partial_block_size: 0,
72 first_full_block_index: 0,
73 block_count,
74 last_partial_block_size: 0,
75 lba_per_block,
76 }
77}
7879/// Returns the LBA status for the given block number.
80fn get_block_lba_status(
81 _block_number: u32,
82 _leaf_node_state_only: bool,
83) -> Result<LbaStatus, DiskError> {
84Ok(LbaStatus::Mapped)
85}
8687impl SimpleScsiDisk {
88pub(crate) fn handle_get_lba_status(
89&self,
90 external_data: &RequestBuffers<'_>,
91 request: &Request,
92 sector_count: u64,
93 ) -> Result<usize, ScsiError> {
94let cdb = scsi::GetLbaStatus::read_from_prefix(&request.cdb[..])
95 .unwrap()
96 .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
9798 // Validate the request parameters.
99let start_lba = cdb.start_lba.get();
100if start_lba >= sector_count {
101return Err(ScsiError::IllegalRequest(
102 AdditionalSenseCode::ILLEGAL_BLOCK,
103 ));
104 }
105106let allocation_length = cdb.allocation_length.get() as usize;
107if allocation_length != external_data.len() {
108return Err(ScsiError::SrbError);
109 }
110111// A special case in the SCSI spec. Complete the command immediately.
112if allocation_length == 0 {
113return Ok(0);
114 }
115116// Must meet the minimum response size (a header and one descriptor entry).
117let min = size_of::<scsi::LbaStatusListHeader>() + size_of::<scsi::LbaStatusDescriptor>();
118if allocation_length < min {
119tracelimit::error_ratelimited!(allocation_length, min, "invalid cdb");
120return Err(ScsiError::IllegalRequest(AdditionalSenseCode::INVALID_CDB));
121 }
122123let sector_size = self.sector_size as u64;
124let disk_size = sector_size * sector_count;
125let block_size = sector_size; // TODO: get block_size from disk
126127let start_offset = start_lba << self.sector_shift;
128let get_lba_status_range_length = disk_size - start_offset;
129let total_lba_count_requested = get_lba_status_range_length / sector_size;
130131let mut lba_count_remaining = total_lba_count_requested;
132133let mut block_index_info = file_offset_to_device_block_index_and_length(
134&self.disk,
135 start_offset,
136 get_lba_status_range_length,
137 block_size,
138 );
139140let mut block_number = block_index_info.first_full_block_index;
141if block_index_info.first_partial_block_size != 0 {
142 block_number -= 1;
143 block_index_info.block_count += 1;
144 }
145146let block_number_max = block_number + block_index_info.block_count;
147148// at this point, we have
149 //
150 // BlockNumber: First block number to query LBA status (may be partial).
151 // FirstPartialBlockSize: Size of the first block to query LBA status.
152 // LastPartialBlockSize: Size of the last block to query LBA status.
153 //
154 // BlockCount: Total number of blocks to query status for, including the first
155 // and last partial blocks.
156157 // the output buffer is to look like this
158 //
159 // LBA_STATUS_LIST_HEADER
160 // LBA_STATUS_DESCRIPTOR
161 // LBA_STATUS_DESCRIPTOR
162 // ...
163 // LBA_STATUS_DESCRIPTOR
164 //
165 // Each LBA_STATUS_DESCRIPTOR describes a range of consecutive LBAs having the
166 // same LBA status. Since every LBA in a VHD block must have the same LBA status,
167 // we can always report all LBAs within the same block using exactly one
168 // LBA_STATUS_DESCRIPTOR. We need as many LBA_STATUS_DESCRIPTORs as
169 // BlockCount. We end up using fewer LBA_STATUS_DESCRIPTORs when we
170 // realize some neighboring blocks have the same block status and we can group them
171 // into the same LBA_STATUS_DESCRIPTOR.
172173 // Build additional LBA_STATUS_DESCRIPTORs and copy them to the output buffer as
174 // long as there is room and we have more block statuses to report.
175176let mut lba_descriptors_used = 0;
177178// Calculate how many descriptors are available in the buffer which does not exceed
179 // the amount expressible by the Parameter Length field.
180 // Maximum number of LBA_STATUS_DESCRIPTORs the SCSI-3 spec allows per request.
181const LBA_STATUS_DESCRIPTOR_COUNT_MAX: u32 = (u32::MAX
182 - size_of::<scsi::LbaStatusListHeader>() as u32)
183 / (size_of::<scsi::LbaStatusDescriptor>() as u32);
184let mut lba_descriptors_available = std::cmp::min(
185 LBA_STATUS_DESCRIPTOR_COUNT_MAX,
186 ((allocation_length - size_of::<scsi::LbaStatusListHeader>())
187 / size_of::<scsi::LbaStatusDescriptor>()) as u32,
188 );
189190// Get the LBA status for the very first block.
191let leaf_node_state_only =
192 request.srb_flags & scsi::SRB_FLAGS_CONSOLIDATEABLE_BLOCKS_ONLY != 0;
193let mut provisioning_status = match get_block_lba_status(block_number, leaf_node_state_only)
194 {
195Ok(status) => status,
196Err(e) => return Err(ScsiError::Disk(e)),
197 };
198199let mut provisioning_status_in_previous_block;
200let mut lba_count_in_current_block;
201let mut descriptor_lba_count;
202let mut buffer: Vec<u8> = vec![0; allocation_length];
203let mut next_lba_status_descriptor = size_of::<scsi::LbaStatusListHeader>();
204let mut next_start_lba = start_lba;
205while lba_descriptors_available != 0 && block_number < block_number_max {
206 provisioning_status_in_previous_block = provisioning_status;
207208// NextStartLba can be pointing to in the middle of this block. Thus,
209 // the LBA count in this block can be less than a full block.
210lba_count_in_current_block = std::cmp::min(
211 block_index_info.lba_per_block - next_start_lba % block_index_info.lba_per_block,
212 lba_count_remaining,
213 );
214 lba_count_remaining -= lba_count_in_current_block;
215216// Start a new accumulated LBA count for a new LBA_STATUS_DESCRIPTOR.
217descriptor_lba_count = lba_count_in_current_block;
218 block_number += 1;
219220// Loop through all subsequent blocks until we see a different status and
221 // add it to the total LBA count.
222while block_number < block_number_max {
223// Get the LBA status for the next block.
224 // Usually it's a full block except when we get to the last block.
225provisioning_status = match get_block_lba_status(block_number, leaf_node_state_only)
226 {
227Ok(status) => status,
228Err(e) => return Err(ScsiError::Disk(e)),
229 };
230231// This block has a different status from what we are looking for.
232 // Break out of here so we can composite a new LBA_STATUS_DESCRIPTOR.
233if provisioning_status != provisioning_status_in_previous_block {
234break;
235 }
236237 lba_count_in_current_block =
238 std::cmp::min(block_index_info.lba_per_block, lba_count_remaining);
239240// Only a limited number of LBAs can share the same status descriptor. Finish
241 // this descriptor if it is full.
242if descriptor_lba_count + lba_count_in_current_block > u32::MAX.into() {
243 lba_count_remaining -= (u32::MAX as u64) - descriptor_lba_count;
244 descriptor_lba_count = u32::MAX.into();
245break;
246 }
247248 lba_count_remaining -= lba_count_in_current_block;
249 descriptor_lba_count += lba_count_in_current_block;
250 block_number += 1;
251 }
252253let provisioning_status = match provisioning_status_in_previous_block {
254 LbaStatus::Mapped => scsi::LBA_STATUS_MAPPED,
255 LbaStatus::Deallocated => scsi::LBA_STATUS_DEALLOCATED,
256 LbaStatus::Anchored => scsi::LBA_STATUS_ANCHORED,
257 };
258259// Composite the current LBA_STATUS_DESCRIPTOR and
260 // prepare for the next LBA_STATUS_DESCRIPTOR.
261let lba_status_descriptor = scsi::LbaStatusDescriptor {
262 start_lba: next_start_lba.into(),
263 logical_block_count: (descriptor_lba_count as u32).into(),
264 provisioning_status,
265 reserved2: [0; 3],
266 };
267268tracing::trace!(
269 lba_status = ?lba_status_descriptor,
270"get_lba_status"
271);
272273let new_next_lba_status_descriptor =
274 next_lba_status_descriptor + size_of::<scsi::LbaStatusDescriptor>();
275 buffer[next_lba_status_descriptor..new_next_lba_status_descriptor]
276 .copy_from_slice(lba_status_descriptor.as_bytes());
277 lba_descriptors_used += 1;
278 lba_descriptors_available -= 1;
279 next_start_lba += descriptor_lba_count;
280 next_lba_status_descriptor = new_next_lba_status_descriptor;
281 }
282283// Fill out the header, including the number of contained descriptors.
284let lba_status_descriptors_length =
285 lba_descriptors_used * size_of::<scsi::LbaStatusDescriptor>();
286let mut lba_status_list_header = scsi::LbaStatusListHeader::new_zeroed();
287 lba_status_list_header.parameter_length = ((lba_status_descriptors_length
288 + size_of_val(&lba_status_list_header.reserved))
289as u32)
290 .into();
291292 buffer[0..size_of::<scsi::LbaStatusListHeader>()]
293 .copy_from_slice(lba_status_list_header.as_bytes());
294let tx = lba_status_descriptors_length + size_of::<scsi::LbaStatusListHeader>();
295296 external_data
297 .writer()
298 .write(&buffer[..tx])
299 .map_err(ScsiError::MemoryAccess)?;
300301Ok(tx)
302 }
303}