blob: 0fd45dd98aa41a9878e62e27c079ffea57c3b452 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Brunfba5da02022-12-15 11:20:47 +00004//go:build linux
5
6package nvme
7
8import (
9 "errors"
10 "fmt"
11 "math"
12 "runtime"
13 "unsafe"
14
15 "golang.org/x/sys/unix"
16)
17
18// From @linux//include/uapi/linux/nvme_ioctl.h
19const (
20 nvmeIoctlAdminCmd = 0xC0484E41 // _IOWR('N', 0x41, sizeof cmd)
21)
22
23// From @linux//include/uapi/linux/nvme_ioctl.h
24type passthruCmd struct {
25 // Corresponding to Figure 88
26 opcode uint8
27 flags uint8
28 rsvd1 uint16
29 nsid uint32
30 cdw2 uint32
31 cdw3 uint32
32 metadata uint64
33 addr uint64
34 metadataLen uint32
35 dataLen uint32
36 cdw10 uint32
37 cdw11 uint32
38 cdw12 uint32
39 cdw13 uint32
40 cdw14 uint32
41 cdw15 uint32
42
43 // Linux ioctl-specific
44 timeoutMs uint32
45 result uint32
46}
47
48// RawCommand runs a raw command on the NVMe device.
49// Please note that depending on the payload this can be very dangerous and can
50// cause data loss or even firmware issues.
51func (d *Device) RawCommand(cmd *Command) error {
52 conn, err := d.fd.SyscallConn()
53 if err != nil {
54 return fmt.Errorf("unable to get RawConn: %w", err)
55 }
56 cmdRaw := passthruCmd{
57 opcode: cmd.Opcode,
58 flags: cmd.Flags,
59 nsid: cmd.NamespaceID,
60 cdw2: cmd.CDW2,
61 cdw3: cmd.CDW3,
62 cdw10: cmd.CDW10,
63 cdw11: cmd.CDW11,
64 cdw12: cmd.CDW12,
65 cdw13: cmd.CDW13,
66 cdw14: cmd.CDW14,
67 cdw15: cmd.CDW15,
68 timeoutMs: uint32(cmd.Timeout.Milliseconds()),
69 }
Lorenz Brunb4f50242023-11-14 21:51:07 +010070 var ioctlPins runtime.Pinner
71 defer ioctlPins.Unpin()
Lorenz Brunfba5da02022-12-15 11:20:47 +000072 if cmd.Data != nil {
73 if len(cmd.Data) > math.MaxUint32 {
74 return errors.New("data buffer larger than uint32, this is unsupported")
75 }
Lorenz Brunb4f50242023-11-14 21:51:07 +010076 ioctlPins.Pin(&cmd.Data[0])
Lorenz Brunfba5da02022-12-15 11:20:47 +000077 cmdRaw.dataLen = uint32(len(cmd.Data))
78 cmdRaw.addr = uint64(uintptr(unsafe.Pointer(&cmd.Data[0])))
79 }
80 if cmd.Metadata != nil {
81 if len(cmd.Metadata) > math.MaxUint32 {
82 return errors.New("metadata buffer larger than uint32, this is unsupported")
83 }
Lorenz Brunb4f50242023-11-14 21:51:07 +010084 ioctlPins.Pin(&cmd.Metadata[0])
Lorenz Brunfba5da02022-12-15 11:20:47 +000085 cmdRaw.metadataLen = uint32(len(cmd.Metadata))
86 cmdRaw.metadata = uint64(uintptr(unsafe.Pointer(&cmd.Metadata[0])))
87 }
88 var errno unix.Errno
89 var status uintptr
90 err = conn.Control(func(fd uintptr) {
91 status, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, nvmeIoctlAdminCmd, uintptr(unsafe.Pointer(&cmdRaw)))
92 })
93 runtime.KeepAlive(cmdRaw)
94 runtime.KeepAlive(cmd.Data)
95 runtime.KeepAlive(cmd.Metadata)
96 if err != nil {
97 return fmt.Errorf("unable to get fd: %w", err)
98 }
99 if errno != 0 {
100 return errno
101 }
102 var commandErr Error
103 commandErr.DoNotRetry = status&(1<<15) != 0 // Bit 31
104 commandErr.More = status&(1<<14) != 0 // Bit 30
105 commandErr.StatusCodeType = uint8((status >> 8) & 0x7) // Bits 27:25
106 commandErr.StatusCode = uint8(status & 0xff) // Bits 24:17
107 // The only success status is in the generic status code set with value 0
108 if commandErr.StatusCodeType != StatusCodeTypeGeneric ||
109 commandErr.StatusCode != 0 {
110 return commandErr
111 }
112 return nil
113}