Skip to content

Commit 6e82d49

Browse files
committed
take serial numbers in NVMe specs
1 parent d6602c1 commit 6e82d49

File tree

10 files changed

+100
-12
lines changed

10 files changed

+100
-12
lines changed

bin/propolis-cli/src/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use anyhow::{anyhow, Context};
1616
use clap::{Args, Parser, Subcommand};
1717
use futures::{future, SinkExt};
1818
use newtype_uuid::{GenericUuid, TypedUuid, TypedUuidKind, TypedUuidTag};
19+
use propolis_client::support::nvme_serial_from_str;
1920
use propolis_client::types::{
2021
BlobStorageBackend, Board, Chipset, ComponentV0, CrucibleStorageBackend,
2122
I440Fx, InstanceEnsureRequest, InstanceInitializationMethod,
@@ -241,6 +242,7 @@ impl DiskRequest {
241242
"nvme" => ComponentV0::NvmeDisk(NvmeDisk {
242243
backend_id: backend_id.clone(),
243244
pci_path,
245+
serial_number: nvme_serial_from_str(&self.name, b' '),
244246
}),
245247
_ => anyhow::bail!(
246248
"invalid device type in disk request: {:?}",

bin/propolis-server/src/lib/initializer.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -633,11 +633,17 @@ impl<'a> MachineInitializer<'a> {
633633
vioblk
634634
}
635635
DeviceInterface::Nvme => {
636+
let spec::StorageDevice::Nvme(nvme_spec) =
637+
&disk.device_spec
638+
else {
639+
unreachable!("disk is known to be an NVMe disk");
640+
};
641+
636642
// Limit data transfers to 1MiB (2^8 * 4k) in size
637643
let mdts = Some(8);
638644
let component = format!("nvme-{}", device_id);
639645
let nvme = nvme::PciNvme::create(
640-
device_id.to_string(),
646+
&nvme_spec.serial_number,
641647
mdts,
642648
self.log.new(slog::o!("component" => component)),
643649
);

bin/propolis-standalone/src/main.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,14 @@ fn setup_instance(
12301230
log.new(slog::o!("dev" => format!("nvme-{}", name)));
12311231
// Limit data transfers to 1MiB (2^8 * 4k) in size
12321232
let mdts = Some(8);
1233-
let nvme = hw::nvme::PciNvme::create(dev_serial, mdts, log);
1233+
1234+
let mut serial_number = [0u8; 20];
1235+
let sz = dev_serial.len().min(20);
1236+
serial_number[..sz]
1237+
.clone_from_slice(&dev_serial.as_bytes()[..sz]);
1238+
1239+
let nvme =
1240+
hw::nvme::PciNvme::create(&serial_number, mdts, log);
12341241

12351242
guard.inventory.register_instance(&nvme, &bdf.to_string());
12361243
guard.inventory.register_block(&backend, name);

crates/propolis-api-types/src/instance_spec/components/devices.rs

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pub struct NvmeDisk {
2929

3030
/// The PCI bus/device/function at which this disk should be attached.
3131
pub pci_path: PciPath,
32+
33+
/// The serial number to return in response to an NVMe Identify Controller
34+
/// command.
35+
pub serial_number: [u8; 20],
3236
}
3337

3438
/// A network card that presents a virtio-net interface to the guest.

crates/propolis-config-toml/src/spec.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010
};
1111

1212
use propolis_client::{
13+
support::nvme_serial_from_str,
1314
types::{
1415
ComponentV0, DlpiNetworkBackend, FileStorageBackend,
1516
MigrationFailureInjector, NvmeDisk, P9fs, PciPciBridge, SoftNpuP9,
@@ -289,9 +290,11 @@ fn parse_storage_device_from_config(
289290
Interface::Virtio => {
290291
ComponentV0::VirtioDisk(VirtioDisk { backend_id, pci_path })
291292
}
292-
Interface::Nvme => {
293-
ComponentV0::NvmeDisk(NvmeDisk { backend_id, pci_path })
294-
}
293+
Interface::Nvme => ComponentV0::NvmeDisk(NvmeDisk {
294+
backend_id,
295+
pci_path,
296+
serial_number: nvme_serial_from_str(name, b' '),
297+
}),
295298
},
296299
id_to_return,
297300
))

lib/propolis-client/src/support.rs

+29
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ impl Default for Chipset {
2727
}
2828
}
2929

30+
/// Generates a 20-byte NVMe device serial number from the bytes in a string
31+
/// slice. If the slice is too short to populate the entire serial number, the
32+
/// remaining bytes are filled with `pad`.
33+
pub fn nvme_serial_from_str(s: &str, pad: u8) -> [u8; 20] {
34+
let mut sn = [0u8; 20];
35+
36+
let bytes_from_slice = sn.len().min(s.len());
37+
sn[..bytes_from_slice].copy_from_slice(&s.as_bytes()[..bytes_from_slice]);
38+
sn[bytes_from_slice..].fill(pad);
39+
sn
40+
}
41+
3042
/// Clone of `InstanceSerialConsoleControlMessage` type defined in
3143
/// `propolis_api_types`, with which this must be kept in sync.
3244
///
@@ -572,4 +584,21 @@ mod test {
572584
{
573585
WebSocketStream::from_raw_socket(conn, Role::Server, None).await
574586
}
587+
588+
#[test]
589+
fn test_nvme_serial_from_str() {
590+
use super::nvme_serial_from_str;
591+
592+
let expected = b"hello world ";
593+
assert_eq!(nvme_serial_from_str("hello world", b' '), *expected);
594+
595+
let expected = b"enthusiasm!!!!!!!!!!";
596+
assert_eq!(nvme_serial_from_str("enthusiasm", b'!'), *expected);
597+
598+
let expected = b"very_long_disk_name_";
599+
assert_eq!(
600+
nvme_serial_from_str("very_long_disk_name_goes_here", b'?'),
601+
*expected
602+
);
603+
}
575604
}

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

+2-6
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ pub struct PciNvme {
505505
impl PciNvme {
506506
/// Create a new pci-nvme device with the given values
507507
pub fn create(
508-
serial_number: String,
508+
serial_number: &[u8; 20],
509509
mdts: Option<u8>,
510510
log: slog::Logger,
511511
) -> Arc<Self> {
@@ -527,16 +527,12 @@ impl PciNvme {
527527
let cqes = size_of::<CompletionQueueEntry>().trailing_zeros() as u8;
528528
let sqes = size_of::<SubmissionQueueEntry>().trailing_zeros() as u8;
529529

530-
let sz = std::cmp::min(20, serial_number.len());
531-
let mut sn: [u8; 20] = [0u8; 20];
532-
sn[..sz].clone_from_slice(&serial_number.as_bytes()[..sz]);
533-
534530
// Initialize the Identify structure returned when the host issues
535531
// an Identify Controller command.
536532
let ctrl_ident = bits::IdentifyController {
537533
vid: VENDOR_OXIDE,
538534
ssvid: VENDOR_OXIDE,
539-
sn,
535+
sn: *serial_number,
540536
ieee: OXIDE_OUI,
541537
mdts: mdts.unwrap_or(0),
542538
// We use standard Completion/Submission Queue Entry structures with no extra

openapi/propolis-server.json

+13-1
Original file line numberDiff line numberDiff line change
@@ -1513,11 +1513,23 @@
15131513
"$ref": "#/components/schemas/PciPath"
15141514
}
15151515
]
1516+
},
1517+
"serial_number": {
1518+
"description": "The serial number to return in response to an NVMe Identify Controller command.",
1519+
"type": "array",
1520+
"items": {
1521+
"type": "integer",
1522+
"format": "uint8",
1523+
"minimum": 0
1524+
},
1525+
"minItems": 20,
1526+
"maxItems": 20
15161527
}
15171528
},
15181529
"required": [
15191530
"backend_id",
1520-
"pci_path"
1531+
"pci_path",
1532+
"serial_number"
15211533
],
15221534
"additionalProperties": false
15231535
},

phd-tests/framework/src/disk/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ impl DeviceName {
9595
pub fn into_string(self) -> String {
9696
self.0
9797
}
98+
99+
pub fn as_str(&self) -> &str {
100+
self.0.as_str()
101+
}
98102
}
99103

100104
/// The name for a backend implementing storage for a disk. This is derived

phd-tests/framework/src/test_vm/config.rs

+25
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::sync::Arc;
77
use anyhow::Context;
88
use cpuid_utils::CpuidIdent;
99
use propolis_client::{
10+
support::nvme_serial_from_str,
1011
types::{
1112
Board, BootOrderEntry, BootSettings, Chipset, ComponentV0, Cpuid,
1213
CpuidEntry, CpuidVendor, InstanceMetadata, InstanceSpecV0,
@@ -313,6 +314,30 @@ impl<'dr> VmConfig<'dr> {
313314
backend_name.clone().into_string(),
314315
),
315316
pci_path,
317+
serial_number: nvme_serial_from_str(
318+
device_name.as_str(),
319+
// TODO(#790): Propolis's NVMe controller used to pad
320+
// serial numbers with 0 bytes by default. This causes
321+
// disk serial numbers to appear in the guest to be
322+
// null-terminated strings. This, in turn, affects (or
323+
// at least may affect) how guest firmware images
324+
// identify disks when determining a VM's boot order: a
325+
// disk whose serial number is padded to 20 bytes with
326+
// spaces may not match a disk whose serial number was
327+
// padded with \0 bytes.
328+
//
329+
// Unfortunately for us, guest firmware may persist disk
330+
// identifiers to a VM's nonvolatile EFI variable store.
331+
// This means that changing from zero-padded to
332+
// space-padded serial numbers may break an existing
333+
// VM's saved boot order.
334+
//
335+
// Until we decide whether and how to preserve
336+
// compatibility for existing VMs that may have
337+
// preserved a zero-padded disk ID, continue to zero-pad
338+
// disk IDs in PHD to match previous behavior.
339+
0,
340+
),
316341
}),
317342
};
318343

0 commit comments

Comments
 (0)