Lorenz Brun | bffdda8 | 2023-01-10 05:00:36 +0000 | [diff] [blame^] | 1 | package scsi |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/binary" |
| 6 | "fmt" |
| 7 | "io" |
| 8 | "math" |
| 9 | ) |
| 10 | |
| 11 | // Inquiry queries the device for various metadata about its identity and |
| 12 | // supported features. |
| 13 | func (d Device) Inquiry() (*InquiryData, error) { |
| 14 | data := make([]byte, 96) |
| 15 | var req [4]byte |
| 16 | binary.BigEndian.PutUint16(req[2:4], uint16(len(data))) |
| 17 | if err := d.RawCommand(&CommandDataBuffer{ |
| 18 | OperationCode: InquiryOp, |
| 19 | Request: req[:], |
| 20 | Data: data, |
| 21 | DataTransferDirection: DataTransferFromDevice, |
| 22 | }); err != nil { |
| 23 | return nil, fmt.Errorf("error during INQUIRY: %w", err) |
| 24 | } |
| 25 | resLen := int64(data[4]) + 5 |
| 26 | // Use LimitReader to not have to deal with out-of-bounds slices |
| 27 | rawReader := io.LimitReader(bytes.NewReader(data), resLen) |
| 28 | var raw inquiryDataRaw |
| 29 | if err := binary.Read(rawReader, binary.BigEndian, &raw); err != nil { |
| 30 | if err == io.ErrUnexpectedEOF { |
| 31 | return nil, fmt.Errorf("response to INQUIRY is smaller than %d bytes, very old or broken device", binary.Size(raw)) |
| 32 | } |
| 33 | panic(err) // Read from memory, shouldn't be possible to hit |
| 34 | } |
| 35 | |
| 36 | var res InquiryData |
| 37 | res.PeriperalQualifier = (raw.PeripheralData >> 5) & 0b111 |
| 38 | res.PeripheralDeviceType = DeviceType(raw.PeripheralData & 0b11111) |
| 39 | res.RemovableMedium = (raw.Flags1 & 1 << 0) != 0 |
| 40 | res.LogicalUnitConglomerate = (raw.Flags1 & 1 << 1) != 0 |
| 41 | res.CommandSetVersion = Version(raw.Version) |
| 42 | res.NormalACASupported = (raw.Flags2 & 1 << 5) != 0 |
| 43 | res.HistoricalSupport = (raw.Flags2 & 1 << 4) != 0 |
| 44 | res.ResponseDataFormat = raw.Flags2 & 0b1111 |
| 45 | res.SCCSupported = (raw.Flags3 & 1 << 7) != 0 |
| 46 | res.TargetPortGroupSupport = (raw.Flags3 >> 4) & 0b11 |
| 47 | res.ThirdPartyCopySupport = (raw.Flags3 & 1 << 3) != 0 |
| 48 | res.HasProtectionInfo = (raw.Flags3 & 1 << 0) != 0 |
| 49 | res.HasEnclosureServices = (raw.Flags4 & 1 << 6) != 0 |
| 50 | res.VendorFeature1 = (raw.Flags4 & 1 << 5) != 0 |
| 51 | res.HasMultipleSCSIPorts = (raw.Flags4 & 1 << 4) != 0 |
| 52 | res.CmdQueue = (raw.Flags5 & 1 << 1) != 0 |
| 53 | res.VendorFeature2 = (raw.Flags5 & 1 << 0) != 0 |
| 54 | res.Vendor = string(bytes.TrimRight(raw.Vendor[:], " ")) |
| 55 | res.Product = string(bytes.TrimRight(raw.Product[:], " ")) |
| 56 | res.ProductRevisionLevel = string(bytes.TrimRight(raw.ProductRevisionLevel[:], " ")) |
| 57 | |
| 58 | // Read rest conditionally, as it might not be present on every device |
| 59 | var vendorSpecific bytes.Buffer |
| 60 | _, err := io.CopyN(&vendorSpecific, rawReader, 20) |
| 61 | res.VendorSpecific = vendorSpecific.Bytes() |
| 62 | if err == io.EOF { |
| 63 | return &res, nil |
| 64 | } |
| 65 | if err != nil { |
| 66 | panic(err) // Mem2Mem copy, can't really happen |
| 67 | } |
| 68 | var padding [2]byte |
| 69 | if _, err := io.ReadFull(rawReader, padding[:]); err != nil { |
| 70 | if err == io.ErrUnexpectedEOF { |
| 71 | return &res, nil |
| 72 | } |
| 73 | } |
| 74 | for i := 0; i < 8; i++ { |
| 75 | var versionDesc uint16 |
| 76 | if err := binary.Read(rawReader, binary.BigEndian, &versionDesc); err != nil { |
| 77 | if err == io.EOF || err == io.ErrUnexpectedEOF { |
| 78 | return &res, nil |
| 79 | } |
| 80 | } |
| 81 | res.VersionDescriptors = append(res.VersionDescriptors, versionDesc) |
| 82 | } |
| 83 | |
| 84 | return &res, nil |
| 85 | } |
| 86 | |
| 87 | // Table 148, only first 36 mandatory bytes |
| 88 | type inquiryDataRaw struct { |
| 89 | PeripheralData uint8 |
| 90 | Flags1 uint8 |
| 91 | Version uint8 |
| 92 | Flags2 uint8 |
| 93 | AdditionalLength uint8 // n-4 |
| 94 | Flags3 uint8 |
| 95 | Flags4 uint8 |
| 96 | Flags5 uint8 |
| 97 | Vendor [8]byte |
| 98 | Product [16]byte |
| 99 | ProductRevisionLevel [4]byte |
| 100 | } |
| 101 | |
| 102 | // DeviceType represents a SCSI peripheral device type, which |
| 103 | // can be used to determine the command set to use to control |
| 104 | // the device. See Table 150 in the standard. |
| 105 | type DeviceType uint8 |
| 106 | |
| 107 | const ( |
| 108 | TypeBlockDevice DeviceType = 0x00 |
| 109 | TypeSequentialAccessDevice DeviceType = 0x01 |
| 110 | TypeProcessor DeviceType = 0x03 |
| 111 | TypeOpticalDrive DeviceType = 0x05 |
| 112 | TypeOpticalMemory DeviceType = 0x07 |
| 113 | TypeMediaChanger DeviceType = 0x08 |
| 114 | TypeArrayController DeviceType = 0x0c |
| 115 | TypeEncloseServices DeviceType = 0x0d |
| 116 | TypeOpticalCardRWDevice DeviceType = 0x0f |
| 117 | TypeObjectStorageDevice DeviceType = 0x11 |
| 118 | TypeAutomationDriveInterface DeviceType = 0x12 |
| 119 | TypeZonedBlockDevice DeviceType = 0x14 |
| 120 | TypeUnknownDevice DeviceType = 0x1f |
| 121 | ) |
| 122 | |
| 123 | var deviceTypeDesc = map[DeviceType]string{ |
| 124 | TypeBlockDevice: "Block Device", |
| 125 | TypeSequentialAccessDevice: "Sequential Access Device", |
| 126 | TypeProcessor: "Processor", |
| 127 | TypeOpticalDrive: "Optical Drive", |
| 128 | TypeOpticalMemory: "Optical Memory", |
| 129 | TypeMediaChanger: "Media Changer", |
| 130 | TypeArrayController: "Array Controller", |
| 131 | TypeEncloseServices: "Enclosure Services", |
| 132 | TypeOpticalCardRWDevice: "Optical Card reader/writer device", |
| 133 | TypeObjectStorageDevice: "Object-based Storage Device", |
| 134 | TypeAutomationDriveInterface: "Automation/Drive Interface", |
| 135 | TypeZonedBlockDevice: "Zoned Block Device", |
| 136 | TypeUnknownDevice: "Unknown or no device", |
| 137 | } |
| 138 | |
| 139 | func (d DeviceType) String() string { |
| 140 | if str, ok := deviceTypeDesc[d]; ok { |
| 141 | return str |
| 142 | } |
| 143 | return fmt.Sprintf("unknown device type %xh", uint8(d)) |
| 144 | } |
| 145 | |
| 146 | // Version represents a specific standardized version of the SCSI |
| 147 | // primary command set (SPC). The enum values are sorted, so |
| 148 | // for example version >= SPC3 is true for SPC-3 and all later |
| 149 | // standards. See table 151 in the standard. |
| 150 | type Version uint8 |
| 151 | |
| 152 | const ( |
| 153 | SPC1 = 0x03 |
| 154 | SPC2 = 0x04 |
| 155 | SPC3 = 0x05 |
| 156 | SPC4 = 0x06 |
| 157 | SPC5 = 0x07 |
| 158 | ) |
| 159 | |
| 160 | var versionDesc = map[Version]string{ |
| 161 | SPC1: "SPC-1 (INCITS 301-1997)", |
| 162 | SPC2: "SPC-2 (INCITS 351-2001)", |
| 163 | SPC3: "SPC-3 (INCITS 408-2005)", |
| 164 | SPC4: "SPC-4 (INCITS 513-2015)", |
| 165 | SPC5: "SPC-5 (INCITS 502-2019)", |
| 166 | } |
| 167 | |
| 168 | func (v Version) String() string { |
| 169 | if str, ok := versionDesc[v]; ok { |
| 170 | return str |
| 171 | } |
| 172 | return fmt.Sprintf("unknown version %xh", uint8(v)) |
| 173 | } |
| 174 | |
| 175 | // InquiryData contains data returned by the INQUIRY command. |
| 176 | type InquiryData struct { |
| 177 | PeriperalQualifier uint8 |
| 178 | PeripheralDeviceType DeviceType |
| 179 | RemovableMedium bool |
| 180 | LogicalUnitConglomerate bool |
| 181 | CommandSetVersion Version |
| 182 | NormalACASupported bool |
| 183 | HistoricalSupport bool |
| 184 | ResponseDataFormat uint8 |
| 185 | SCCSupported bool |
| 186 | TargetPortGroupSupport uint8 |
| 187 | ThirdPartyCopySupport bool |
| 188 | HasProtectionInfo bool |
| 189 | HasEnclosureServices bool |
| 190 | VendorFeature1 bool |
| 191 | HasMultipleSCSIPorts bool |
| 192 | CmdQueue bool |
| 193 | VendorFeature2 bool |
| 194 | Vendor string |
| 195 | Product string |
| 196 | ProductRevisionLevel string |
| 197 | VendorSpecific []byte |
| 198 | VersionDescriptors []uint16 |
| 199 | } |
| 200 | |
| 201 | // Table 498 |
| 202 | type VPDPageCode uint8 |
| 203 | |
| 204 | const ( |
| 205 | SupportedVPDs VPDPageCode = 0x00 |
| 206 | UnitSerialNumberVPD VPDPageCode = 0x80 |
| 207 | DeviceIdentificationVPD VPDPageCode = 0x83 |
| 208 | SoftwareInterfaceIdentificationVPD VPDPageCode = 0x84 |
| 209 | ManagementNetworkAddressesVPD VPDPageCode = 0x85 |
| 210 | ExtendedINQUIRYDataVPD VPDPageCode = 0x86 |
| 211 | ModePagePolicyVPD VPDPageCode = 0x87 |
| 212 | SCSIPortsVPD VPDPageCode = 0x88 |
| 213 | ATAInformationVPD VPDPageCode = 0x89 |
| 214 | PowerConditionVPD VPDPageCode = 0x8a |
| 215 | DeviceConstituentsVPD VPDPageCode = 0x8b |
| 216 | ) |
| 217 | |
| 218 | var vpdPageCodeDesc = map[VPDPageCode]string{ |
| 219 | SupportedVPDs: "Supported VPD Pages", |
| 220 | UnitSerialNumberVPD: "Unit Serial Number", |
| 221 | DeviceIdentificationVPD: "Device Identification", |
| 222 | SoftwareInterfaceIdentificationVPD: "Software Interface Identification", |
| 223 | ManagementNetworkAddressesVPD: "Management Network Addresses", |
| 224 | ExtendedINQUIRYDataVPD: "Extended INQUIRY Data", |
| 225 | ModePagePolicyVPD: "Mode Page Policy", |
| 226 | SCSIPortsVPD: "SCSI Ports", |
| 227 | ATAInformationVPD: "ATA Information", |
| 228 | PowerConditionVPD: "Power Condition", |
| 229 | DeviceConstituentsVPD: "Device Constituents", |
| 230 | } |
| 231 | |
| 232 | func (v VPDPageCode) String() string { |
| 233 | if str, ok := vpdPageCodeDesc[v]; ok { |
| 234 | return str |
| 235 | } |
| 236 | return fmt.Sprintf("Page %xh", uint8(v)) |
| 237 | } |
| 238 | |
| 239 | // InquiryVPD requests a specified Vital Product Description Page from the |
| 240 | // device. If the size of the page is known in advance, initialSize should be |
| 241 | // set to a non-zero value to make the query more efficient. |
| 242 | func (d *Device) InquiryVPD(pageCode VPDPageCode, initialSize uint16) ([]byte, error) { |
| 243 | var bufferSize uint16 = 254 |
| 244 | if initialSize > 0 { |
| 245 | bufferSize = initialSize |
| 246 | } |
| 247 | for { |
| 248 | data := make([]byte, bufferSize) |
| 249 | var req [4]byte |
| 250 | req[0] = 0b1 // Enable Vital Product Data |
| 251 | req[1] = uint8(pageCode) |
| 252 | binary.BigEndian.PutUint16(req[2:4], uint16(len(data))) |
| 253 | if err := d.RawCommand(&CommandDataBuffer{ |
| 254 | OperationCode: InquiryOp, |
| 255 | Request: req[:], |
| 256 | Data: data, |
| 257 | DataTransferDirection: DataTransferFromDevice, |
| 258 | }); err != nil { |
| 259 | return nil, fmt.Errorf("error during INQUIRY VPD: %w", err) |
| 260 | } |
| 261 | if data[1] != uint8(pageCode) { |
| 262 | return nil, fmt.Errorf("requested VPD page %x, got %x", pageCode, data[1]) |
| 263 | } |
| 264 | pageLength := binary.BigEndian.Uint16(data[2:4]) |
| 265 | if pageLength > math.MaxUint16-4 { |
| 266 | // Guard against uint16 overflows, this cannot be requested anyway |
| 267 | return nil, fmt.Errorf("device VPD page is too long (%d bytes)", pageLength) |
| 268 | } |
| 269 | if pageLength > uint16(len(data)-4) { |
| 270 | bufferSize = pageLength + 4 |
| 271 | continue |
| 272 | } |
| 273 | return data[4 : pageLength+4], nil |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | // SupportedVPDPages returns the list of supported vital product data pages |
| 278 | // supported by the device. |
| 279 | func (d *Device) SupportedVPDPages() (map[VPDPageCode]bool, error) { |
| 280 | res, err := d.InquiryVPD(SupportedVPDs, 0) |
| 281 | if err != nil { |
| 282 | return nil, err |
| 283 | } |
| 284 | supportedPages := make(map[VPDPageCode]bool) |
| 285 | for _, p := range res { |
| 286 | supportedPages[VPDPageCode(p)] = true |
| 287 | } |
| 288 | return supportedPages, nil |
| 289 | } |
| 290 | |
| 291 | // UnitSerialNumber returns the serial number of the device. Only available if |
| 292 | // UnitSerialNumberVPD is a supported VPD page. |
| 293 | func (d *Device) UnitSerialNumber() (string, error) { |
| 294 | serial, err := d.InquiryVPD(UnitSerialNumberVPD, 0) |
| 295 | if err != nil { |
| 296 | return "", err |
| 297 | } |
| 298 | return string(bytes.Trim(serial, " \x00")), nil |
| 299 | } |