github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/ipmi/blobs/blob_transfer.go (about) 1 // Copyright 2020 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package blobs implements OpenBMC IPMI Blob Protocol commands. 6 // 7 // This file declares functions that implement the generic blob transfer 8 // interface detailed at https://github.com/openbmc/phosphor-ipmi-blobs 9 // with IPMI as a transport layer. 10 // See https://github.com/openbmc/google-ipmi-i2c for details on OEM 11 // commands. 12 package blobs 13 14 import ( 15 "bytes" 16 "encoding/binary" 17 "fmt" 18 19 "github.com/u-root/u-root/pkg/ipmi" 20 ) 21 22 // CRCOption is an option for sending/receiving CRCs. 23 type CRCOption string 24 25 // SessionID is a unique identifier for an open blob. 26 type SessionID uint16 27 28 // BlobStats contains statistics for a given blob. 29 type BlobStats struct { 30 state uint16 31 size uint32 32 metadataLen uint8 33 metadata []uint8 34 } 35 36 // BlobHandler provides an interface for the blob protocol. IT can be used 37 // to call all the blob transfer commands. 38 type BlobHandler struct { 39 IPMI *ipmi.IPMI 40 } 41 42 const ( 43 _IPMI_GGL_NET_FN ipmi.NetFn = 46 44 _IPMI_GGL_LUN = 0 45 _IPMI_GGL_BLOB_CMD ipmi.Command = 128 46 47 _OEN_LEN = 3 48 _CRC_LEN = 2 49 ) 50 51 // Blob transfer command codes. 52 const ( 53 _BMC_BLOB_CMD_CODE_GET_COUNT = 0 54 _BMC_BLOB_CMD_CODE_ENUMERATE = 1 55 _BMC_BLOB_CMD_CODE_OPEN = 2 56 _BMC_BLOB_CMD_CODE_READ = 3 57 _BMC_BLOB_CMD_CODE_WRITE = 4 58 _BMC_BLOB_CMD_CODE_COMMIT = 5 59 _BMC_BLOB_CMD_CODE_CLOSE = 6 60 _BMC_BLOB_CMD_CODE_DELETE = 7 61 _BMC_BLOB_CMD_CODE_STAT = 8 62 _BMC_BLOB_CMD_CODE_SESSION_STAT = 9 63 ) 64 65 // Flags for blob open command. 66 const ( 67 BMC_BLOB_OPEN_FLAG_READ = 1 << 0 68 BMC_BLOB_OPEN_FLAG_WRITE = 1 << 1 69 // Blob open: bit positions 2-7 are reserved for future protocol use. 70 // Bit positions 8-15 are available for blob-specific definitions. 71 ) 72 73 // Flags for blob state. 74 const ( 75 BMC_BLOB_STATE_OPEN_R = 1 << 0 76 BMC_BLOB_STATE_OPEN_W = 1 << 1 77 BMC_BLOB_STATE_COMMITTING = 1 << 2 78 BMC_BLOB_STATE_COMMITTED = 1 << 3 79 BMC_BLOB_STATE_COMMIT_ERROR = 1 << 4 80 // Blob state: bit positions 5-7 are reserved for future protocol use. 81 // Bit positions 8-16 are available for blob-specific definitions. 82 ) 83 84 // CRC options 85 const ( 86 REQ_CRC CRCOption = "REQ_CRC" 87 RES_CRC CRCOption = "RES_CRC" 88 NO_CRC CRCOption = "NO_CRC" 89 REQ_RES_CRC CRCOption = "REQ_RES_CRC" 90 ) 91 92 // OENMap maps OEM names to a 3 byte OEM number. 93 // OENs are typically serialized as the first 3 bytes of a request body. 94 var OENMap = map[string][3]uint8{ 95 "OpenBMC": {0xcf, 0xc2, 0x00}, 96 } 97 98 // NewBlobHandler takes an IPMI struct, which provides a reference to the IPMI 99 // device driver, and returns a BlobHandler. 100 func NewBlobHandler(i *ipmi.IPMI) *BlobHandler { 101 return &BlobHandler{IPMI: i} 102 } 103 104 // sendBlobCmd takes a command code, data given in little endian format, and 105 // an option for cyclic redundancy checks (CRC). It constructs the request 106 // and sends the command over IPMI. It receives the response, validates it, 107 // and then returns the response body. 108 func (h *BlobHandler) sendBlobCmd(code uint8, data []uint8, crcOpt CRCOption) ([]byte, error) { 109 i := h.IPMI 110 // Initialize a buffer with the correct OEN and code. 111 oen, ok := OENMap["OpenBMC"] 112 if !ok { 113 return nil, fmt.Errorf("couldn't find OEN for OpenBMC") 114 } 115 116 buf := []uint8{oen[0], oen[1], oen[2], code} 117 118 // If the request should have a CRC, derive a CRC based on the request body. 119 if crcOpt == REQ_CRC || crcOpt == REQ_RES_CRC { 120 crc := new(bytes.Buffer) 121 if err := binary.Write(crc, binary.LittleEndian, genCRC(data)); err != nil { 122 return nil, fmt.Errorf("failed to generate request CRC: %v", err) 123 } 124 buf = append(buf, crc.Bytes()...) 125 } 126 127 buf = append(buf, data...) 128 129 // The request buffer should now be as follows: 130 // - 3-byte OEN 131 // - 1-byte subcommand code 132 // - (optionally) 2-byte CRC over request body in little endian format 133 // - request body in little endian format 134 135 res, err := i.SendRecv(_IPMI_GGL_NET_FN, _IPMI_GGL_BLOB_CMD, buf) 136 if err != nil { 137 return nil, err 138 } 139 // Response always has a leading 0, so we ignore it. 140 res = res[1:] 141 142 // The response buffer is expected to be as follows: 143 // - 3-byte OEN 144 // - 2-byte CRC over response body in little endian format 145 // - response body in little endian format 146 // We verify that the OEN and CRC match the expected values. 147 148 if len(res) < _OEN_LEN { 149 return nil, fmt.Errorf("response too small: %d < size of OEN", len(res)) 150 } 151 resOen, resBody := res[0:_OEN_LEN], res[_OEN_LEN:] 152 153 // if oen[0] != resOen[0] || oen[1] != resOen[1] || oen[2] != resOen[2] { 154 if !bytes.Equal(oen[0:3], resOen) { 155 return nil, fmt.Errorf("response OEN incorrect: got %v, expected %v", resOen, oen) 156 } 157 158 // If the response should have a CRC, validate the CRC for the response body. 159 if crcOpt == RES_CRC || crcOpt == REQ_RES_CRC { 160 if err := verifyCRC(resBody); err != nil { 161 return nil, fmt.Errorf("failed to verify response CRC: %v", err) 162 } 163 resBody = resBody[_CRC_LEN:] 164 } 165 166 return resBody, nil 167 } 168 169 // Sets a CCITT CRC based on the contents of the buffer. 170 // TODO(plaud): this is right now a copied implementation. Better to get and use 171 // a functional library (tried snksoft_crc but didn't work?) 172 func genCRC(data []uint8) uint16 { 173 var kPoly uint16 = 0x1021 174 var kLeftBit uint16 = 0x8000 175 var crc uint16 = 0xFFFF 176 kExtraRounds := 2 177 178 for i := 0; i < len(data)+kExtraRounds; i++ { 179 for j := 0; j < 8; j++ { 180 xorFlag := false 181 if (crc & kLeftBit) != 0 { 182 xorFlag = true 183 } 184 crc = crc << 1 185 // If this isn't an extra round and the current byte's j'th bit from the 186 // left is set, increment the CRC. 187 if i < len(data) && (data[i]&(1<<(7-j))) != 0 { 188 crc = crc + 1 189 } 190 if xorFlag { 191 crc = crc ^ kPoly 192 } 193 } 194 } 195 196 return crc 197 } 198 199 // Verifies the CRC in the buffer, which must be the first two bytes. The CRC 200 // is validated against all data that follows it. 201 func verifyCRC(buf []uint8) error { 202 if len(buf) < _CRC_LEN { 203 return fmt.Errorf("response too small") 204 } 205 206 var respCrc uint16 207 if err := binary.Read(bytes.NewReader(buf[0:_CRC_LEN]), binary.LittleEndian, &respCrc); err != nil { 208 return fmt.Errorf("failed to read response CRC: %v", err) 209 } 210 211 expCrc := genCRC(buf[_CRC_LEN:]) 212 213 if expCrc != respCrc { 214 return fmt.Errorf("CRC error: generated 0x%04X, got 0x%04X", expCrc, respCrc) 215 } 216 return nil 217 } 218 219 // Convert all args to little endian format and append to the given buffer. 220 func appendLittleEndian(buf []uint8, args ...interface{}) ([]uint8, error) { 221 for _, arg := range args { 222 data := new(bytes.Buffer) 223 err := binary.Write(data, binary.LittleEndian, arg) 224 if err != nil { 225 return nil, err 226 } 227 buf = append(buf, data.Bytes()...) 228 } 229 230 return buf, nil 231 } 232 233 // BlobGetCount returns the number of enumerable blobs available. 234 func (h *BlobHandler) BlobGetCount() (int, error) { 235 data, err := h.sendBlobCmd(_BMC_BLOB_CMD_CODE_GET_COUNT, []uint8{}, RES_CRC) 236 if err != nil { 237 return 0, err 238 } 239 240 buf := bytes.NewReader(data) 241 var blobCount int32 242 243 if err := binary.Read(buf, binary.LittleEndian, &blobCount); err != nil { 244 return 0, fmt.Errorf("failed to read response: %v", err) 245 } 246 247 return (int)(blobCount), nil 248 } 249 250 // BlobEnumerate returns the blob identifier for the given index. 251 // 252 // Note that the index for a given blob ID is not expected to be stable long 253 // term. Callers are expected to call BlobGetCount, followed by N calls to 254 // BlobEnumerate, to collect all blob IDs. 255 func (h *BlobHandler) BlobEnumerate(index int) (string, error) { 256 req, err := appendLittleEndian([]uint8{}, (int32)(index)) 257 if err != nil { 258 return "", fmt.Errorf("failed to create data buffer: %v", err) 259 } 260 261 data, err := h.sendBlobCmd(_BMC_BLOB_CMD_CODE_ENUMERATE, req, REQ_RES_CRC) 262 if err != nil { 263 return "", err 264 } 265 266 return (string)(data), nil 267 } 268 269 // BlobOpen opens a blob referred to by |id| with the given |flags|, and returns 270 // a unique session identifier. 271 // 272 // The BMC allocates a unique session identifier, and internally maps it 273 // to the blob identifier. The sessionId should be used by the rest of the 274 // session based commands to operate on the blob. 275 // NOTE: the new blob is not serialized and stored until BlobCommit is called. 276 func (h *BlobHandler) BlobOpen(id string, flags int16) (SessionID, error) { 277 req, err := appendLittleEndian([]uint8{}, flags, ([]byte)(id)) 278 if err != nil { 279 return 0, fmt.Errorf("failed to create data buffer: %v", err) 280 } 281 282 data, err := h.sendBlobCmd(_BMC_BLOB_CMD_CODE_OPEN, req, REQ_RES_CRC) 283 if err != nil { 284 return 0, err 285 } 286 287 buf := bytes.NewReader(data) 288 var sid SessionID 289 290 if err := binary.Read(buf, binary.LittleEndian, &sid); err != nil { 291 return 0, fmt.Errorf("failed to read response: %v", err) 292 } 293 294 return sid, nil 295 } 296 297 // BlobRead reads and return the blob data. 298 // 299 // |sessionID| returned from BlobOpen gives us the open blob. 300 // The byte sequence starts at |offset|, and |size| bytes are read. 301 // If there are not enough bytes, return the bytes that are available. 302 func (h *BlobHandler) BlobRead(sid SessionID, offset, size uint32) ([]uint8, error) { 303 req, err := appendLittleEndian([]uint8{}, sid, offset, size) 304 if err != nil { 305 return nil, fmt.Errorf("failed to create data buffer: %v", err) 306 } 307 308 data, err := h.sendBlobCmd(_BMC_BLOB_CMD_CODE_READ, req, REQ_RES_CRC) 309 if err != nil { 310 return nil, err 311 } 312 313 return ([]uint8)(data), nil 314 } 315 316 // BlobWrite writes bytes to the requested blob offset, and returns number of 317 // bytes written if success. 318 // 319 // |sessionID| returned from BlobOpen gives us the open blob. 320 // |data| is bounded by max size of an IPMI packet, which is platform-dependent. 321 // If not all of the bytes can be written, this operation will fail. 322 func (h *BlobHandler) BlobWrite(sid SessionID, offset int32, data []int8) error { 323 req, err := appendLittleEndian([]uint8{}, sid, offset, data) 324 if err != nil { 325 return fmt.Errorf("failed to create data buffer: %v", err) 326 } 327 328 _, err = h.sendBlobCmd(_BMC_BLOB_CMD_CODE_WRITE, req, REQ_CRC) 329 return err 330 } 331 332 // BlobCommit commits the blob. 333 // 334 // Each blob defines its own commit behavior. Optional blob-specific commit data 335 // can be provided with |data|. 336 func (h *BlobHandler) BlobCommit(sid SessionID, data []int8) error { 337 req, err := appendLittleEndian([]uint8{}, sid, (uint8)(len(data)), data) 338 if err != nil { 339 return fmt.Errorf("failed to create data buffer: %v", err) 340 } 341 342 _, err = h.sendBlobCmd(_BMC_BLOB_CMD_CODE_COMMIT, req, REQ_CRC) 343 return err 344 } 345 346 // BlobClose has the BMC mark the specified blob as closed. 347 // 348 // It must be called after commit-polling has finished, regardless of the result. 349 func (h *BlobHandler) BlobClose(sid SessionID) error { 350 req, err := appendLittleEndian([]uint8{}, sid) 351 if err != nil { 352 return fmt.Errorf("failed to create data buffer: %v", err) 353 } 354 355 _, err = h.sendBlobCmd(_BMC_BLOB_CMD_CODE_CLOSE, req, REQ_CRC) 356 return err 357 } 358 359 // BlobDelete deletes a blob if the operation is supported. 360 // 361 // This command will fail if there are open sessions for the blob. 362 func (h *BlobHandler) BlobDelete(id string) error { 363 req, err := appendLittleEndian([]uint8{}, ([]byte)(id)) 364 if err != nil { 365 return fmt.Errorf("failed to create data buffer: %v", err) 366 } 367 368 _, err = h.sendBlobCmd(_BMC_BLOB_CMD_CODE_DELETE, req, REQ_CRC) 369 return err 370 } 371 372 // BlobStat returns statistics about a blob. 373 // 374 // |size| is the size of blob in bytes. This may be zero if the blob does not 375 // support reading. 376 // |state| will be set with OPEN_R, OPEN_W, and/or COMMITTED as appropriate 377 // |metadata| is optional blob-specific bytes 378 func (h *BlobHandler) BlobStat(id string) (*BlobStats, error) { 379 req, err := appendLittleEndian([]uint8{}, ([]byte)(id)) 380 if err != nil { 381 return nil, fmt.Errorf("failed to create data buffer: %v", err) 382 } 383 384 data, err := h.sendBlobCmd(_BMC_BLOB_CMD_CODE_STAT, req, REQ_RES_CRC) 385 if err != nil { 386 return nil, err 387 } 388 389 buf := bytes.NewReader(data) 390 var stats BlobStats 391 392 if err := binary.Read(buf, binary.LittleEndian, &stats); err != nil { 393 return nil, fmt.Errorf("failed to read response: %v", err) 394 } 395 396 return &stats, nil 397 } 398 399 // BlobSessionStat command returns the same data as BmcBlobStat. 400 // 401 // However, this command operates on sessions, rather than blob IDs. Not all 402 // blobs must support this command; this is only useful when session semantics 403 // are more useful than raw blob IDs. 404 func (h *BlobHandler) BlobSessionStat(sid SessionID) (*BlobStats, error) { 405 req, err := appendLittleEndian([]uint8{}, sid) 406 if err != nil { 407 return nil, fmt.Errorf("failed to create data buffer: %v", err) 408 } 409 410 data, err := h.sendBlobCmd(_BMC_BLOB_CMD_CODE_SESSION_STAT, req, REQ_RES_CRC) 411 if err != nil { 412 return nil, err 413 } 414 415 buf := bytes.NewReader(data) 416 var stats BlobStats 417 418 if err := binary.Read(buf, binary.LittleEndian, &stats); err != nil { 419 return nil, fmt.Errorf("failed to read response: %v", err) 420 } 421 422 return &stats, nil 423 }