blob: 96054ffff52f94d0e9a3519fade539e085fd4435 [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 }
Lorenz Brunb4f50242023-11-14 21:51:07 +010067 var ioctlPins runtime.Pinner
68 defer ioctlPins.Unpin()
Lorenz Brunfba5da02022-12-15 11:20:47 +000069 if cmd.Data != nil {
70 if len(cmd.Data) > math.MaxUint32 {
71 return errors.New("data buffer larger than uint32, this is unsupported")
72 }
Lorenz Brunb4f50242023-11-14 21:51:07 +010073 ioctlPins.Pin(&cmd.Data[0])
Lorenz Brunfba5da02022-12-15 11:20:47 +000074 cmdRaw.dataLen = uint32(len(cmd.Data))
75 cmdRaw.addr = uint64(uintptr(unsafe.Pointer(&cmd.Data[0])))
76 }
77 if cmd.Metadata != nil {
78 if len(cmd.Metadata) > math.MaxUint32 {
79 return errors.New("metadata buffer larger than uint32, this is unsupported")
80 }
Lorenz Brunb4f50242023-11-14 21:51:07 +010081 ioctlPins.Pin(&cmd.Metadata[0])
Lorenz Brunfba5da02022-12-15 11:20:47 +000082 cmdRaw.metadataLen = uint32(len(cmd.Metadata))
83 cmdRaw.metadata = uint64(uintptr(unsafe.Pointer(&cmd.Metadata[0])))
84 }
85 var errno unix.Errno
86 var status uintptr
87 err = conn.Control(func(fd uintptr) {
88 status, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, nvmeIoctlAdminCmd, uintptr(unsafe.Pointer(&cmdRaw)))
89 })
90 runtime.KeepAlive(cmdRaw)
91 runtime.KeepAlive(cmd.Data)
92 runtime.KeepAlive(cmd.Metadata)
93 if err != nil {
94 return fmt.Errorf("unable to get fd: %w", err)
95 }
96 if errno != 0 {
97 return errno
98 }
99 var commandErr Error
100 commandErr.DoNotRetry = status&(1<<15) != 0 // Bit 31
101 commandErr.More = status&(1<<14) != 0 // Bit 30
102 commandErr.StatusCodeType = uint8((status >> 8) & 0x7) // Bits 27:25
103 commandErr.StatusCode = uint8(status & 0xff) // Bits 24:17
104 // The only success status is in the generic status code set with value 0
105 if commandErr.StatusCodeType != StatusCodeTypeGeneric ||
106 commandErr.StatusCode != 0 {
107 return commandErr
108 }
109 return nil
110}