| 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 |
| } |