Skip to content

scsidisk: Support WRITE_ATOMIC(16) SCSI command in NVMe-backed storage #1518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions vm/devices/storage/disk_backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,32 @@ pub trait DiskIo: 'static + Send + Sync + Inspect {
1
}

/// The maximum transfer length supported for atomic IOs, if any.
fn maximum_atomic_transfer_length(&self) -> Option<u32> {
None
}

/// Any required alignment of LBA for atomic IOs.
fn atomic_alignment(&self) -> Option<u32> {
None
}

/// Required granularity of atomic IOs, if any. If set, IO sizes must be a multiple of this.
fn atomic_transfer_length_granularity(&self) -> Option<u32> {
None
}

/// Maximum transfer length of atomic IOs when atomic boundary set, if any.
fn maximum_atomic_transfer_length_with_atomic_boundary(&self) -> Option<u32> {
None
}

/// The maximum size of atomic IOs when performing multiple atomic IOs in a single request, if any.
/// If None and atomic IOs are enabled, only one atomic IO supported per request.
fn maximum_atomic_boundary_size(&self) -> Option<u32> {
None
}

/// Optionally returns a trait object to issue persistent reservation
/// requests.
fn pr(&self) -> Option<&dyn pr::PersistentReservation> {
Expand Down Expand Up @@ -204,6 +230,11 @@ struct DiskInner<T: ?Sized = dyn DynDisk> {
is_read_only: bool,
unmap_behavior: UnmapBehavior,
optimal_unmap_sectors: u32,
maximum_atomic_transfer_length: Option<u32>,
atomic_alignment: Option<u32>,
atomic_transfer_length_granularity: Option<u32>,
maximum_atomic_transfer_length_with_atomic_boundary: Option<u32>,
maximum_atomic_boundary_size: Option<u32>,
disk: T,
}

Expand Down Expand Up @@ -242,6 +273,12 @@ impl Disk {
is_read_only: disk.is_read_only(),
optimal_unmap_sectors: disk.optimal_unmap_sectors(),
unmap_behavior: disk.unmap_behavior(),
maximum_atomic_transfer_length: disk.maximum_atomic_transfer_length(),
atomic_alignment: disk.atomic_alignment(),
atomic_transfer_length_granularity: disk.atomic_transfer_length_granularity(),
maximum_atomic_transfer_length_with_atomic_boundary: disk
.maximum_atomic_transfer_length_with_atomic_boundary(),
maximum_atomic_boundary_size: disk.maximum_atomic_boundary_size(),
disk,
})))
}
Expand Down Expand Up @@ -363,6 +400,32 @@ impl Disk {
pub fn wait_resize(&self, sector_count: u64) -> impl use<'_> + Future<Output = u64> {
self.0.disk.wait_resize(sector_count)
}

/// The maximum transfer length supported for atomic IOs, if any.
pub fn maximum_atomic_transfer_length(&self) -> Option<u32> {
self.0.maximum_atomic_transfer_length
}

/// Any required alignment of LBA for atomic IOs.
pub fn atomic_alignment(&self) -> Option<u32> {
self.0.atomic_alignment
}

/// Required granularity of atomic IOs, if any. If set, IO sizes must be a multiple of this.
pub fn atomic_transfer_length_granularity(&self) -> Option<u32> {
self.0.atomic_transfer_length_granularity
}

/// Maximum transfer length of atomic IOs when atomic boundary set, if any.
pub fn maximum_atomic_transfer_length_with_atomic_boundary(&self) -> Option<u32> {
self.0.maximum_atomic_transfer_length_with_atomic_boundary
}

/// The maximum size of atomic IOs when performing multiple atomic IOs in a single request, if any.
/// If None and atomic IOs are enabled, only one atomic IO supported per request.
pub fn maximum_atomic_boundary_size(&self) -> Option<u32> {
self.0.maximum_atomic_boundary_size
}
}

/// The behavior of unmap.
Expand Down
25 changes: 25 additions & 0 deletions vm/devices/storage/disk_nvme/nvme_driver/src/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,31 @@ impl Namespace {
identify_ns.clone(),
)
}

/// Get value of NSABP (Namespace Supported Atomic Boundary & Power) from the namespace identify data.
pub fn nsabp(&self) -> bool {
self.state.identify.lock().nsfeat.nsabp()
}

/// Get value of AWUN (Atomic Write Unit Normal) from the identify data of the controller associated with this namespace.
pub fn awun(&self) -> u16 {
self.controller_identify.as_ref().awun
}

/// Get value of NAWUN (Namespace Atomic Write Unit Normal) from the namespace identify data.
pub fn nawun(&self) -> u16 {
self.state.identify.lock().nawun
}

/// Get value of NABSN (Namespace Atomic Boundary Size Normal) from the namespace identify data.
pub fn nabsn(&self) -> u16 {
self.state.identify.lock().nabsn
}

