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