blob: 1941d8363cb865776631a51aa72187f9e8223da9 [file] [log] [blame]
package scsi
// Written against SBC-4
// Contains SCSI block device specific commands.
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
)
// ReadDefectDataLBA reads the primary (manufacturer) and/or grown defect list
// in LBA format. This is commonly used on SSDs and generally returns an error
// on spinning drives.
func (d *Device) ReadDefectDataLBA(plist, glist bool) ([]uint64, error) {
data := make([]byte, 4096)
var req [8]byte
if plist {
req[1] |= 1 << 4
}
if glist {
req[1] |= 1 << 3
}
defectListFormat := 0b011
req[1] |= byte(defectListFormat)
binary.BigEndian.PutUint16(req[6:8], uint16(len(data)))
if err := d.RawCommand(&CommandDataBuffer{
OperationCode: ReadDefectDataOp,
Request: req[:],
Data: data,
DataTransferDirection: DataTransferFromDevice,
}); err != nil {
var fixedErr *FixedError
if errors.As(err, &fixedErr) && fixedErr.SenseKey == RecoveredError && fixedErr.AdditionalSenseCode == DefectListNotFound {
return nil, fmt.Errorf("error during LOG SENSE: unsupported defect list format, device returned %03bb", data[1]&0b111)
}
return nil, fmt.Errorf("error during LOG SENSE: %w", err)
}
if data[1]&0b111 != byte(defectListFormat) {
return nil, fmt.Errorf("device returned wrong defect list format, requested %03bb, got %03bb", defectListFormat, data[1]&0b111)
}
defectListLength := binary.BigEndian.Uint16(data[2:4])
if defectListLength%8 != 0 {
return nil, errors.New("returned defect list not divisible by array item size")
}
res := make([]uint64, defectListLength/8)
if err := binary.Read(bytes.NewReader(data[4:]), binary.BigEndian, &res); err != nil {
panic(err)
}
return res, nil
}
const (
// AllSectors is a magic sector number indicating that it applies to all
// sectors on the track.
AllSectors = math.MaxUint16
)
// PhysicalSectorFormatAddress represents a physical sector (or the the whole
// track if SectorNumber == AllSectors) on a spinning hard drive.
type PhysicalSectorFormatAddress struct {
CylinderNumber uint32
HeadNumber uint8
SectorNumber uint32
MultiAddressDescriptorStart bool
}
func parseExtendedPhysicalSectorFormatAddress(buf []byte) (p PhysicalSectorFormatAddress) {
p.CylinderNumber = uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])
p.HeadNumber = buf[3]
p.MultiAddressDescriptorStart = buf[4]&(1<<7) != 0
p.SectorNumber = uint32(buf[4]&0b1111)<<24 | uint32(buf[5])<<16 | uint32(buf[6])<<8 | uint32(buf[7])
return
}
func parsePhysicalSectorFormatAddress(buf []byte) (p PhysicalSectorFormatAddress) {
p.CylinderNumber = uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])
p.HeadNumber = buf[3]
p.SectorNumber = binary.BigEndian.Uint32(buf[4:8])
return
}
// ReadDefectDataPhysical reads the primary (manufacturer) and/or grown defect
// list in physical format.
// This is only defined for spinning drives, returning an error on SSDs.
func (d *Device) ReadDefectDataPhysical(plist, glist bool) ([]PhysicalSectorFormatAddress, error) {
data := make([]byte, 4096)
var req [8]byte
if plist {
req[1] |= 1 << 4
}
if glist {
req[1] |= 1 << 3
}
defectListFormat := 0b101
req[1] |= byte(defectListFormat)
binary.BigEndian.PutUint16(req[6:8], uint16(len(data)))
if err := d.RawCommand(&CommandDataBuffer{
OperationCode: ReadDefectDataOp,
Request: req[:],
Data: data,
DataTransferDirection: DataTransferFromDevice,
}); err != nil {
var fixedErr *FixedError
if errors.As(err, &fixedErr) && fixedErr.SenseKey == RecoveredError && fixedErr.AdditionalSenseCode == DefectListNotFound {
return nil, fmt.Errorf("error during LOG SENSE: unsupported defect list format, device returned %03bb", data[1]&0b111)
}
return nil, fmt.Errorf("error during LOG SENSE: %w", err)
}
if data[1]&0b111 != byte(defectListFormat) {
return nil, fmt.Errorf("device returned wrong defect list format, requested %03bb, got %03bb", defectListFormat, data[1]&0b111)
}
defectListLength := binary.BigEndian.Uint16(data[2:4])
if defectListLength%8 != 0 {
return nil, errors.New("returned defect list not divisible by array item size")
}
if len(data) < int(defectListLength)+4 {
return nil, errors.New("returned defect list longer than buffer")
}
res := make([]PhysicalSectorFormatAddress, defectListLength/8)
data = data[4:]
for i := 0; i < int(defectListLength)/8; i++ {
res[i] = parsePhysicalSectorFormatAddress(data[i*8 : (i+1)*8])
}
return res, nil
}
type SolidStateMediaHealth struct {
// PercentageUsedEnduranceIndicator is a value which represents a
// vendor-specific wear estimate of the solid state medium.
// A new device starts at 0, at 100 the device is considered end-of-life.
// Values up to 255 are possible.
PercentageUsedEnduranceIndicator uint8
}
// SolidStateMediaHealth reports parameters about the health of the solid-state
// media of a SCSI block device.
func (d *Device) SolidStateMediaHealth() (*SolidStateMediaHealth, error) {
raw, err := d.LogSenseParameters(LogSenseRequest{PageCode: 0x11})
if err != nil {
return nil, err
}
if len(raw[0x1]) == 0 {
return nil, errors.New("mandatory parameter 0001h missing")
}
param1 := raw[0x01][0]
if len(param1.Data) < 4 {
return nil, errors.New("parameter 0001h too short")
}
return &SolidStateMediaHealth{
PercentageUsedEnduranceIndicator: param1.Data[3],
}, nil
}