/// Get value of NABO (Namespace Atomic Boundary Offset) from the namespace identify data.
pub fn nabo(&self) -> u16 {
self.state.identify.lock().nabo
}
}

impl DynamicState {
Expand Down
45 changes: 45 additions & 0 deletions vm/devices/storage/disk_nvme/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,51 @@ impl DiskIo for NvmeDisk {
fn optimal_unmap_sectors(&self) -> u32 {
self.namespace.preferred_deallocate_granularity().into()
}

fn maximum_atomic_transfer_length(&self) -> Option<u32> {
if self.namespace.nsabp() {
if self.namespace.nawun() == 0 {
None
} else {
Some(self.namespace.nawun().into())
}
} else {
if self.namespace.awun() == 0 {
None
} else {
Some(self.namespace.awun().into())
}
}
}

fn atomic_alignment(&self) -> Option<u32> {
if self.namespace.nabo() == 0 {
None
} else {
Some(self.namespace.nabo().into())
}
}

fn atomic_transfer_length_granularity(&self) -> Option<u32> {
// There appears to be no granularity requirement in the NVMe spec.
None
}

fn maximum_atomic_transfer_length_with_atomic_boundary(&self) -> Option<u32> {
if self.namespace.nabsn() == 0 {
None
} else {
Some(self.namespace.nabsn().into())
}
}

fn maximum_atomic_boundary_size(&self) -> Option<u32> {
if self.namespace.nabsn() == 0 {
None
} else {
Some(self.namespace.nabsn().into())
}
}
}

#[async_trait]
Expand Down
16 changes: 15 additions & 1 deletion vm/devices/storage/scsi_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ open_enum! {
ZBC_OUT = 0x94, // Close Zone, Finish Zone, Open Zone, Reset Write Pointer, etc.
ZBC_IN = 0x95, // Report Zones, etc.
READ_DATA_BUFF16 = 0x9B,
WRITE_ATOMIC16 = 0x9C,
READ_CAPACITY16 = 0x9E,
GET_LBA_STATUS = 0x9E,
GET_PHYSICAL_ELEMENT_STATUS = 0x9E,
Expand Down Expand Up @@ -464,7 +465,8 @@ pub struct VpdBlockLimitsDescriptor {
pub max_atomic_transfer_length: U32BE,
pub atomic_alignment: U32BE,
pub atomic_transfer_length_granularity: U32BE,
pub reserved1: [u8; 8],
pub maximum_atomic_transfer_length_with_atomic_boundary: U32BE,
pub maximum_atomic_boundary_size: U32BE,
}

/// VPD Page 0xB1, Block Device Characteristics
Expand Down Expand Up @@ -1025,6 +1027,18 @@ pub struct Cdb16 {
pub control: u8,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct Cdb16Atomic {
pub operation_code: ScsiOp,
pub flags: Cdb16Flags,
pub logical_block: U64BE,
pub atomic_boundary: U16BE,
pub transfer_blocks: U16BE,
pub reserved2: u8,
pub control: u8,
}

#[bitfield(u8)]
#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct CdbFlags {
Expand Down
11 changes: 11 additions & 0 deletions vm/devices/storage/scsidisk/src/inquiry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ impl SimpleScsiDisk {
let max_write_same_length = (self.scsi_parameters.maximum_transfer_length as u64)
.min(VHDMP_MAX_WRITE_SAME_LENGTH_BYTES)
>> self.sector_shift;
// 0 indicates unsupported
let max_atomic_transfer_length = self.scsi_parameters.maximum_atomic_transfer_length.unwrap_or(0);
let atomic_alignment = self.scsi_parameters.atomic_alignment.unwrap_or(0);
let atomic_transfer_length_granularity = self.scsi_parameters.atomic_transfer_length_granularity.unwrap_or(0);
let maximum_atomic_transfer_length_with_atomic_boundary = self.scsi_parameters.maximum_atomic_transfer_length_with_atomic_boundary.unwrap_or(0);
let maximum_atomic_boundary_size = self.scsi_parameters.maximum_atomic_boundary_size.unwrap_or(0);

// Since we are only here if unmap is supported, ensure the reported
// granularity is non-zero (or the guest will think that unmap is not
Expand All @@ -254,6 +260,11 @@ impl SimpleScsiDisk {
optimal_unmap_granularity: optimal_unmap_granularity.into(),
unmap_granularity_alignment: [0x80, 0x00, 0x00, 0x00], // UGAValid = 1
max_write_same_length: max_write_same_length.into(),
max_atomic_transfer_length: max_atomic_transfer_length.into(),
atomic_alignment: atomic_alignment.into(),
atomic_transfer_length_granularity: atomic_transfer_length_granularity.into(),
maximum_atomic_transfer_length_with_atomic_boundary: maximum_atomic_transfer_length_with_atomic_boundary.into(),
maximum_atomic_boundary_size: maximum_atomic_boundary_size.into(),
..FromZeros::new_zeroed()
};

Expand Down
Loading