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