blob: 36a692498aebd083f7db1e9816ebb3a93bc3d8ab [file]
/** @file
This module implements functions used by MPXY clients.
@par Glossary:
- MPXY - Message Proxy extension in the RISC-V SBI specification
Copyright (c) 2026, Qualcomm Technologies, Inc.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Base.h>
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseRiscVSbiLib.h>
#include <Library/DebugLib.h>
#include <Library/DxeRiscvMpxy.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/SafeIntLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#define MPXY_INVALID_SHMEM_ADDR MAX_UINTN
#define MAX_MPXY_OPEN_CHANNELS 32
STATIC VOID *mNonChanTempShmem = NULL;
STATIC VOID *mNonChanTempShmemPhys = NULL;
typedef struct {
BOOLEAN InUse;
UINTN ChannelId;
VOID *ShmemVirt;
VOID *ShmemPhys;
UINTN NrEfiPages;
UINT32 MsgDataMaxLen;
UINTN RefCount;
} MPXY_CHANNEL_CONTEXT;
STATIC MPXY_CHANNEL_CONTEXT mMpxyChannelCtx[MAX_MPXY_OPEN_CHANNELS];
///
/// Set Virtual Address Map Event
///
STATIC EFI_EVENT mDxeRiscVMpxyLibVirtualNotifyEvent = NULL;
STATIC BOOLEAN mMpxyLibraryInitialized = FALSE;
/**
Convert the physical channel shared memory address.
@param[in] Event The event that is being processed.
@param[in] Context The Event Context.
**/
VOID
EFIAPI
DxeRiscVMpxyLibVirtualNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
UINTN Index;
//
// If there have been no runtime registrations, then just return
//
if (mNonChanTempShmem == NULL) {
return;
}
for (Index = 0; Index < MAX_MPXY_OPEN_CHANNELS; Index++) {
if (mMpxyChannelCtx[Index].InUse && (mMpxyChannelCtx[Index].ShmemVirt != NULL)) {
gRT->ConvertPointer (0, (VOID **)&mMpxyChannelCtx[Index].ShmemVirt);
}
}
gRT->ConvertPointer (0, (VOID **)&mNonChanTempShmem);
}
/**
Find the channel context structure from a given ChannelID.
@param[in] ChannelId Id of the channel being searched
@retval On success, pointer to a MPXY channel context, NULL otherwise
**/
STATIC
MPXY_CHANNEL_CONTEXT *
FindChannelContext (
IN UINTN ChannelId
)
{
UINTN Index;
for (Index = 0; Index < MAX_MPXY_OPEN_CHANNELS; Index++) {
if (mMpxyChannelCtx[Index].InUse && (mMpxyChannelCtx[Index].ChannelId == ChannelId)) {
return &mMpxyChannelCtx[Index];
}
}
return NULL;
}
/**
Allocate a free channel context.
@retval Pointer to free channel context
**/
STATIC
MPXY_CHANNEL_CONTEXT *
AllocChannelContext (
VOID
)
{
UINTN Index;
for (Index = 0; Index < MAX_MPXY_OPEN_CHANNELS; Index++) {
if (!mMpxyChannelCtx[Index].InUse) {
return &mMpxyChannelCtx[Index];
}
}
return NULL;
}
/**
Get the size of shared memory for MPXY set with SBI.
@param[out] ShmemSize Size of the shared memory
@retval EFI_SUCCESS on success
@retval Error as returned by SBI translated to corresponding EFI error
**/
STATIC
EFI_STATUS
EFIAPI
SbiMpxyGetShmemSize (
OUT UINT64 *ShmemSize
)
{
SBI_RET Ret;
if (ShmemSize == NULL) {
return EFI_INVALID_PARAMETER;
}
Ret = SbiCall (
SBI_EXT_MPXY,
SBI_EXT_MPXY_GET_SHMEM_SIZE,
0
);
if (Ret.Error == SBI_SUCCESS) {
*ShmemSize = Ret.Value;
return EFI_SUCCESS;
}
*ShmemSize = 0;
return TranslateError (Ret.Error);
}
/**
Set shared memory for communication over MPXY channel. Optionally
previous address can be read.
@param[in] ShmemPhys Physical address of shared memory
@param[in] ShmemVirt Virtual address of shared memory
@param[out] PrevShmemPhys Physical address previously set
@param[in] ReadBackOldShmem Flag to read the previous physical address
@retval EFI_SUCCESS On success
@retval Error returned by SBI translated to corresponding EFI error
**/
STATIC
EFI_STATUS
EFIAPI
SbiMpxySetShmem (
IN UINTN ShmemPhys,
IN UINTN ShmemVirt,
OUT UINTN *PrevShmemPhys,
BOOLEAN ReadBackOldShmem
)
{
SBI_RET Ret;
UINTN Flags;
UINTN ShmemPhysLo;
UINTN ShmemPhysHi;
UINTN *PrevMemDet;
if (ReadBackOldShmem && ((PrevShmemPhys == NULL) || (ShmemVirt == 0))) {
return EFI_INVALID_PARAMETER;
}
Flags = ReadBackOldShmem ?
SBI_EXT_MPXY_SHMEM_FLAG_OVERWRITE_RETURN :
SBI_EXT_MPXY_SHMEM_FLAG_OVERWRITE;
if (ShmemPhys == MPXY_INVALID_SHMEM_ADDR) {
ShmemPhysLo = MAX_UINTN;
ShmemPhysHi = MAX_UINTN;
} else {
ShmemPhysLo = ShmemPhys;
/* Upper XLEN bits are unused for native-width addresses. */
ShmemPhysHi = 0;
}
Ret = SbiCall (
SBI_EXT_MPXY,
SBI_EXT_MPXY_SET_SHMEM,
3,
ShmemPhysLo,
ShmemPhysHi,
Flags
);
if (Ret.Error != SBI_SUCCESS) {
return TranslateError (Ret.Error);
}
if (ReadBackOldShmem) {
PrevMemDet = (UINTN *)ShmemVirt;
if ((PrevMemDet[0] == MAX_UINTN) && (PrevMemDet[1] == MAX_UINTN)) {
*PrevShmemPhys = MPXY_INVALID_SHMEM_ADDR;
} else {
*PrevShmemPhys = PrevMemDet[0];
}
}
return EFI_SUCCESS;
}
/**
Get list of MPXY channels available in SBI.
@param[in] StartIndex First channel ID to start list from
@param[out] ChannelList Array of the MPXY channels IDs returned by SBI
@param[out] Remaining Pointer containing the number of channel Ids to be read
@param[out] Returned Pointer to the number of channel Ids returned in this call
@retval EFI_SUCCESS on success
@retval EFI_NOT_READY if library is not initialised
@retval EFI error translated from error returned by SBI
**/
EFI_STATUS
EFIAPI
SbiMpxyGetChannelList (
IN UINTN StartIndex,
OUT UINTN *ChannelList,
OUT UINTN *Remaining,
OUT UINTN *Returned
)
{
UINTN OldShmemPhys;
EFI_STATUS Status;
EFI_STATUS RestoreStatus;
SBI_RET Ret;
UINT32 *Shmem;
UINTN Index;
if ((ChannelList == NULL) || (Remaining == NULL) || (Returned == NULL)) {
return EFI_INVALID_PARAMETER;
}
if (mNonChanTempShmem == NULL) {
return EFI_NOT_READY;
}
Shmem = mNonChanTempShmem;
/* Set the shared memory to memory allocated for non-channel specific reads */
Status = SbiMpxySetShmem (
(UINTN)mNonChanTempShmemPhys,
(UINTN)mNonChanTempShmem,
&OldShmemPhys,
TRUE /* Read back the old address */
);
if (EFI_ERROR (Status)) {
return Status;
}
Ret = SbiCall (
SBI_EXT_MPXY,
SBI_EXT_MPXY_GET_CHANNEL_IDS,
1,
StartIndex
);
if (Ret.Error != SBI_SUCCESS) {
Status = TranslateError (Ret.Error);
goto RestoreShmem;
}
/* Index 0 contains number of channels pending to be read */
*Remaining = Shmem[0];
/* Number of channels returned */
if (Shmem[1] > 0) {
for (Index = 0; Index < Shmem[1]; Index++) {
ChannelList[Index] = Shmem[Index + 2];
}
}
*Returned = Shmem[1];
/* Switch back to old shared memory */
Status = EFI_SUCCESS;
RestoreShmem:
RestoreStatus = SbiMpxySetShmem (
OldShmemPhys,
MPXY_INVALID_SHMEM_ADDR,
NULL,
FALSE /* Read back the old address */
);
if (EFI_ERROR (RestoreStatus)) {
return RestoreStatus;
}
return Status;
}
/**
Read MPXY channel attributes from the SBI.
@param[in] ChannelId ID of the channel to read attributes
@param[in] BaseAttrId Base Attribute ID from where to read attributes
@param[in] NrAttrs Number of attributes to read
@param[out] Attrs Array of the attributes read
@retval EFI_SUCCESS on success
@retval EFI_NOT_READY if MPXY library is not initialized
@retval EFI error translated from SBI error code
**/
EFI_STATUS
EFIAPI
SbiMpxyReadChannelAttrs (
IN UINTN ChannelId,
IN UINT32 BaseAttrId,
IN UINT32 NrAttrs,
OUT UINT32 *Attrs
)
{
UINTN OldShmemPhys;
EFI_STATUS Status;
EFI_STATUS RestoreStatus;
SBI_RET Ret;
if ((Attrs == NULL) || (NrAttrs == 0)) {
return EFI_INVALID_PARAMETER;
}
if (mNonChanTempShmem == NULL) {
return EFI_NOT_READY;
}
/* Set the shared memory to memory allocated for non-channel specific reads */
Status = SbiMpxySetShmem (
(UINTN)mNonChanTempShmemPhys,
(UINTN)mNonChanTempShmem,
&OldShmemPhys,
TRUE /* Read back the old address */
);
if (EFI_ERROR (Status)) {
return Status;
}
Ret = SbiCall (
SBI_EXT_MPXY,
SBI_EXT_MPXY_READ_ATTRS,
3,
ChannelId,
BaseAttrId, /* Base attribute Id */
NrAttrs /* Number of attributes */
);
if (Ret.Error != SBI_SUCCESS) {
Status = TranslateError (Ret.Error);
goto RestoreShmem;
}
CopyMem (
Attrs,
mNonChanTempShmem,
sizeof (UINT32) * NrAttrs
);
Status = EFI_SUCCESS;
RestoreShmem:
RestoreStatus = SbiMpxySetShmem (
OldShmemPhys,
MPXY_INVALID_SHMEM_ADDR,
NULL,
FALSE /* Read back the old address */
);
if (EFI_ERROR (RestoreStatus)) {
return RestoreStatus;
}
return Status;
}
/**
Open an MPXY channel identified by the channel Id.
@param[in] ChannelId Id of the channel to be opened
@retval EFI_SUCCESS On success
@retval EFI_NOT_READY if library is not initialized
@retval EFI_INVALID_PARAMETER if Channel information or Channel Is is wrong
@retval EFI_OUT_OF_RESOURCES if new channel cannot be allocated or allocation
of the shared memory fails
**/
EFI_STATUS
EFIAPI
SbiMpxyChannelOpen (
IN UINTN ChannelId
)
{
MPXY_CHANNEL_CONTEXT *ChannelCtx;
UINT32 Attributes[MpxyChanAttrMax]; // space to read id and version
UINT32 ChanDataLen;
VOID *SbiShmem;
UINTN NrEfiPages;
EFI_STATUS Status;
if (mNonChanTempShmem == NULL) {
return EFI_NOT_READY;
}
ChannelCtx = FindChannelContext (ChannelId);
if (ChannelCtx != NULL) {
ChannelCtx->RefCount++;
return EFI_SUCCESS;
}
Status = SbiMpxyReadChannelAttrs (
ChannelId,
0,
MpxyChanAttrMax,
&Attributes[0]
);
if (EFI_ERROR (Status)) {
return Status;
}
ChanDataLen = Attributes[MpxyChanAttrMsgDataMaxLen];
if (ChanDataLen == 0) {
return EFI_INVALID_PARAMETER;
}
NrEfiPages = EFI_SIZE_TO_PAGES (ChanDataLen);
ChannelCtx = AllocChannelContext ();
if (ChannelCtx == NULL) {
return EFI_OUT_OF_RESOURCES;
}
SbiShmem = AllocateAlignedRuntimePages (NrEfiPages, EFI_PAGE_SIZE);
if (SbiShmem == NULL) {
return EFI_OUT_OF_RESOURCES;
}
ZeroMem (ChannelCtx, sizeof (*ChannelCtx));
ChannelCtx->InUse = TRUE;
ChannelCtx->ChannelId = ChannelId;
ChannelCtx->ShmemVirt = SbiShmem;
ChannelCtx->ShmemPhys = SbiShmem;
ChannelCtx->NrEfiPages = NrEfiPages;
ChannelCtx->MsgDataMaxLen = ChanDataLen;
ChannelCtx->RefCount = 1;
return EFI_SUCCESS;
}
/**
Close a given MPXY channel.
@param[in] ChannelId Id of the channel to be closed
@retval EFI_SUCCESS on success
@retval EFI_NOT_FOUND is context related to ChannelId could not be found
**/
EFI_STATUS
EFIAPI
SbiMpxyChannelClose (
IN UINTN ChannelId
)
{
MPXY_CHANNEL_CONTEXT *ChannelCtx;
ChannelCtx = FindChannelContext (ChannelId);
if (ChannelCtx == NULL) {
return EFI_NOT_FOUND;
}
if (ChannelCtx->RefCount > 1) {
ChannelCtx->RefCount--;
return EFI_SUCCESS;
}
if ((ChannelCtx->ShmemVirt != NULL) && (ChannelCtx->NrEfiPages != 0)) {
FreeAlignedPages (ChannelCtx->ShmemVirt, ChannelCtx->NrEfiPages);
}
ZeroMem (ChannelCtx, sizeof (*ChannelCtx));
return EFI_SUCCESS;
}
/**
Send a message over an MPXY channel.
@param[in] ChannelId Id of the channel on which message is to be sent
@param[in] MessageId Id of the message being sent
@param[in] Message Pointer to the buffer containing the message
@param[in] MessageDataLen Size of the message being sent
@param[out] Response Pointer to the buffer on which response will be received
@param[out] ResponseLen Size of the response
@retval EFI_SUCCESS on success
@retval EFI_NOT_READY if channel context is not found or is invalid
@retval EFI_INVALID_PARAMETER if length of the message data is more than channel can carry
@retval EFI error translated from error returned by SBI
**/
EFI_STATUS
EFIAPI
SbiMpxySendMessage (
IN UINTN ChannelId,
IN UINTN MessageId,
IN VOID *Message,
IN UINTN MessageDataLen,
OUT VOID *Response,
OUT UINTN *ResponseLen
)
{
MPXY_CHANNEL_CONTEXT *ChannelCtx;
SBI_RET Ret;
EFI_STATUS Status;
EFI_STATUS RestoreStatus;
UINTN PrevShmemPhys;
UINTN Virt;
if (mNonChanTempShmem == NULL) {
return EFI_NOT_READY;
}
ChannelCtx = FindChannelContext (ChannelId);
if ((ChannelCtx == NULL) || !ChannelCtx->InUse || (ChannelCtx->ShmemVirt == NULL)) {
return EFI_NOT_READY;
}
if (MessageDataLen > ChannelCtx->MsgDataMaxLen) {
return EFI_INVALID_PARAMETER;
}
Status = SbiMpxySetShmem (
(UINTN)ChannelCtx->ShmemPhys,
(UINTN)ChannelCtx->ShmemVirt,
&PrevShmemPhys,
TRUE
);
if (EFI_ERROR (Status)) {
return Status;
}
Virt = (UINTN)ChannelCtx->ShmemVirt;
/* Copy message to Hart's shared memory */
CopyMem (
(VOID *)Virt,
Message,
MessageDataLen
);
Ret = SbiCall (
SBI_EXT_MPXY,
SBI_EXT_MPXY_SEND_MSG_WITH_RESP,
3,
ChannelId,
MessageId,
MessageDataLen
);
if ((Ret.Error == SBI_SUCCESS) && (Response != NULL)) {
/* Copy the response to out buffer */
CopyMem (
Response,
(const VOID *)Virt,
Ret.Value
);
if (ResponseLen != NULL) {
*ResponseLen = Ret.Value;
}
}
RestoreStatus = SbiMpxySetShmem (
PrevShmemPhys,
MPXY_INVALID_SHMEM_ADDR,
NULL,
FALSE
);
if (EFI_ERROR (RestoreStatus)) {
return RestoreStatus;
}
return TranslateError (Ret.Error);
}
/**
Initialize the MPXY library
@retval EFI_SUCCESS Library is successfully initialized.
@retval EFI_ALREADY_STARTED Library is already initialized.
@retval EFI_OUT_OF_RESOURCES Not enough memory to allocate.
**/
RETURN_STATUS
EFIAPI
SbiMpxyLibInit (
VOID
)
{
EFI_STATUS Status;
UINT64 ShmemSize;
if (mMpxyLibraryInitialized) {
return EFI_ALREADY_STARTED;
}
Status = SbiProbeExtension (SBI_EXT_MPXY);
if (EFI_ERROR (Status)) {
return Status;
}
Status = SbiMpxyGetShmemSize (&ShmemSize);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Allocate memory to be shared with OpenSBI for initial MPXY communications
// until channels are initialized by their respective drivers.
//
mNonChanTempShmem = AllocateAlignedRuntimePages (
EFI_SIZE_TO_PAGES (ShmemSize),
ShmemSize // Align
);
if (mNonChanTempShmem == NULL) {
return EFI_OUT_OF_RESOURCES;
}
mNonChanTempShmemPhys = mNonChanTempShmem;
//
// Register SetVirtualAddressMap () notify function
//
Status = gBS->CreateEvent (
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
TPL_NOTIFY,
DxeRiscVMpxyLibVirtualNotify,
NULL,
&mDxeRiscVMpxyLibVirtualNotifyEvent
);
if (EFI_ERROR (Status)) {
FreeAlignedPages (mNonChanTempShmem, EFI_SIZE_TO_PAGES (ShmemSize));
mNonChanTempShmem = NULL;
mNonChanTempShmemPhys = NULL;
return Status;
}
mMpxyLibraryInitialized = TRUE;
return Status;
}