/** @file Copyright (c) 2020, Rebecca Cran Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.
Copyright (C) 2013, Red Hat, Inc. Copyright (c) 2015, Nahanni Systems. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "Uefi.h" #include #include #include #include #include #include #include #define FW_PORT 0x510 #define FW_IPORT 0x511 /* Transport protocol basic operations */ #define OP_NULL 1 #define OP_ECHO 2 #define OP_GET 3 #define OP_GET_LEN 4 #define OP_SET 5 /* Transport protocol error returns */ #define T_ESUCCESS 0 #define T_ENOENT 2 #define T_E2BIG 7 #define T_EMSGSIZE 40 #define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y)) STATIC CONST CHAR8 mBhyveSig[4] = { 'B', 'H', 'Y', 'V' }; STATIC BOOLEAN mBhyveFwCtlSupported = FALSE; STATIC INT32 mBhyveFwCtlTxid = 0xa5; /* XXX Maybe a better inbuilt version of this ? */ typedef struct { VOID *Base; UINT32 Len; } BIO_VEC; typedef struct { UINT32 Sz; UINT32 Op; UINT32 TxId; UINT32 Err; } MSG_RX_HDR; STATIC RETURN_STATUS EFIAPI BhyveFwCtl_CvtErr ( IN UINT32 errno ) { RETURN_STATUS Status; switch (errno) { case T_ESUCCESS: Status = RETURN_SUCCESS; break; case T_ENOENT: Status = RETURN_NOT_FOUND; break; case T_E2BIG: Status = RETURN_INVALID_PARAMETER; break; case T_EMSGSIZE: Status = RETURN_BUFFER_TOO_SMALL; break; default: Status = RETURN_PROTOCOL_ERROR; break; } return Status; } STATIC UINT32 EFIAPI BIov_WLen ( IN BIO_VEC b[] ) { UINT32 i; UINT32 tLen; tLen = 0; if (b != NULL) { for (i = 0; b[i].Base != NULL; i++) tLen += ROUNDUP (b[i].Len, sizeof(UINT32)); } return tLen; } /** Utility to send 1-3 bytes of input as a 4-byte value with trailing zeroes. **/ STATIC UINT32 BIov_Send_Rem ( IN UINT32 *Data, IN UINT32 Len ) { union { UINT8 c[4]; UINT32 w; } u; UINT8 *cdata; UINT32 i; cdata = (UINT8 *)Data; u.w = 0; for (i = 0; i < Len; i++) u.c[i] = *cdata++; return u.w; } /** Send a block of data out the i/o port as 4-byte quantities, appending trailing zeroes on the last if required. **/ STATIC VOID BIov_Send ( IN char *Data, IN UINT32 Len ) { UINT32 *LData; LData = (UINT32 *)Data; while (Len > sizeof(UINT32)) { IoWrite32 (FW_PORT, *LData++); Len -= sizeof(UINT32); } if (Len > 0) { IoWrite32 (FW_PORT, BIov_Send_Rem (LData, Len)); } } /** Send data described by an array of iovecs out the i/o port. **/ STATIC VOID BIov_SendAll ( IN BIO_VEC b[] ) { INT32 i; if (b != NULL) { for (i = 0; b[i].Base; i++) { BIov_Send (b[i].Base, b[i].Len); } } } /** Prepend the transport header to a block of data and send. **/ STATIC VOID EFIAPI BhyveFwCtl_MsgSend( IN UINT32 OpCode, IN BIO_VEC Data[] ) { BIO_VEC hIov[4]; UINT32 Hdr[3]; UINT32 i; /* Set up header as an iovec */ for (i = 0; i < 3; i++) { hIov[i].Base = &Hdr[i]; hIov[i].Len = sizeof(Hdr[0]); } hIov[i].Base = NULL; hIov[i].Len = 0; /* Initialize header */ Hdr[0] = BIov_WLen (hIov) + BIov_WLen (Data); Hdr[1] = (UINT32)OpCode; Hdr[2] = mBhyveFwCtlTxid; /* Send header and data */ BIov_SendAll (hIov); BIov_SendAll (Data); } /** Read a transport response and optional data from the i/o port. **/ STATIC RETURN_STATUS EFIAPI BhyveFwCtl_MsgRecv( OUT MSG_RX_HDR *Rhdr, OUT BIO_VEC Data[] ) { RETURN_STATUS Status; UINT32 *Dp; UINT32 Rd; UINT32 remLen; INT32 oLen; INT32 xLen; Rd = IoRead32 (FW_PORT); if (Rd < sizeof (MSG_RX_HDR)) { ; } /* Read in header and setup initial error */ Rhdr->Sz = Rd; Rhdr->Op = IoRead32 (FW_PORT); Rhdr->TxId = IoRead32 (FW_PORT); Rhdr->Err = IoRead32 (FW_PORT); /* Convert transport errno into UEFI error status */ Status = BhyveFwCtl_CvtErr (Rhdr->Err); remLen = Rd - sizeof (MSG_RX_HDR); xLen = 0; /* * A few cases to handle: * - the user didn't supply a read buffer * - the buffer is too small for the response * - the response is zero-length */ if (Data != NULL) { Dp = (UINT32 *)Data[0].Base; oLen = remLen; if (remLen > Data[0].Len) { Status = RETURN_BUFFER_TOO_SMALL; xLen = remLen - Data[0].Len; oLen = remLen = Data[0].Len; } while (remLen > 0) { *Dp++ = IoRead32 (FW_PORT); remLen -= sizeof (UINT32); } Data[0].Len = oLen; } else { /* No user data, but data returned - drop */ if (remLen > 0) { Status = RETURN_BUFFER_TOO_SMALL; xLen = remLen; } } /* Drop additional data */ while (xLen > 0) { (void) IoRead32 (FW_PORT); xLen -= sizeof (UINT32); } return Status; } STATIC RETURN_STATUS EFIAPI BhyveFwCtl_Msg( IN UINT32 OpCode, IN BIO_VEC Sdata[], OUT BIO_VEC Rdata[] ) { MSG_RX_HDR Rh; RETURN_STATUS Status; Status = RETURN_SUCCESS; BhyveFwCtl_MsgSend (OpCode, Sdata); Status = BhyveFwCtl_MsgRecv (&Rh, Rdata); mBhyveFwCtlTxid++; return Status; } STATIC RETURN_STATUS EFIAPI BhyveFwCtlGetLen ( IN CONST CHAR8 *Name, IN OUT UINT32 *Size ) { BIO_VEC Req[2], Resp[2]; RETURN_STATUS Status; Req[0].Base = (VOID *)Name; Req[0].Len = (UINT32)AsciiStrLen (Name) + 1; Req[1].Base = NULL; Resp[0].Base = Size; Resp[0].Len = sizeof (UINT32); Resp[1].Base = NULL; Status = BhyveFwCtl_Msg (OP_GET_LEN, Req, Resp); return Status; } #define FMAXSZ 1024 STATIC struct { UINT64 fSize; UINT32 fData[FMAXSZ]; } FwGetvalBuf; STATIC RETURN_STATUS EFIAPI BhyveFwCtlGetVal ( IN CONST CHAR8 *Name, OUT VOID *Item, IN OUT UINT32 *Size ) { BIO_VEC Req[2]; BIO_VEC Resp[2]; RETURN_STATUS Status; /* Make sure temp buffer is larger than passed-in size */ if (*Size > sizeof (FwGetvalBuf.fData)) return RETURN_INVALID_PARAMETER; Req[0].Base = (VOID *)Name; Req[0].Len = (UINT32)AsciiStrLen (Name) + 1; Req[1].Base = NULL; Resp[0].Base = &FwGetvalBuf; Resp[0].Len = sizeof (UINT64) + *Size; Resp[1].Base = NULL; Status = BhyveFwCtl_Msg (OP_GET, Req, Resp); /* * Copy out data on success (or on a truncated message). * XXX This step can be eliminted with Msg() supporting * multiple iovecs. */ if ((Status == RETURN_SUCCESS) || (Status == RETURN_BUFFER_TOO_SMALL)) { *Size = (UINT32)FwGetvalBuf.fSize; CopyMem (Item, FwGetvalBuf.fData, *Size); } return Status; } /** Front end to the internal GET_LEN and GET protocols **/ RETURN_STATUS EFIAPI BhyveFwCtlGet ( IN CONST CHAR8 *Name, OUT VOID *Item, IN OUT UINTN *Size ) { RETURN_STATUS Status; if (mBhyveFwCtlSupported == FALSE) return RETURN_UNSUPPORTED; if (Item == NULL) { Status = BhyveFwCtlGetLen (Name, (UINT32*)Size); } else { Status = BhyveFwCtlGetVal (Name, Item, (UINT32*)Size); } return Status; } /** Library initialization. Probe the host to see if the f/w ctl interface is supported. **/ RETURN_STATUS EFIAPI BhyveFwCtlInitialize ( VOID ) { UINT32 i; UINT8 ch; DEBUG ((DEBUG_INFO, "FwCtlInitialize\n")); IoWrite16 (FW_PORT, 0x0000); for (i = 0; i < 4; i++) { ch = IoRead8 (FW_IPORT); if (ch != mBhyveSig[i]) { DEBUG ((DEBUG_INFO, "Host f/w sig mismatch %c/%c\n", ch, mBhyveSig[i])); return RETURN_SUCCESS; } } mBhyveFwCtlSupported = TRUE; return RETURN_SUCCESS; }