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