Lorenz Brun | bffdda8 | 2023-01-10 05:00:36 +0000 | [diff] [blame] | 1 | package scsi |
| 2 | |
| 3 | import ( |
| 4 | "encoding/binary" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | "math" |
| 8 | ) |
| 9 | |
| 10 | type LogSenseRequest struct { |
| 11 | // PageCode contains the identifier of the requested page |
| 12 | PageCode uint8 |
| 13 | // SubpageCode contains the identifier of the requested subpage |
| 14 | // or the zero value if no subpage is requested. |
| 15 | SubpageCode uint8 |
| 16 | // PageControl specifies what type of values should be returned for bounded |
| 17 | // and unbounded log parameters. See also Table 156 in the standard. |
| 18 | PageControl uint8 |
| 19 | // ParameterPointer allows requesting parameter data beginning from a |
| 20 | // specific parameter code. The zero value starts from the beginning. |
| 21 | ParameterPointer uint16 |
| 22 | // SaveParameters requests the device to save all parameters without |
| 23 | // DisableUpdate set to non-volatile storage. |
| 24 | SaveParameters bool |
| 25 | // InitialSize is an optional hint how big the buffer for the log page |
| 26 | // should be for the initial request. The zero value sets this to 4096. |
| 27 | InitialSize uint16 |
| 28 | } |
| 29 | |
| 30 | // LogSenseRaw requests a raw log page. For log pages with parameters |
| 31 | // LogSenseParameters is better-suited. |
| 32 | func (d *Device) LogSenseRaw(r LogSenseRequest) ([]byte, error) { |
| 33 | var bufferSize uint16 = 4096 |
| 34 | for { |
| 35 | data := make([]byte, bufferSize) |
| 36 | var req [8]byte |
| 37 | if r.SaveParameters { |
| 38 | req[0] = 0b1 |
| 39 | } |
| 40 | req[1] = r.PageControl<<6 | r.PageCode |
| 41 | req[2] = r.SubpageCode |
| 42 | binary.BigEndian.PutUint16(req[4:6], r.ParameterPointer) |
| 43 | binary.BigEndian.PutUint16(req[6:8], uint16(len(data))) |
| 44 | if err := d.RawCommand(&CommandDataBuffer{ |
| 45 | OperationCode: LogSenseOp, |
| 46 | Request: req[:], |
| 47 | Data: data, |
| 48 | DataTransferDirection: DataTransferFromDevice, |
| 49 | }); err != nil { |
| 50 | return nil, fmt.Errorf("error during LOG SENSE: %w", err) |
| 51 | } |
| 52 | if data[0]&0b111111 != r.PageCode { |
| 53 | return nil, fmt.Errorf("requested log page %x, got %x", r.PageCode, data[1]) |
| 54 | } |
| 55 | if data[1] != r.SubpageCode { |
| 56 | return nil, fmt.Errorf("requested log subpage %x, got %x", r.SubpageCode, data[1]) |
| 57 | } |
| 58 | pageLength := binary.BigEndian.Uint16(data[2:4]) |
| 59 | if pageLength > math.MaxUint16-4 { |
| 60 | // Guard against uint16 overflows, this cannot be requested anyways |
| 61 | return nil, fmt.Errorf("device log page is too long (%d bytes)", pageLength) |
| 62 | } |
| 63 | if pageLength > uint16(len(data)-4) { |
| 64 | bufferSize = pageLength + 4 |
| 65 | continue |
| 66 | } |
| 67 | return data[4 : pageLength+4], nil |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | // SupportedLogPages returns a map with all supported log pages. |
| 72 | // This can return an error if the device does not support logs at all. |
| 73 | func (d *Device) SupportedLogPages() (map[uint8]bool, error) { |
| 74 | raw, err := d.LogSenseRaw(LogSenseRequest{PageCode: 0}) |
| 75 | if err != nil { |
| 76 | return nil, err |
| 77 | } |
| 78 | res := make(map[uint8]bool) |
| 79 | for _, r := range raw { |
| 80 | res[r] = true |
| 81 | } |
| 82 | return res, nil |
| 83 | } |
| 84 | |
| 85 | // PageAndSubpage identifies a log page uniquely. |
| 86 | type PageAndSubpage uint16 |
| 87 | |
| 88 | func NewPageAndSubpage(page, subpage uint8) PageAndSubpage { |
| 89 | return PageAndSubpage(uint16(page)<<8 | uint16(subpage)) |
| 90 | } |
| 91 | |
| 92 | func (p PageAndSubpage) Page() uint8 { |
| 93 | return uint8(p >> 8) |
| 94 | } |
| 95 | func (p PageAndSubpage) Subpage() uint8 { |
| 96 | return uint8(p & 0xFF) |
| 97 | } |
| 98 | |
| 99 | func (p PageAndSubpage) String() string { |
| 100 | return fmt.Sprintf("Page %xh Subpage %xh", p.Page(), p.Subpage()) |
| 101 | } |
| 102 | |
| 103 | // SupportedLogPagesAndSubpages returns the list of supported pages and subpages. |
| 104 | // This can return an error if the device does not support logs at all. |
| 105 | func (d *Device) SupportedLogPagesAndSubpages() (map[PageAndSubpage]bool, error) { |
| 106 | raw, err := d.LogSenseRaw(LogSenseRequest{PageCode: 0x00, SubpageCode: 0xff}) |
| 107 | if err != nil { |
| 108 | return nil, err |
| 109 | } |
| 110 | res := make(map[PageAndSubpage]bool) |
| 111 | for i := 0; i < len(raw)/2; i++ { |
| 112 | res[NewPageAndSubpage(raw[i*2], raw[(i*2)+1])] = true |
| 113 | } |
| 114 | return res, nil |
| 115 | } |
| 116 | |
| 117 | // SupportedLogSubPages returns the list of subpages supported in a log page. |
| 118 | func (d *Device) SupportedLogSubPages(pageCode uint8) (map[uint8]bool, error) { |
| 119 | raw, err := d.LogSenseRaw(LogSenseRequest{PageCode: pageCode, SubpageCode: 0xff}) |
| 120 | if err != nil { |
| 121 | return nil, err |
| 122 | } |
| 123 | res := make(map[uint8]bool) |
| 124 | for _, r := range raw { |
| 125 | res[r] = true |
| 126 | } |
| 127 | return res, nil |
| 128 | } |
| 129 | |
| 130 | type LogParameter struct { |
| 131 | // DisableUpdate indicates if the device is updating this parameter. |
| 132 | // If this is true the parameter has either overflown or updating has been |
| 133 | // manually disabled. |
| 134 | DisableUpdate bool |
| 135 | // TargetSaveDisable indicates if automatic saving of this parameter has |
| 136 | // been disabled. |
| 137 | TargetSaveDisable bool |
| 138 | // FormatAndLinking contains the format of the log parameter. |
| 139 | FormatAndLinking uint8 |
| 140 | // Data contains the payload of the log parameter. |
| 141 | Data []byte |
| 142 | } |
| 143 | |
| 144 | // LogSenseParameters returns the parameters of a log page. The returned map |
| 145 | // contains one entry per parameter ID in the result. The order of parameters |
| 146 | // of the same ID is kept. |
| 147 | func (d *Device) LogSenseParameters(r LogSenseRequest) (map[uint16][]LogParameter, error) { |
| 148 | raw, err := d.LogSenseRaw(r) |
| 149 | if err != nil { |
| 150 | return nil, err |
| 151 | } |
| 152 | res := make(map[uint16][]LogParameter) |
| 153 | for { |
| 154 | if len(raw) == 0 { |
| 155 | break |
| 156 | } |
| 157 | if len(raw) < 4 { |
| 158 | return nil, errors.New("not enough data left to read full parameter metadata") |
| 159 | } |
| 160 | var param LogParameter |
| 161 | parameterCode := binary.BigEndian.Uint16(raw[0:2]) |
| 162 | param.DisableUpdate = raw[2]&(1<<7) != 0 |
| 163 | param.TargetSaveDisable = raw[2]&(1<<5) != 0 |
| 164 | param.FormatAndLinking = raw[2] & 0b11 |
| 165 | if int(raw[3]) > len(raw)-4 { |
| 166 | fmt.Println(raw[3], len(raw)) |
| 167 | return nil, errors.New("unable to read parameter, not enough data for indicated length") |
| 168 | } |
| 169 | param.Data = raw[4 : int(raw[3])+4] |
| 170 | raw = raw[int(raw[3])+4:] |
| 171 | res[parameterCode] = append(res[parameterCode], param) |
| 172 | } |
| 173 | return res, nil |
| 174 | } |