blob: 45c16035c1f0c8dc58c0d3d87fcc3168ae286818 [file] [log] [blame]
Lorenz Brunbffdda82023-01-10 05:00:36 +00001package scsi
2
3import (
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.
13func (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
88type 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.
105type DeviceType uint8
106
107const (
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
123var 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
139func (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.
150type Version uint8
151
152const (
153 SPC1 = 0x03
154 SPC2 = 0x04
155 SPC3 = 0x05
156 SPC4 = 0x06
157 SPC5 = 0x07
158)
159
160var 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
168func (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.
176type 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
202type VPDPageCode uint8
203
204const (
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
218var 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
232func (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.
242func (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.
279func (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.
293func (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}