blob: 3aecd5e7ff29a86ef6b88cc972d066c9706ff229 [file] [log] [blame]
Lorenz Brunbffdda82023-01-10 05:00:36 +00001package scsi
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "math"
8)
9
10type 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.
32func (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.
73func (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.
86type PageAndSubpage uint16
87
88func NewPageAndSubpage(page, subpage uint8) PageAndSubpage {
89 return PageAndSubpage(uint16(page)<<8 | uint16(subpage))
90}
91
92func (p PageAndSubpage) Page() uint8 {
93 return uint8(p >> 8)
94}
95func (p PageAndSubpage) Subpage() uint8 {
96 return uint8(p & 0xFF)
97}
98
99func (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.
105func (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.
118func (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
130type 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.
147func (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}