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/error.go b/metropolis/pkg/nvme/error.go
new file mode 100644
index 0000000..8c4a207
--- /dev/null
+++ b/metropolis/pkg/nvme/error.go
@@ -0,0 +1,136 @@
+package nvme
+
+import "fmt"
+
+// Figure 31 in the spec
+var genericStatusCodeDesc = map[uint8]string{
+	0x00: "successful completion",
+	0x01: "invalid command opcode",
+	0x02: "invalid field in command",
+	0x03: "command ID conflict",
+	0x04: "data transfer error",
+	0x05: "command aborted due power loss notification",
+	0x06: "internal error",
+	0x07: "command abort requested",
+	0x08: "command abort due to SQ deletion",
+	0x09: "command abort due to failed fused command",
+	0x0a: "command abort due to missing fused command",
+	0x0b: "invalid namespace or format",
+	0x0c: "command sequence error",
+	0x0d: "invalid SGL segment descriptor",
+	0x0e: "invalid number of SGL descriptors",
+	0x0f: "data SGL length invalid",
+	0x10: "metadata SGL length invalid",
+	0x11: "SGL descriptor type invalid",
+	0x12: "invalid use of controller memory buffer",
+	0x13: "PRP offset invalid",
+	0x14: "atomic write unit exceeded",
+	0x15: "operation denied",
+	0x16: "SGL offset invalid",
+	0x18: "host identifer inconsistent format",
+	0x19: "keep alive timeout expired",
+	0x1a: "keep alive timeout invalid",
+	0x1b: "command aborted due to preempt and abort",
+	0x1c: "sanitize failed",
+	0x1d: "sanitize in progress",
+	0x1e: "SGL data block granularity invalid",
+	0x1f: "command not supported for queue in CMB",
+
+	// Figure 32
+	0x80: "LBA out of range",
+	0x81: "capacity exceeded",
+	0x82: "namespace not ready",
+	0x83: "reservation conflict",
+	0x84: "format in progress",
+}
+
+// Figure 33 in the spec
+var commandSpecificStatusCodeDesc = map[uint8]string{
+	0x00: "completion queue invalid",
+	0x01: "invalid queue identifier",
+	0x02: "invalid queue size",
+	0x03: "abort command limit exceeded",
+	0x05: "asynchronous event request limit exceeded",
+	0x06: "invalid firmware slot",
+	0x07: "invalid firmware image",
+	0x08: "invalid interrupt vector",
+	0x09: "invalid log page",
+	0x0a: "invalid format",
+	0x0b: "firmware activation requires conventional reset",
+	0x0c: "invalid queue deletion",
+	0x0d: "feature identifier not saveable",
+	0x0e: "feature not changeable",
+	0x0f: "feature not namespace-specific",
+	0x10: "firmware activation requires NVM subsystem reset",
+	0x11: "firmware activation requires reset",
+	0x12: "firmware activation requires maximum time violation",
+	0x13: "firmware activation prohibited",
+	0x14: "overlapping range",
+	0x15: "namespace insufficient capacity",
+	0x16: "namespace identifier unavailable",
+	0x18: "namespace already attached",
+	0x19: "namespace is private",
+	0x1a: "namespace is not attached",
+	0x1b: "thin provisioning not supported",
+	0x1c: "controller list invalid",
+	0x1d: "device self-test in progress",
+	0x1e: "boot partition write prohibited",
+	0x1f: "invalid controller identifier",
+	0x20: "invalid secondary controller state",
+	0x21: "invalid number of controller resources",
+	0x22: "invalid resource identifier",
+
+	// Figure 34
+	0x80: "conflicting attributes",
+	0x81: "invalid protection information",
+	0x82: "attempted to write to read-only range",
+}
+
+// Figure 36
+var mediaAndDataIntegrityStatusCodeDesc = map[uint8]string{
+	0x80: "write fault",
+	0x81: "unrecovered read error",
+	0x82: "end-to-end guard check error",
+	0x83: "end-to-end application tag check error",
+	0x84: "end-to-end reference tag check error",
+	0x85: "compare failure",
+	0x86: "access denied",
+	0x87: "deallocated or unwritten logical block",
+}
+
+const (
+	StatusCodeTypeGeneric               = 0x0
+	StatusCodeTypeCommandSpecific       = 0x1
+	StatusCodeTypeMediaAndDataIntegrity = 0x2
+)
+
+// Error represents an error returned by the NVMe device in the form of a
+// NVMe Status Field (see also Figure 29 in the spec).
+type Error struct {
+	DoNotRetry     bool
+	More           bool
+	StatusCodeType uint8
+	StatusCode     uint8
+}
+
+func (e Error) Error() string {
+	switch e.StatusCodeType {
+	case StatusCodeTypeGeneric:
+		if errStr, ok := genericStatusCodeDesc[e.StatusCode]; ok {
+			return errStr
+		}
+		return fmt.Sprintf("unknown error with generic code 0x%x", e.StatusCode)
+	case StatusCodeTypeCommandSpecific:
+		if errStr, ok := commandSpecificStatusCodeDesc[e.StatusCode]; ok {
+			return errStr
+		}
+		return fmt.Sprintf("unknown error with command-specific code 0x%x", e.StatusCode)
+	case StatusCodeTypeMediaAndDataIntegrity:
+		if errStr, ok := mediaAndDataIntegrityStatusCodeDesc[e.StatusCode]; ok {
+			return errStr
+		}
+		return fmt.Sprintf("unknown error with media and data integrity code 0x%x", e.StatusCode)
+	default:
+		return fmt.Sprintf("unknown error with unknown type 0x%x and code 0x%x", e.StatusCodeType, e.StatusCode)
+	}
+}