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/selftest.go b/metropolis/pkg/nvme/selftest.go
new file mode 100644
index 0000000..8f46995
--- /dev/null
+++ b/metropolis/pkg/nvme/selftest.go
@@ -0,0 +1,96 @@
+package nvme
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type SelfTestOp uint8
+
+const (
+	SelfTestNone     SelfTestOp = 0x0
+	SelfTestShort    SelfTestOp = 0x1
+	SelfTestExtended SelfTestOp = 0x2
+	SelfTestAbort    SelfTestOp = 0xF
+)
+
+func (d *Device) StartSelfTest(ns uint32, action SelfTestOp) error {
+	return d.RawCommand(&Command{
+		Opcode:      0x14,
+		NamespaceID: ns,
+		CDW10:       uint32(action & 0xF),
+	})
+}
+
+// Figure 99
+type selfTestResult struct {
+	SelfTestStatus             uint8
+	SegmentNumber              uint8
+	ValidDiagnosticInformation uint8
+	_                          byte
+	PowerOnHours               uint64
+	NamespaceID                uint32
+	FailingLBA                 uint64
+	StatusCodeType             uint8
+	StatusCode                 uint8
+	VendorSpecific             [2]byte
+}
+
+// Figure 98
+type selfTestLogPage struct {
+	CurrentSelfTestOp         uint8
+	CurrentSelfTestCompletion uint8
+	_                         [2]byte
+	SelfTestResults           [20]selfTestResult
+}
+
+type SelfTestResult struct {
+	// Op contains the self test type
+	Op            SelfTestOp
+	Result        uint8
+	SegmentNumber uint8
+	PowerOnHours  uint64
+	NamespaceID   uint32
+	FailingLBA    uint64
+	Error         Error
+}
+
+type SelfTestResults struct {
+	// CurrentOp contains the currently in-progress self test type (or
+	// SelfTestTypeNone if no self test is in progress).
+	CurrentOp SelfTestOp
+	// CurrentCompletion contains the progress from 0 to 1 of the currently
+	// in-progress self-test. Only valid if CurrentOp is not SelfTestTypeNone.
+	CurrentSelfTestCompletion float32
+	// PastResults contains a list of up to 20 previous self test results,
+	// sorted from the most recent to the oldest.
+	PastResults []SelfTestResult
+}
+
+func (d *Device) GetSelfTestResults(ns uint32) (*SelfTestResults, error) {
+	var buf [564]byte
+	if err := d.GetLogPage(ns, 0x06, 0, 0, buf[:]); err != nil {
+		return nil, err
+	}
+	var page selfTestLogPage
+	binary.Read(bytes.NewReader(buf[:]), binary.LittleEndian, &page)
+	var res SelfTestResults
+	res.CurrentOp = SelfTestOp(page.CurrentSelfTestOp & 0xF)
+	res.CurrentSelfTestCompletion = float32(page.CurrentSelfTestCompletion&0x7F) / 100.
+	for _, r := range page.SelfTestResults {
+		var t SelfTestResult
+		t.Op = SelfTestOp((r.SelfTestStatus >> 4) & 0xF)
+		t.Result = r.SelfTestStatus & 0xF
+		if t.Result == 0xF {
+			continue
+		}
+		t.SegmentNumber = r.SegmentNumber
+		t.PowerOnHours = r.PowerOnHours
+		t.NamespaceID = r.NamespaceID
+		t.FailingLBA = r.FailingLBA
+		t.Error.StatusCode = r.StatusCode
+		t.Error.StatusCodeType = r.StatusCodeType
+		res.PastResults = append(res.PastResults, t)
+	}
+	return &res, nil
+}