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