| 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 |
| } |