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/nvme.go b/metropolis/pkg/nvme/nvme.go
new file mode 100644
index 0000000..f46546d
--- /dev/null
+++ b/metropolis/pkg/nvme/nvme.go
@@ -0,0 +1,79 @@
+// Package nvme provides methods and data structures for issuing commands to
+// device speaking the NVMe protocol.
+// This package is written against the NVMe Specification Revision 1.3 and
+// all references to figures or other parts of the spec refer to this version.
+package nvme
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "syscall"
+ "time"
+)
+
+// Device is a handle for a NVMe device.
+type Device struct {
+ fd syscall.Conn
+}
+
+// NewFromFd creates a new NVMe device handle from a system handle.
+func NewFromFd(fd syscall.Conn) (*Device, error) {
+ d := &Device{fd: fd}
+ // There is no good way to validate that a file descriptor indeed points to
+ // a NVMe device. For future compatibility let this return an error so that
+ // code is already prepared to handle it.
+ return d, nil
+}
+
+// Open opens a new NVMe device handle from a device path (like /dev/nvme0).
+func Open(path string) (*Device, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, fmt.Errorf("unable to open path: %w", err)
+ }
+ return NewFromFd(f)
+}
+
+// Close closes the NVMe device handle. It returns an error if the handle was
+// not created by Open. Please close the handle passed to NewFromFd yourself
+// in that case.
+func (d *Device) Close() error {
+ if f, ok := d.fd.(*os.File); ok {
+ return f.Close()
+ } else {
+ return errors.New("unable to close device not opened via Open, please close it yourself")
+ }
+}
+
+const (
+ // GlobalNamespace is the namespace ID for operations not on a specific
+ // namespace.
+ GlobalNamespace = 0xffffffff
+)
+
+// Command represents a generic NVMe command. Only use this if the command
+// you need is not already wrapped by this library.
+type Command struct {
+ Opcode uint8
+ Flags uint8
+ NamespaceID uint32
+ CDW2, CDW3 uint32
+ Metadata []byte
+ Data []byte
+ CDW10, CDW11, CDW12, CDW13, CDW14, CDW15 uint32
+ Timeout time.Duration
+}
+
+func (d *Device) GetLogPage(ns uint32, logPageIdentifier uint8, logSpecificField uint8, logPageOffset uint64, pageBuf []byte) error {
+ numberOfDwords := len(pageBuf) / 4
+ return d.RawCommand(&Command{
+ Opcode: 0x02,
+ NamespaceID: ns,
+ Data: pageBuf,
+ CDW10: uint32(logPageIdentifier) | uint32(logSpecificField&0xF)<<8 | uint32(numberOfDwords)<<16, // TODO: RAE
+ CDW11: uint32(numberOfDwords >> 16 & 0xffff),
+ CDW12: uint32(logPageOffset & 0xffffffff),
+ CDW13: uint32(logPageOffset >> 32),
+ })
+}