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  }