blob: fba05120f6d720a32d4ae16ca3f0fab8dc95ab04 [file] [log] [blame]
Lorenz Brunbffdda82023-01-10 05:00:36 +00001package scsi
2
3// Written against SBC-4
4// Contains SCSI block device specific commands.
5
6import (
7 "bytes"
8 "encoding/binary"
9 "errors"
10 "fmt"
11 "math"
12)
13
14// ReadDefectDataLBA reads the primary (manufacturer) and/or grown defect list
15// in LBA format. This is commonly used on SSDs and generally returns an error
16// on spinning drives.
17func (d *Device) ReadDefectDataLBA(plist, glist bool) ([]uint64, error) {
18 data := make([]byte, 4096)
19 var req [8]byte
20 if plist {
21 req[1] |= 1 << 4
22 }
23 if glist {
24 req[1] |= 1 << 3
25 }
26 defectListFormat := 0b011
27 req[1] |= byte(defectListFormat)
28 binary.BigEndian.PutUint16(req[6:8], uint16(len(data)))
29 if err := d.RawCommand(&CommandDataBuffer{
30 OperationCode: ReadDefectDataOp,
31 Request: req[:],
32 Data: data,
33 DataTransferDirection: DataTransferFromDevice,
34 }); err != nil {
35 var fixedErr *FixedError
36 if errors.As(err, &fixedErr) && fixedErr.SenseKey == RecoveredError && fixedErr.AdditionalSenseCode == DefectListNotFound {
37 return nil, fmt.Errorf("error during LOG SENSE: unsupported defect list format, device returned %03bb", data[1]&0b111)
38 }
39 return nil, fmt.Errorf("error during LOG SENSE: %w", err)
40 }
41 if data[1]&0b111 != byte(defectListFormat) {
42 return nil, fmt.Errorf("device returned wrong defect list format, requested %03bb, got %03bb", defectListFormat, data[1]&0b111)
43 }
44 defectListLength := binary.BigEndian.Uint16(data[2:4])
45 if defectListLength%8 != 0 {
46 return nil, errors.New("returned defect list not divisible by array item size")
47 }
Jan Schär2c388502024-04-10 14:48:39 +020048 if len(data) < int(defectListLength)+4 {
49 return nil, errors.New("returned defect list longer than buffer")
50 }
Lorenz Brunbffdda82023-01-10 05:00:36 +000051 res := make([]uint64, defectListLength/8)
52 if err := binary.Read(bytes.NewReader(data[4:]), binary.BigEndian, &res); err != nil {
53 panic(err)
54 }
55 return res, nil
56}
57
58const (
59 // AllSectors is a magic sector number indicating that it applies to all
60 // sectors on the track.
61 AllSectors = math.MaxUint16
62)
63
64// PhysicalSectorFormatAddress represents a physical sector (or the the whole
65// track if SectorNumber == AllSectors) on a spinning hard drive.
66type PhysicalSectorFormatAddress struct {
67 CylinderNumber uint32
68 HeadNumber uint8
69 SectorNumber uint32
70 MultiAddressDescriptorStart bool
71}
72
73func parseExtendedPhysicalSectorFormatAddress(buf []byte) (p PhysicalSectorFormatAddress) {
74 p.CylinderNumber = uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])
75 p.HeadNumber = buf[3]
76 p.MultiAddressDescriptorStart = buf[4]&(1<<7) != 0
77 p.SectorNumber = uint32(buf[4]&0b1111)<<24 | uint32(buf[5])<<16 | uint32(buf[6])<<8 | uint32(buf[7])
78 return
79}
80
81func parsePhysicalSectorFormatAddress(buf []byte) (p PhysicalSectorFormatAddress) {
82 p.CylinderNumber = uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])
83 p.HeadNumber = buf[3]
84 p.SectorNumber = binary.BigEndian.Uint32(buf[4:8])
85 return
86}
87
88// ReadDefectDataPhysical reads the primary (manufacturer) and/or grown defect
89// list in physical format.
90// This is only defined for spinning drives, returning an error on SSDs.
91func (d *Device) ReadDefectDataPhysical(plist, glist bool) ([]PhysicalSectorFormatAddress, error) {
92 data := make([]byte, 4096)
93 var req [8]byte
94 if plist {
95 req[1] |= 1 << 4
96 }
97 if glist {
98 req[1] |= 1 << 3
99 }
100 defectListFormat := 0b101
101 req[1] |= byte(defectListFormat)
102 binary.BigEndian.PutUint16(req[6:8], uint16(len(data)))
103 if err := d.RawCommand(&CommandDataBuffer{
104 OperationCode: ReadDefectDataOp,
105 Request: req[:],
106 Data: data,
107 DataTransferDirection: DataTransferFromDevice,
108 }); err != nil {
109 var fixedErr *FixedError
110 if errors.As(err, &fixedErr) && fixedErr.SenseKey == RecoveredError && fixedErr.AdditionalSenseCode == DefectListNotFound {
111 return nil, fmt.Errorf("error during LOG SENSE: unsupported defect list format, device returned %03bb", data[1]&0b111)
112 }
113 return nil, fmt.Errorf("error during LOG SENSE: %w", err)
114 }
115 if data[1]&0b111 != byte(defectListFormat) {
116 return nil, fmt.Errorf("device returned wrong defect list format, requested %03bb, got %03bb", defectListFormat, data[1]&0b111)
117 }
118 defectListLength := binary.BigEndian.Uint16(data[2:4])
119 if defectListLength%8 != 0 {
120 return nil, errors.New("returned defect list not divisible by array item size")
121 }
122 if len(data) < int(defectListLength)+4 {
123 return nil, errors.New("returned defect list longer than buffer")
124 }
125 res := make([]PhysicalSectorFormatAddress, defectListLength/8)
126 data = data[4:]
127 for i := 0; i < int(defectListLength)/8; i++ {
128 res[i] = parsePhysicalSectorFormatAddress(data[i*8 : (i+1)*8])
129 }
130 return res, nil
131}
132
133type SolidStateMediaHealth struct {
134 // PercentageUsedEnduranceIndicator is a value which represents a
135 // vendor-specific wear estimate of the solid state medium.
136 // A new device starts at 0, at 100 the device is considered end-of-life.
137 // Values up to 255 are possible.
138 PercentageUsedEnduranceIndicator uint8
139}
140
141// SolidStateMediaHealth reports parameters about the health of the solid-state
142// media of a SCSI block device.
143func (d *Device) SolidStateMediaHealth() (*SolidStateMediaHealth, error) {
144 raw, err := d.LogSenseParameters(LogSenseRequest{PageCode: 0x11})
145 if err != nil {
146 return nil, err
147 }
148 if len(raw[0x1]) == 0 {
149 return nil, errors.New("mandatory parameter 0001h missing")
150 }
151 param1 := raw[0x01][0]
152 if len(param1.Data) < 4 {
153 return nil, errors.New("parameter 0001h too short")
154 }
155 return &SolidStateMediaHealth{
156 PercentageUsedEnduranceIndicator: param1.Data[3],
157 }, nil
158}