blob: 3aecd5e7ff29a86ef6b88cc972d066c9706ff229 [file] [log] [blame]
package scsi
import (
"encoding/binary"
"errors"
"fmt"
"math"
)
type LogSenseRequest struct {
// PageCode contains the identifier of the requested page
PageCode uint8
// SubpageCode contains the identifier of the requested subpage
// or the zero value if no subpage is requested.
SubpageCode uint8
// PageControl specifies what type of values should be returned for bounded
// and unbounded log parameters. See also Table 156 in the standard.
PageControl uint8
// ParameterPointer allows requesting parameter data beginning from a
// specific parameter code. The zero value starts from the beginning.
ParameterPointer uint16
// SaveParameters requests the device to save all parameters without
// DisableUpdate set to non-volatile storage.
SaveParameters bool
// InitialSize is an optional hint how big the buffer for the log page
// should be for the initial request. The zero value sets this to 4096.
InitialSize uint16
}
// LogSenseRaw requests a raw log page. For log pages with parameters
// LogSenseParameters is better-suited.
func (d *Device) LogSenseRaw(r LogSenseRequest) ([]byte, error) {
var bufferSize uint16 = 4096
for {
data := make([]byte, bufferSize)
var req [8]byte
if r.SaveParameters {
req[0] = 0b1
}
req[1] = r.PageControl<<6 | r.PageCode
req[2] = r.SubpageCode
binary.BigEndian.PutUint16(req[4:6], r.ParameterPointer)
binary.BigEndian.PutUint16(req[6:8], uint16(len(data)))
if err := d.RawCommand(&CommandDataBuffer{
OperationCode: LogSenseOp,
Request: req[:],
Data: data,
DataTransferDirection: DataTransferFromDevice,
}); err != nil {
return nil, fmt.Errorf("error during LOG SENSE: %w", err)
}
if data[0]&0b111111 != r.PageCode {
return nil, fmt.Errorf("requested log page %x, got %x", r.PageCode, data[1])
}
if data[1] != r.SubpageCode {
return nil, fmt.Errorf("requested log subpage %x, got %x", r.SubpageCode, data[1])
}
pageLength := binary.BigEndian.Uint16(data[2:4])
if pageLength > math.MaxUint16-4 {
// Guard against uint16 overflows, this cannot be requested anyways
return nil, fmt.Errorf("device log page is too long (%d bytes)", pageLength)
}
if pageLength > uint16(len(data)-4) {
bufferSize = pageLength + 4
continue
}
return data[4 : pageLength+4], nil
}
}
// SupportedLogPages returns a map with all supported log pages.
// This can return an error if the device does not support logs at all.
func (d *Device) SupportedLogPages() (map[uint8]bool, error) {
raw, err := d.LogSenseRaw(LogSenseRequest{PageCode: 0})
if err != nil {
return nil, err
}
res := make(map[uint8]bool)
for _, r := range raw {
res[r] = true
}
return res, nil
}
// PageAndSubpage identifies a log page uniquely.
type PageAndSubpage uint16
func NewPageAndSubpage(page, subpage uint8) PageAndSubpage {
return PageAndSubpage(uint16(page)<<8 | uint16(subpage))
}
func (p PageAndSubpage) Page() uint8 {
return uint8(p >> 8)
}
func (p PageAndSubpage) Subpage() uint8 {
return uint8(p & 0xFF)
}
func (p PageAndSubpage) String() string {
return fmt.Sprintf("Page %xh Subpage %xh", p.Page(), p.Subpage())
}
// SupportedLogPagesAndSubpages returns the list of supported pages and subpages.
// This can return an error if the device does not support logs at all.
func (d *Device) SupportedLogPagesAndSubpages() (map[PageAndSubpage]bool, error) {
raw, err := d.LogSenseRaw(LogSenseRequest{PageCode: 0x00, SubpageCode: 0xff})
if err != nil {
return nil, err
}
res := make(map[PageAndSubpage]bool)
for i := 0; i < len(raw)/2; i++ {
res[NewPageAndSubpage(raw[i*2], raw[(i*2)+1])] = true
}
return res, nil
}
// SupportedLogSubPages returns the list of subpages supported in a log page.
func (d *Device) SupportedLogSubPages(pageCode uint8) (map[uint8]bool, error) {
raw, err := d.LogSenseRaw(LogSenseRequest{PageCode: pageCode, SubpageCode: 0xff})
if err != nil {
return nil, err
}
res := make(map[uint8]bool)
for _, r := range raw {
res[r] = true
}
return res, nil
}
type LogParameter struct {
// DisableUpdate indicates if the device is updating this parameter.
// If this is true the parameter has either overflown or updating has been
// manually disabled.
DisableUpdate bool
// TargetSaveDisable indicates if automatic saving of this parameter has
// been disabled.
TargetSaveDisable bool
// FormatAndLinking contains the format of the log parameter.
FormatAndLinking uint8
// Data contains the payload of the log parameter.
Data []byte
}
// LogSenseParameters returns the parameters of a log page. The returned map
// contains one entry per parameter ID in the result. The order of parameters
// of the same ID is kept.
func (d *Device) LogSenseParameters(r LogSenseRequest) (map[uint16][]LogParameter, error) {
raw, err := d.LogSenseRaw(r)
if err != nil {
return nil, err
}
res := make(map[uint16][]LogParameter)
for {
if len(raw) == 0 {
break
}
if len(raw) < 4 {
return nil, errors.New("not enough data left to read full parameter metadata")
}
var param LogParameter
parameterCode := binary.BigEndian.Uint16(raw[0:2])
param.DisableUpdate = raw[2]&(1<<7) != 0
param.TargetSaveDisable = raw[2]&(1<<5) != 0
param.FormatAndLinking = raw[2] & 0b11
if int(raw[3]) > len(raw)-4 {
fmt.Println(raw[3], len(raw))
return nil, errors.New("unable to read parameter, not enough data for indicated length")
}
param.Data = raw[4 : int(raw[3])+4]
raw = raw[int(raw[3])+4:]
res[parameterCode] = append(res[parameterCode], param)
}
return res, nil
}