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