pkg/nvme: add NVMe package
This adds a NVMe package for performing various low-level operations on
NVMe devices. Only the most important (to us) calls are implemented as
NVMe has a vast API surface.
Change-Id: I532894c3c2eb780309993a1688226c92c91cdedf
Reviewed-on: https://review.monogon.dev/c/monogon/+/999
Reviewed-by: Mateusz Zalega <mateusz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/pkg/nvme/identify.go b/metropolis/pkg/nvme/identify.go
new file mode 100644
index 0000000..218d089
--- /dev/null
+++ b/metropolis/pkg/nvme/identify.go
@@ -0,0 +1,193 @@
+package nvme
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "math/big"
+)
+
+// Figure 109
+type identifyData struct {
+ // Controller Capabilities and Features
+ PCIVendorID uint16
+ PCISubsystemVendorID uint16
+ SerialNumber [20]byte
+ ModelNumber [40]byte
+ FirmwareRevision [8]byte
+ RecommendedArbitrationBurst uint8
+ IEEEOUI [3]byte
+ CMIC uint8
+ MaximumDataTransferSize uint8
+ ControllerID uint16
+ Version uint32
+ RuntimeD3ResumeLatency uint32
+ RuntimeD3EntryLatency uint32
+ OAES uint32
+ CTRATT uint32
+ _ [12]byte
+ FRUGUID [16]byte
+ _ [128]byte
+ // Admin Command Set Attributes & Optional Controller Capabilities
+ OACS uint16
+ AbortCommandLimit uint8
+ AsynchronousEventRequestLimit uint8
+ FRMW uint8
+ LPA uint8
+ ErrorLogPageEntries uint8
+ NumberOfPowerStatesSupport uint8
+ AdminVendorSpecificCmdConfig uint8
+ AutonomousPowerStateTransitionAttrs uint8
+ WarningCompositeTempThreshold uint16
+ CriticalCompositeTempThreshold uint16
+ MaximumTimeForFirmwareActivation uint16
+ HostMemoryBufferPreferredSize uint32
+ HostMemoryBufferMinimumSize uint32
+ TotalNVMCapacity uint128le
+ UnallocatedNVMCapacity uint128le
+ ReplyProtectedMemoryBlockSupport uint32
+ ExtendedDeviceSelfTestTime uint16
+ DeviceSelfTestOptions uint8
+ FirmwareUpdateGranularity uint8
+ KeepAliveSupport uint16
+ HostControlledThermalMgmtAttrs uint16
+ MinimumThermalMgmntTemp uint16
+ MaximumThermalMgmntTemp uint16
+ SanitizeCapabilities uint32
+ _ [180]byte
+ // NVM Command Set Attributes
+ SubmissionQueueEntrySize uint8
+ CompletionQueueEntrySize uint8
+ MaximumOutstandingCommands uint16
+ NumberOfNamespaces uint32
+ OptionalNVMCommandSupport uint16
+ FusedOperationSupport uint16
+ FormatNVMAttributes uint8
+ VolatileWriteCache uint8
+ AtomicWriteUnitNormal uint16
+ AtomicWriteUnitPowerFail uint16
+ NVMVendorSepcificCommandConfig uint8
+ AtomicCompareAndWriteUnit uint16
+ _ [2]byte
+ SGLSupport uint32
+ _ [228]byte
+ NVMSubsystemNVMeQualifiedName [256]byte
+ _ [1024]byte
+ // Power State Descriptors
+ PowerStateDescriptors [32][32]byte
+}
+
+// IdentifyData contains various identifying information about a NVMe
+// controller. Because the actual data structure is very large, currently not
+// all fields are exposed as properly-typed individual fields. If you need
+// a new field, please add it to this structure.
+type IdentifyData struct {
+ // PCIVendorID contains the company vendor identifier assigned by the PCI
+ // SIG.
+ PCIVendorID uint16
+ // PCISubsystemVendorID contains the company vendor identifier that is
+ // assigned by the PCI SIG for the subsystem.
+ PCISubsystemVendorID uint16
+ // SerialNumber contains the serial number for the NVM subsystem that is
+ // assigned by the vendor.
+ SerialNumber string
+ // ModelNumber contains the model number for the NVM subsystem that is
+ // assigned by the vendor.
+ ModelNumber string
+ // FirmwareRevision contains the currently active firmware revision for the
+ // NVM subsystem.
+ FirmwareRevision string
+ // IEEEOUI contains the Organization Unique Identifier for the controller
+ // vendor as assigned by the IEEE.
+ IEEEOUI [3]byte
+
+ // IsPCIVirtualFunction indicates if the controller is a virtual controller
+ // as part of a PCI virtual function.
+ IsPCIVirtualFunction bool
+
+ // SpecVersionMajor/Minor contain the version of the NVMe specification the
+ // controller supports. Only mandatory from spec version 1.2 onwards.
+ SpecVersionMajor uint16
+ SpecVersionMinor uint8
+
+ // FRUGloballyUniqueIdentifier contains a 128-bit value that is globally
+ // unique for a given Field Replaceable Unit (FRU). Contains all-zeroes if
+ // unavailable.
+ FRUGloballyUniqueIdentifier [16]byte
+ // VirtualizationManagementSupported indicates if the controller
+ // supports the Virtualization Management command.
+ VirtualizationManagementSupported bool
+ // NVMeMISupported indicates if the controller supports the NVMe-MI
+ // Send and Receive commands.
+ NVMeMISupported bool
+ // DirectivesSupported indicates if the controller supports the
+ // Directive Send and Receive commands.
+ DirectivesSupported bool
+ // SelfTestSupported indicates if the controller supports the Device Self-
+ // test command.
+ SelfTestSupported bool
+ // NamespaceManagementSupported indicates if the controller supports the
+ // Namespace Management and Attachment commands.
+ NamespaceManagementSupported bool
+ // FirmwareUpdateSupported indicates if the controller supports the
+ // Firmware Commit and Image Download commands.
+ FirmwareUpdateSupported bool
+ // FormattingSupported indicates if the controller supports the Format
+ // command.
+ FormattingSupported bool
+ // SecuritySupported indicates if the controller supports the Security Send
+ // and Receive commands.
+ SecuritySupported bool
+
+ // TotalNVMCapacity contains the total NVM capacity in bytes in the NVM
+ // subsystem. This can be 0 on devices without NamespaceManagementSupported.
+ TotalNVMCapacity *big.Int
+ // UnallocatedNVMCapacity contains the unallocated NVM capacity in bytes in
+ // the NVM subsystem. This can be 0 on devices without
+ // NamespaceManagementSupported.
+ UnallocatedNVMCapacity *big.Int
+
+ // MaximumNumberOfNamespace defines the maximum number of namespaces
+ // supported by the controller.
+ MaximumNumberOfNamespaces uint32
+}
+
+func (d *Device) Identify() (*IdentifyData, error) {
+ var resp [4096]byte
+
+ if err := d.RawCommand(&Command{
+ Opcode: 0x06,
+ Data: resp[:],
+ CDW10: 1,
+ }); err != nil {
+ return nil, fmt.Errorf("Identify command failed: %w", err)
+ }
+ var raw identifyData
+ binary.Read(bytes.NewReader(resp[:]), binary.LittleEndian, &raw)
+
+ var res IdentifyData
+ res.PCIVendorID = raw.PCIVendorID
+ res.PCISubsystemVendorID = raw.PCISubsystemVendorID
+ res.SerialNumber = string(bytes.TrimRight(raw.SerialNumber[:], " "))
+ res.ModelNumber = string(bytes.TrimRight(raw.ModelNumber[:], " "))
+ res.FirmwareRevision = string(bytes.TrimRight(raw.FirmwareRevision[:], " "))
+ // OUIs are traditionally big-endian, but NVMe exposes them in little-endian
+ res.IEEEOUI[0], res.IEEEOUI[1], res.IEEEOUI[2] = raw.IEEEOUI[2], raw.IEEEOUI[1], raw.IEEEOUI[0]
+ res.IsPCIVirtualFunction = raw.CMIC&(1<<2) != 0
+ res.SpecVersionMajor = uint16(raw.Version >> 16)
+ res.SpecVersionMinor = uint8((raw.Version >> 8) & 0xFF)
+ res.FRUGloballyUniqueIdentifier = raw.FRUGUID
+ res.VirtualizationManagementSupported = raw.OACS&(1<<7) != 0
+ res.NVMeMISupported = raw.OACS&(1<<6) != 0
+ res.DirectivesSupported = raw.OACS&(1<<5) != 0
+ res.SelfTestSupported = raw.OACS&(1<<4) != 0
+ res.NamespaceManagementSupported = raw.OACS&(1<<3) != 0
+ res.FirmwareUpdateSupported = raw.OACS&(1<<2) != 0
+ res.FormattingSupported = raw.OACS&(1<<1) != 0
+ res.SecuritySupported = raw.OACS&(1<<0) != 0
+
+ res.TotalNVMCapacity = raw.TotalNVMCapacity.BigInt()
+ res.UnallocatedNVMCapacity = raw.UnallocatedNVMCapacity.BigInt()
+ res.MaximumNumberOfNamespaces = raw.NumberOfNamespaces
+ return &res, nil
+}