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