blob: 45c16035c1f0c8dc58c0d3d87fcc3168ae286818 [file] [log] [blame] [edit]
package scsi
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math"
)
// Inquiry queries the device for various metadata about its identity and
// supported features.
func (d Device) Inquiry() (*InquiryData, error) {
data := make([]byte, 96)
var req [4]byte
binary.BigEndian.PutUint16(req[2:4], uint16(len(data)))
if err := d.RawCommand(&CommandDataBuffer{
OperationCode: InquiryOp,
Request: req[:],
Data: data,
DataTransferDirection: DataTransferFromDevice,
}); err != nil {
return nil, fmt.Errorf("error during INQUIRY: %w", err)
}
resLen := int64(data[4]) + 5
// Use LimitReader to not have to deal with out-of-bounds slices
rawReader := io.LimitReader(bytes.NewReader(data), resLen)
var raw inquiryDataRaw
if err := binary.Read(rawReader, binary.BigEndian, &raw); err != nil {
if err == io.ErrUnexpectedEOF {
return nil, fmt.Errorf("response to INQUIRY is smaller than %d bytes, very old or broken device", binary.Size(raw))
}
panic(err) // Read from memory, shouldn't be possible to hit
}
var res InquiryData
res.PeriperalQualifier = (raw.PeripheralData >> 5) & 0b111
res.PeripheralDeviceType = DeviceType(raw.PeripheralData & 0b11111)
res.RemovableMedium = (raw.Flags1 & 1 << 0) != 0
res.LogicalUnitConglomerate = (raw.Flags1 & 1 << 1) != 0
res.CommandSetVersion = Version(raw.Version)
res.NormalACASupported = (raw.Flags2 & 1 << 5) != 0
res.HistoricalSupport = (raw.Flags2 & 1 << 4) != 0
res.ResponseDataFormat = raw.Flags2 & 0b1111
res.SCCSupported = (raw.Flags3 & 1 << 7) != 0
res.TargetPortGroupSupport = (raw.Flags3 >> 4) & 0b11
res.ThirdPartyCopySupport = (raw.Flags3 & 1 << 3) != 0
res.HasProtectionInfo = (raw.Flags3 & 1 << 0) != 0
res.HasEnclosureServices = (raw.Flags4 & 1 << 6) != 0
res.VendorFeature1 = (raw.Flags4 & 1 << 5) != 0
res.HasMultipleSCSIPorts = (raw.Flags4 & 1 << 4) != 0
res.CmdQueue = (raw.Flags5 & 1 << 1) != 0
res.VendorFeature2 = (raw.Flags5 & 1 << 0) != 0
res.Vendor = string(bytes.TrimRight(raw.Vendor[:], " "))
res.Product = string(bytes.TrimRight(raw.Product[:], " "))
res.ProductRevisionLevel = string(bytes.TrimRight(raw.ProductRevisionLevel[:], " "))
// Read rest conditionally, as it might not be present on every device
var vendorSpecific bytes.Buffer
_, err := io.CopyN(&vendorSpecific, rawReader, 20)
res.VendorSpecific = vendorSpecific.Bytes()
if err == io.EOF {
return &res, nil
}
if err != nil {
panic(err) // Mem2Mem copy, can't really happen
}
var padding [2]byte
if _, err := io.ReadFull(rawReader, padding[:]); err != nil {
if err == io.ErrUnexpectedEOF {
return &res, nil
}
}
for i := 0; i < 8; i++ {
var versionDesc uint16
if err := binary.Read(rawReader, binary.BigEndian, &versionDesc); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
return &res, nil
}
}
res.VersionDescriptors = append(res.VersionDescriptors, versionDesc)
}
return &res, nil
}
// Table 148, only first 36 mandatory bytes
type inquiryDataRaw struct {
PeripheralData uint8
Flags1 uint8
Version uint8
Flags2 uint8
AdditionalLength uint8 // n-4
Flags3 uint8
Flags4 uint8
Flags5 uint8
Vendor [8]byte
Product [16]byte
ProductRevisionLevel [4]byte
}
// DeviceType represents a SCSI peripheral device type, which
// can be used to determine the command set to use to control
// the device. See Table 150 in the standard.
type DeviceType uint8
const (
TypeBlockDevice DeviceType = 0x00
TypeSequentialAccessDevice DeviceType = 0x01
TypeProcessor DeviceType = 0x03
TypeOpticalDrive DeviceType = 0x05
TypeOpticalMemory DeviceType = 0x07
TypeMediaChanger DeviceType = 0x08
TypeArrayController DeviceType = 0x0c
TypeEncloseServices DeviceType = 0x0d
TypeOpticalCardRWDevice DeviceType = 0x0f
TypeObjectStorageDevice DeviceType = 0x11
TypeAutomationDriveInterface DeviceType = 0x12
TypeZonedBlockDevice DeviceType = 0x14
TypeUnknownDevice DeviceType = 0x1f
)
var deviceTypeDesc = map[DeviceType]string{
TypeBlockDevice: "Block Device",
TypeSequentialAccessDevice: "Sequential Access Device",
TypeProcessor: "Processor",
TypeOpticalDrive: "Optical Drive",
TypeOpticalMemory: "Optical Memory",
TypeMediaChanger: "Media Changer",
TypeArrayController: "Array Controller",
TypeEncloseServices: "Enclosure Services",
TypeOpticalCardRWDevice: "Optical Card reader/writer device",
TypeObjectStorageDevice: "Object-based Storage Device",
TypeAutomationDriveInterface: "Automation/Drive Interface",
TypeZonedBlockDevice: "Zoned Block Device",
TypeUnknownDevice: "Unknown or no device",
}
func (d DeviceType) String() string {
if str, ok := deviceTypeDesc[d]; ok {
return str
}
return fmt.Sprintf("unknown device type %xh", uint8(d))
}
// Version represents a specific standardized version of the SCSI
// primary command set (SPC). The enum values are sorted, so
// for example version >= SPC3 is true for SPC-3 and all later
// standards. See table 151 in the standard.
type Version uint8
const (
SPC1 = 0x03
SPC2 = 0x04
SPC3 = 0x05
SPC4 = 0x06
SPC5 = 0x07
)
var versionDesc = map[Version]string{
SPC1: "SPC-1 (INCITS 301-1997)",
SPC2: "SPC-2 (INCITS 351-2001)",
SPC3: "SPC-3 (INCITS 408-2005)",
SPC4: "SPC-4 (INCITS 513-2015)",
SPC5: "SPC-5 (INCITS 502-2019)",
}
func (v Version) String() string {
if str, ok := versionDesc[v]; ok {
return str
}
return fmt.Sprintf("unknown version %xh", uint8(v))
}
// InquiryData contains data returned by the INQUIRY command.
type InquiryData struct {
PeriperalQualifier uint8
PeripheralDeviceType DeviceType
RemovableMedium bool
LogicalUnitConglomerate bool
CommandSetVersion Version
NormalACASupported bool
HistoricalSupport bool
ResponseDataFormat uint8
SCCSupported bool
TargetPortGroupSupport uint8
ThirdPartyCopySupport bool
HasProtectionInfo bool
HasEnclosureServices bool
VendorFeature1 bool
HasMultipleSCSIPorts bool
CmdQueue bool
VendorFeature2 bool
Vendor string
Product string
ProductRevisionLevel string
VendorSpecific []byte
VersionDescriptors []uint16
}
// Table 498
type VPDPageCode uint8
const (
SupportedVPDs VPDPageCode = 0x00
UnitSerialNumberVPD VPDPageCode = 0x80
DeviceIdentificationVPD VPDPageCode = 0x83
SoftwareInterfaceIdentificationVPD VPDPageCode = 0x84
ManagementNetworkAddressesVPD VPDPageCode = 0x85
ExtendedINQUIRYDataVPD VPDPageCode = 0x86
ModePagePolicyVPD VPDPageCode = 0x87
SCSIPortsVPD VPDPageCode = 0x88
ATAInformationVPD VPDPageCode = 0x89
PowerConditionVPD VPDPageCode = 0x8a
DeviceConstituentsVPD VPDPageCode = 0x8b
)
var vpdPageCodeDesc = map[VPDPageCode]string{
SupportedVPDs: "Supported VPD Pages",
UnitSerialNumberVPD: "Unit Serial Number",
DeviceIdentificationVPD: "Device Identification",
SoftwareInterfaceIdentificationVPD: "Software Interface Identification",
ManagementNetworkAddressesVPD: "Management Network Addresses",
ExtendedINQUIRYDataVPD: "Extended INQUIRY Data",
ModePagePolicyVPD: "Mode Page Policy",
SCSIPortsVPD: "SCSI Ports",
ATAInformationVPD: "ATA Information",
PowerConditionVPD: "Power Condition",
DeviceConstituentsVPD: "Device Constituents",
}
func (v VPDPageCode) String() string {
if str, ok := vpdPageCodeDesc[v]; ok {
return str
}
return fmt.Sprintf("Page %xh", uint8(v))
}
// InquiryVPD requests a specified Vital Product Description Page from the
// device. If the size of the page is known in advance, initialSize should be
// set to a non-zero value to make the query more efficient.
func (d *Device) InquiryVPD(pageCode VPDPageCode, initialSize uint16) ([]byte, error) {
var bufferSize uint16 = 254
if initialSize > 0 {
bufferSize = initialSize
}
for {
data := make([]byte, bufferSize)
var req [4]byte
req[0] = 0b1 // Enable Vital Product Data
req[1] = uint8(pageCode)
binary.BigEndian.PutUint16(req[2:4], uint16(len(data)))
if err := d.RawCommand(&CommandDataBuffer{
OperationCode: InquiryOp,
Request: req[:],
Data: data,
DataTransferDirection: DataTransferFromDevice,
}); err != nil {
return nil, fmt.Errorf("error during INQUIRY VPD: %w", err)
}
if data[1] != uint8(pageCode) {
return nil, fmt.Errorf("requested VPD page %x, got %x", pageCode, data[1])
}
pageLength := binary.BigEndian.Uint16(data[2:4])
if pageLength > math.MaxUint16-4 {
// Guard against uint16 overflows, this cannot be requested anyway
return nil, fmt.Errorf("device VPD page is too long (%d bytes)", pageLength)
}
if pageLength > uint16(len(data)-4) {
bufferSize = pageLength + 4
continue
}
return data[4 : pageLength+4], nil
}
}
// SupportedVPDPages returns the list of supported vital product data pages
// supported by the device.
func (d *Device) SupportedVPDPages() (map[VPDPageCode]bool, error) {
res, err := d.InquiryVPD(SupportedVPDs, 0)
if err != nil {
return nil, err
}
supportedPages := make(map[VPDPageCode]bool)
for _, p := range res {
supportedPages[VPDPageCode(p)] = true
}
return supportedPages, nil
}
// UnitSerialNumber returns the serial number of the device. Only available if
// UnitSerialNumberVPD is a supported VPD page.
func (d *Device) UnitSerialNumber() (string, error) {
serial, err := d.InquiryVPD(UnitSerialNumberVPD, 0)
if err != nil {
return "", err
}
return string(bytes.Trim(serial, " \x00")), nil
}