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