m/p/scsi: add SCSI package
This adds a SCSI package to interact with SCSI devices.
It implements a subset of commands from the SPC-5 and SBC-4 standard
useful for discovery and health assessment.
A follow-up will add SAT (SCSI-to-ATA translation) support.
Change-Id: I7f084d26f11d9c951f51051040160e351cf5594c
Reviewed-on: https://review.monogon.dev/c/monogon/+/1066
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/pkg/scsi/scsi_linux.go b/metropolis/pkg/scsi/scsi_linux.go
new file mode 100644
index 0000000..be937d0
--- /dev/null
+++ b/metropolis/pkg/scsi/scsi_linux.go
@@ -0,0 +1,96 @@
+//go:build linux
+
+package scsi
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "math"
+ "runtime"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// RawCommand issues a raw command against the device.
+func (d *Device) RawCommand(c *CommandDataBuffer) error {
+ cdb, err := c.Bytes()
+ if err != nil {
+ return fmt.Errorf("error encoding CDB: %w", err)
+ }
+ conn, err := d.fd.SyscallConn()
+ if err != nil {
+ return fmt.Errorf("unable to get RawConn: %w", err)
+ }
+ var dxferDir int32
+ switch c.DataTransferDirection {
+ case DataTransferNone:
+ dxferDir = SG_DXFER_NONE
+ case DataTransferFromDevice:
+ dxferDir = SG_DXFER_FROM_DEV
+ case DataTransferToDevice:
+ dxferDir = SG_DXFER_TO_DEV
+ case DataTransferBidirectional:
+ dxferDir = SG_DXFER_TO_FROM_DEV
+ default:
+ return errors.New("invalid DataTransferDirection")
+ }
+ var timeout uint32
+ if c.Timeout.Milliseconds() > math.MaxUint32 {
+ timeout = math.MaxUint32
+ }
+ if len(c.Data) > math.MaxUint32 {
+ return errors.New("payload larger than 2^32 bytes, unable to issue")
+ }
+ if len(cdb) > math.MaxUint8 {
+ return errors.New("CDB larger than 2^8 bytes, unable to issue")
+ }
+ var senseBuf [32]byte
+ cmdRaw := sgIOHdr{
+ Interface_id: 'S',
+ Dxfer_direction: dxferDir,
+ Dxfer_len: uint32(len(c.Data)),
+ Dxferp: uintptr(unsafe.Pointer(&c.Data[0])),
+ Cmd_len: uint8(len(cdb)),
+ Cmdp: uintptr(unsafe.Pointer(&cdb[0])),
+ Mx_sb_len: uint8(len(senseBuf)),
+ Sbp: uintptr(unsafe.Pointer(&senseBuf[0])),
+ Timeout: timeout,
+ }
+ var errno unix.Errno
+ err = conn.Control(func(fd uintptr) {
+ _, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, SG_IO, uintptr(unsafe.Pointer(&cmdRaw)))
+ })
+ runtime.KeepAlive(cmdRaw)
+ runtime.KeepAlive(c.Data)
+ runtime.KeepAlive(senseBuf)
+ runtime.KeepAlive(cdb)
+ if err != nil {
+ return fmt.Errorf("unable to get fd: %w", err)
+ }
+ if errno != 0 {
+ return errno
+ }
+ if cmdRaw.Masked_status != 0 {
+ if senseBuf[0] == 0x70 || senseBuf[0] == 0x71 {
+ err := &FixedError{
+ Deferred: senseBuf[0] == 0x71,
+ SenseKey: SenseKey(senseBuf[2] & 0b1111),
+ Information: binary.BigEndian.Uint32(senseBuf[3:7]),
+ }
+ length := int(senseBuf[7])
+ if length >= 4 {
+ err.CommandSpecificInformation = binary.BigEndian.Uint32(senseBuf[8:12])
+ if length >= 6 {
+ err.AdditionalSenseCode = AdditionalSenseCode(uint16(senseBuf[12])<<8 | uint16(senseBuf[13]))
+ }
+ }
+ return err
+ }
+ return &UnknownError{
+ RawSenseData: senseBuf[:],
+ }
+ }
+ return nil
+}