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
+}