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