Skip to content

Commit e4c60d8

Browse files
committed
ixi code review
1 parent 224f78f commit e4c60d8

7 files changed

Lines changed: 79 additions & 38 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/propolis-server/src/lib/stats/virtual_disk.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,8 @@ impl VirtualDiskStats {
7878
}
7979
Operation::Flush => self.on_flush_completion(result, duration),
8080
Operation::Discard => {
81-
// Discard is not wired up in backends we care about for now, so
82-
// it can safely be ignored.
83-
// XXX no longer true
81+
// Discard is only partially wired up in backends we care about, and it's not
82+
// clear what stats (if any) to report yet.
8483
}
8584
}
8685
}

lib/propolis/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ async-trait.workspace = true
4444
iddqd.workspace = true
4545
nix.workspace = true
4646
vm-attest.workspace = true
47+
itertools.workspace = true
4748

4849
# falcon
4950
libloading = { workspace = true, optional = true }

lib/propolis/src/block/crucible.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,10 @@ impl WorkerState {
146146
}
147147
}
148148
block::Operation::Discard => {
149-
// Crucible does not support discard operations for now
150-
return Err(Error::Unsupported);
149+
// Crucible does not support discard operations for now, so we implement this as
150+
// a no-op (which technically is a valid implementation of discard, just one that
151+
// doesn't actually free any space).
152+
return Ok(());
151153
}
152154
}
153155
Ok(())

lib/propolis/src/hw/nvme/cmds.rs

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ pub enum ParseErr {
3131
/// An invalid value was specified in the FUSE bits of `CDW0`.
3232
#[error("reserved FUSE value specified")]
3333
ReservedFuse,
34+
35+
/// A reserved field was set to a non-zero value.
36+
#[error("reserved field value specified")]
37+
Reserved,
3438
}
3539

3640
/// A parsed Admin Command
@@ -715,14 +719,19 @@ impl NvmCmd {
715719
prp2: raw.prp2,
716720
}),
717721
bits::NVM_OPC_DATASET_MANAGEMENT => {
722+
if (raw.cdw11 & !0b111) != 0 {
723+
// Only the lowest 3 bits of CDW11 are used for Dataset
724+
// Management, so reject if any other bits are set.
725+
return Err(ParseErr::Reserved);
726+
}
718727
NvmCmd::DatasetManagement(DatasetManagementCmd {
719728
prp1: raw.prp1,
720729
prp2: raw.prp2,
721730
// Convert from 0's based value
722731
nr: (raw.cdw10 & 0xFF) as u16 + 1,
723732
ad: raw.cdw11 & (1 << 2) != 0,
724-
idw: raw.cdw11 & (1 << 1) != 0,
725-
idr: raw.cdw11 & (1 << 0) != 0,
733+
_idw: raw.cdw11 & (1 << 1) != 0,
734+
_idr: raw.cdw11 & (1 << 0) != 0,
726735
})
727736
}
728737
_ => NvmCmd::Unknown(raw),
@@ -797,7 +806,6 @@ impl ReadCmd {
797806

798807
/// Dataset Management Command Parameters
799808
#[derive(Debug)]
800-
#[allow(dead_code)]
801809
pub struct DatasetManagementCmd {
802810
/// PRP Entry 1 (PRP1)
803811
///
@@ -806,14 +814,13 @@ pub struct DatasetManagementCmd {
806814

807815
/// PRP Entry 2 (PRP2)
808816
///
809-
/// This field contains the second PRP entry that specifies the location where data should be
810-
/// transferred from (if there is a physical discontinuity).
817+
/// Indicates a second data buffer that contains LBA range information. It may not be a PRP
818+
/// List.
811819
prp2: u64,
812820

813821
/// Number of Ranges (NR)
814822
///
815-
/// Indicates the number of 16 byte range sets that are specified in the command. This is a
816-
/// 0’s based value.
823+
/// Indicates the number of 16 byte range sets that are specified in the command.
817824
pub nr: u16,
818825

819826
/// Attribute – Deallocate (AD)
@@ -832,15 +839,19 @@ pub struct DatasetManagementCmd {
832839
/// The host expects to perform operations on all ranges provided as an integral unit for
833840
/// writes, indicating that if a portion of the dataset is written it is expected that all of
834841
/// the ranges in the dataset are going to be written.
835-
idw: bool,
842+
///
843+
/// Note: this field is advisory, and we ignore it.
844+
_idw: bool,
836845

837846
/// Attribute – Integral Dataset for Read (IDR)
838847
///
839848
/// If set to ‘1’ then the dataset should be optimized for read access as an integral unit.
840849
/// The host expects to perform operations on all ranges provided as an integral unit for
841850
/// reads, indicating that if a portion of the dataset is read it is expected that all of the
842851
/// ranges in the dataset are going to be read.
843-
idr: bool,
852+
///
853+
/// Note: this field is advisory, and we ignore it.
854+
_idr: bool,
844855
}
845856

846857
impl DatasetManagementCmd {
@@ -855,27 +866,23 @@ impl DatasetManagementCmd {
855866
)
856867
}
857868

858-
/// Returns an Iterator that yields the LBA ranges specified in this command. Note that if
859-
/// some of the ranges couldn't be read from guest memory, this will yield fewer than
860-
/// `Self.nr` ranges.
869+
/// Returns an Iterator that yields the LBA ranges specified in this command. If any of the
870+
/// ranges cannot be read from guest memory, yields an error for that range instead.
861871
pub fn ranges<'a>(
862872
&self,
863873
mem: &'a MemCtx,
864-
) -> impl Iterator<Item = DatasetManagementRangeDefinition> + 'a {
874+
) -> impl Iterator<
875+
Item = Result<DatasetManagementRangeDefinition, &'static str>,
876+
> + 'a {
865877
self.data(mem).flat_map(|region| {
866-
let mut ranges = Vec::new();
867-
if let Some(mapping) = mem.readable_region(&region) {
868-
ranges.resize_with(
869-
mapping.len()
870-
/ size_of::<DatasetManagementRangeDefinition>(),
871-
Default::default,
872-
);
873-
if mapping.read_many(&mut ranges).is_err() {
874-
ranges.clear();
875-
}
876-
};
877-
878-
ranges.into_iter()
878+
if let Some(Ok(defs)) = mem
879+
.readable_region(&region)
880+
.map(|mapping| mapping.read_many_owned())
881+
{
882+
defs.into_iter().map(Ok).collect::<Vec<_>>().into_iter()
883+
} else {
884+
vec![Err("Failed to read LBA range")].into_iter()
885+
}
879886
})
880887
}
881888

