blob: cc3e64f0c44c0826abce7e3352cb07e995abe0b9 [file] [log] [blame]
/** @file
Copyright (c) 2020, Rebecca Cran <rebecca@bsdio.com>
Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.<BR>
Copyright (C) 2013, Red Hat, Inc.
Copyright (c) 2015, Nahanni Systems.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Uefi.h"
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BhyveFwCtlLib.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#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;
}