blob: e4353ccc7ed817bbbfa7384b3cf0b67e1b6b658b [file] [log] [blame]
Lorenz Brunfba5da02022-12-15 11:20:47 +00001//go:build linux
2
3package nvme
4
5import (
6 "errors"
7 "fmt"
8 "math"
9 "runtime"
10 "unsafe"
11
12 "golang.org/x/sys/unix"
13)
14
15// From @linux//include/uapi/linux/nvme_ioctl.h
16const (
17 nvmeIoctlAdminCmd = 0xC0484E41 // _IOWR('N', 0x41, sizeof cmd)
18)
19
20// From @linux//include/uapi/linux/nvme_ioctl.h
21type passthruCmd struct {
22 // Corresponding to Figure 88
23 opcode uint8
24 flags uint8
25 rsvd1 uint16
26 nsid uint32
27 cdw2 uint32
28 cdw3 uint32
29 metadata uint64
30 addr uint64
31 metadataLen uint32
32 dataLen uint32
33 cdw10 uint32
34 cdw11 uint32
35 cdw12 uint32
36 cdw13 uint32
37 cdw14 uint32
38 cdw15 uint32
39
40 // Linux ioctl-specific
41 timeoutMs uint32
42 result uint32
43}
44
45// RawCommand runs a raw command on the NVMe device.
46// Please note that depending on the payload this can be very dangerous and can
47// cause data loss or even firmware issues.
48func (d *Device) RawCommand(cmd *Command) error {
49 conn, err := d.fd.SyscallConn()
50 if err != nil {
51 return fmt.Errorf("unable to get RawConn: %w", err)
52 }
53 cmdRaw := passthruCmd{
54 opcode: cmd.Opcode,
55 flags: cmd.Flags,
56 nsid: cmd.NamespaceID,
57 cdw2: cmd.CDW2,
58 cdw3: cmd.CDW3,
59 cdw10: cmd.CDW10,
60 cdw11: cmd.CDW11,
61 cdw12: cmd.CDW12,
62 cdw13: cmd.CDW13,
63 cdw14: cmd.CDW14,
64 cdw15: cmd.CDW15,
65 timeoutMs: uint32(cmd.Timeout.Milliseconds()),
66 }
67 // NOTE: Currently this is safe (even if the documentation says otherwise)
68 // as the runtime.KeepAlive call below ensures that the GC cannot clean up
69 // the memory segments passed as data and metadata. This is sufficient as
70 // Go's runtime currently does not use a moving GC, meaning that these
71 // pointers do not get invalidated as long as they are considered alive.
72 // In case Go introduces a moving GC, which they might want to do this will
73 // no longer be safe as a GC-initiated move can happen while the syscall is
74 // running, causing the kernel to overwrite random memory of the calling
75 // process. To avoid this, these data structures need to be pinned. But Go
76 // doesn't have a pinning API yet [1], so all I can do is note this here.
77 // [1] https://github.com/golang/go/issues/46787
78 if cmd.Data != nil {
79 if len(cmd.Data) > math.MaxUint32 {
80 return errors.New("data buffer larger than uint32, this is unsupported")
81 }
82 cmdRaw.dataLen = uint32(len(cmd.Data))
83 cmdRaw.addr = uint64(uintptr(unsafe.Pointer(&cmd.Data[0])))
84 }
85 if cmd.Metadata != nil {
86 if len(cmd.Metadata) > math.MaxUint32 {
87 return errors.New("metadata buffer larger than uint32, this is unsupported")
88 }
89 cmdRaw.metadataLen = uint32(len(cmd.Metadata))
90 cmdRaw.metadata = uint64(uintptr(unsafe.Pointer(&cmd.Metadata[0])))
91 }
92 var errno unix.Errno
93 var status uintptr
94 err = conn.Control(func(fd uintptr) {
95 status, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, nvmeIoctlAdminCmd, uintptr(unsafe.Pointer(&cmdRaw)))
96 })
97 runtime.KeepAlive(cmdRaw)
98 runtime.KeepAlive(cmd.Data)
99 runtime.KeepAlive(cmd.Metadata)
100 if err != nil {
101 return fmt.Errorf("unable to get fd: %w", err)
102 }
103 if errno != 0 {
104 return errno
105 }
106 var commandErr Error
107 commandErr.DoNotRetry = status&(1<<15) != 0 // Bit 31
108 commandErr.More = status&(1<<14) != 0 // Bit 30
109 commandErr.StatusCodeType = uint8((status >> 8) & 0x7) // Bits 27:25
110 commandErr.StatusCode = uint8(status & 0xff) // Bits 24:17
111 // The only success status is in the generic status code set with value 0
112 if commandErr.StatusCodeType != StatusCodeTypeGeneric ||
113 commandErr.StatusCode != 0 {
114 return commandErr
115 }
116 return nil
117}