lib/propolis/src/hw/nvme/requests.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
use itertools::Itertools;
56
use std::sync::Arc;
67
use std::time::Instant;
78

89
use super::{cmds::NvmCmd, queue::Permit, PciNvme};
910
use crate::accessors::MemAccessor;
1011
use crate::block::{self, Operation, Request};
12+
use crate::hw::nvme::cmds::ParseErr;
1113
use crate::hw::nvme::{bits, cmds::Completion, queue::SubQueue};
1214

1315
#[usdt::provider(provider = "propolis")]
@@ -147,7 +149,7 @@ impl block::DeviceQueue for NvmeBlockQueue {
147149
}
148150
Ok(NvmCmd::DatasetManagement(cmd)) => {
149151
// We only support the "deallocate" (discard/trim) operation of Dataset
150-
// Management. XXX should we return success?
152+
// Management.
151153
if !cmd.is_deallocate() {
152154
permit.complete(
153155
Completion::generic_err(bits::STS_INVAL_FIELD)
@@ -161,18 +163,18 @@ impl block::DeviceQueue for NvmeBlockQueue {
161163
cid,
162164
cmd.nr,
163165
));
164-
let ranges: Vec<_> = cmd
166+
let Ok(ranges): Result<Vec<_>, _> = cmd
165167
.ranges(&mem)
166-
.map(|r| r.offset_len(params.lba_data_size))
167-
.collect();
168-
if ranges.len() != cmd.nr as usize {
169-
// If we couldn't read all the ranges, fail the command
168+
.map_ok(|r| r.offset_len(params.lba_data_size))
169+
.try_collect()
170+
else {
171+
// If we couldn't read the ranges, fail the command
170172
permit.complete(
171173
Completion::generic_err(bits::STS_DATA_XFER_ERR)
172174
.dnr(),
173175
);
174176
continue;
175-
}
177+
};
176178
let req = Request::new_discard(ranges);
177179
return Some((req, permit, None));
178180
}
@@ -181,6 +183,13 @@ impl block::DeviceQueue for NvmeBlockQueue {
181183
let req = Request::new_flush();
182184
return Some((req, permit, None));
183185
}
186+
Err(ParseErr::ReservedFuse) | Err(ParseErr::Reserved) => {
187+
// For commands that fail parsing due to reserved fields being set,
188+
// complete with an invalid field error
189+
let comp =
190+
Completion::generic_err(bits::STS_INVAL_FIELD).dnr();
191+
permit.complete(comp);
192+
}
184193
Ok(NvmCmd::Unknown(_)) | Err(_) => {
185194
// For any other unrecognized or malformed command,
186195
// just immediately complete it with an error

lib/propolis/src/vmm/mem.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,28 @@ impl SubMapping<'_> {
557557
Ok(unsafe { typed.read_unaligned() })
558558
}
559559

560+
/// Read the entire mapping as an array of `T` objects.
561+
/// The size of the mapping must be aligned to `size_of::<T>()`.
562+
pub fn read_many_owned<T: Copy + FromBytes>(&self) -> Result<Vec<T>> {
563+
self.check_read_access()?;
564+
if !self.len.is_multiple_of(size_of::<T>()) {
565+
return Err(Error::new(
566+
ErrorKind::InvalidInput,
567+
"Mapping size not aligned to value type",
568+
));
569+
}
570+
let count = self.len / size_of::<T>();
571+
let mut vec = Vec::with_capacity(count);
572+
573+
self.read_many(&mut vec.spare_capacity_mut()[..count])?;
574+
// Safety: read_many() was successful and just initialized the first `count` elements of
575+
// the vector.
576+
unsafe {
577+
vec.set_len(count);
578+
}
579+
Ok(vec)
580+
}
581+
560582
/// Read `values` from the mapping.
561583
pub fn read_many<T: Copy + FromBytes>(
562584
&self,

0 commit comments

Comments
 (0)