File: cmd_read_fru_data.go

package info (click to toggle)
golang-github-bougou-go-ipmi 0.7.8-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,880 kB
  • sloc: makefile: 38
file content (129 lines) | stat: -rw-r--r-- 3,409 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package ipmi

import (
	"context"
	"fmt"
)

// 34.2 Read FRU Data Command
type ReadFRUDataRequest struct {
	FRUDeviceID uint8
	ReadOffset  uint16
	ReadCount   uint8
}

type ReadFRUDataResponse struct {
	CountReturned uint8
	Data          []byte
}

func (req *ReadFRUDataRequest) Command() Command {
	return CommandReadFRUData
}

func (req *ReadFRUDataRequest) Pack() []byte {
	out := make([]byte, 4)
	packUint8(req.FRUDeviceID, out, 0)
	packUint16L(req.ReadOffset, out, 1)
	packUint8(req.ReadCount, out, 3)
	return out
}

func (res *ReadFRUDataResponse) Unpack(msg []byte) error {
	if len(msg) < 1 {
		return ErrUnpackedDataTooShortWith(len(msg), 1)
	}

	res.CountReturned, _, _ = unpackUint8(msg, 0)
	res.Data, _, _ = unpackBytes(msg, 1, len(msg)-1)
	return nil
}

func (r *ReadFRUDataResponse) CompletionCodes() map[uint8]string {
	return map[uint8]string{
		0x81: "FRU device busy",
	}
}

func (res *ReadFRUDataResponse) Format() string {
	return "" +
		fmt.Sprintf("Count returned : %d\n", res.CountReturned) +
		fmt.Sprintf("Data           : %02x\n", res.Data)
}

// The command returns the specified data from the FRU Inventory Info area.
func (c *Client) ReadFRUData(ctx context.Context, fruDeviceID uint8, readOffset uint16, readCount uint8) (response *ReadFRUDataResponse, err error) {
	request := &ReadFRUDataRequest{
		FRUDeviceID: fruDeviceID,
		ReadOffset:  readOffset,
		ReadCount:   readCount,
	}
	response = &ReadFRUDataResponse{}
	err = c.Exchange(ctx, request, response)
	return
}

// readFRUDataByLength reads FRU Data in loop until reaches the specified data length
func (c *Client) readFRUDataByLength(ctx context.Context, deviceID uint8, offset uint16, length uint16) ([]byte, error) {
	var data []byte
	c.Debugf("Read FRU Data by Length, offset: (%d), length: (%d)\n", offset, length)

	for {
		if length <= 0 {
			break
		}

		res, err := c.tryReadFRUData(ctx, deviceID, offset, length)
		if err != nil {
			return nil, fmt.Errorf("tryReadFRUData failed, err: %w", err)
		}
		c.Debug("", res.Format())
		data = append(data, res.Data...)

		length -= uint16(res.CountReturned)
		c.Debugf("left length: %d\n", length)

		// update offset
		offset += uint16(res.CountReturned)
	}

	return data, nil
}

// tryReadFRUData will try to read FRU data with a read count which starts with
// the minimal number of the specified length and the hard-coded 32, if the
// ReadFRUData failed, it try another request with a decreased read count.
func (c *Client) tryReadFRUData(ctx context.Context, deviceID uint8, readOffset uint16, length uint16) (response *ReadFRUDataResponse, err error) {
	var readCount uint8 = 32
	if length <= uint16(readCount) {
		readCount = uint8(length)
	}

	for {
		if readCount <= 0 {
			return nil, fmt.Errorf("nothing to read")
		}

		c.Debugf("Try Read FRU Data, offset: (%d), count: (%d)\n", readOffset, readCount)
		res, err := c.ReadFRUData(ctx, deviceID, readOffset, readCount)
		if err == nil {
			return res, nil
		}

		if respErr, ok := isResponseError(err); ok {
			cc := respErr.CompletionCode()
			if readFRUDataLength2Big(cc) {
				readCount -= 1
				continue
			}
		}

		return nil, fmt.Errorf("ReadFRUData failed, err: %w", err)
	}
}

func readFRUDataLength2Big(cc CompletionCode) bool {
	return cc == CompletionCodeRequestDataLengthInvalid ||
		cc == CompletionCodeRequestDataLengthLimitExceeded ||
		cc == CompletionCodeCannotReturnRequestedDataBytes
}