/** @file
  Grant Table function implementation.

  Grant Table are used to grant access to certain page of the current
  VM to an other VM.

  Author: Steven Smith (sos22@cam.ac.uk)
  Changes: Grzegorz Milos (gm281@cam.ac.uk)
  Copyright (C) 2006, Cambridge University
  Copyright (C) 2014, Citrix Ltd.

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "XenBusDxe.h"

#include <IndustryStandard/Xen/memory.h>

#include <Library/XenHypercallLib.h>
#include <Library/SynchronizationLib.h>

#include "GrantTable.h"

#define NR_RESERVED_ENTRIES  8

#define NR_GRANT_FRAMES   (FixedPcdGet32 (PcdXenGrantFrames))
#define NR_GRANT_ENTRIES  (NR_GRANT_FRAMES * EFI_PAGE_SIZE / sizeof(grant_entry_v1_t))

STATIC grant_entry_v1_t  *GrantTable = NULL;
STATIC grant_ref_t       GrantList[NR_GRANT_ENTRIES];
STATIC EFI_LOCK          mGrantListLock;
#ifdef GNT_DEBUG
STATIC BOOLEAN  GrantInUseList[NR_GRANT_ENTRIES];
#endif

STATIC
VOID
XenGrantTablePutFreeEntry (
  grant_ref_t  Ref
  )
{
  EfiAcquireLock (&mGrantListLock);
 #ifdef GNT_DEBUG
  ASSERT (GrantInUseList[Ref]);
  GrantInUseList[Ref] = FALSE;
 #endif
  GrantList[Ref] = GrantList[0];
  GrantList[0]   = Ref;
  EfiReleaseLock (&mGrantListLock);
}

STATIC
grant_ref_t
XenGrantTableGetFreeEntry (
  VOID
  )
{
  grant_ref_t  Ref;

  EfiAcquireLock (&mGrantListLock);
  Ref = GrantList[0];
  ASSERT (Ref >= NR_RESERVED_ENTRIES && Ref < NR_GRANT_ENTRIES);
  GrantList[0] = GrantList[Ref];
 #ifdef GNT_DEBUG
  ASSERT (!GrantInUseList[Ref]);
  GrantInUseList[Ref] = TRUE;
 #endif
  EfiReleaseLock (&mGrantListLock);
  return Ref;
}

STATIC
grant_ref_t
XenGrantTableGrantAccess (
  IN domid_t  DomainId,
  IN UINTN    Frame,
  IN BOOLEAN  ReadOnly
  )
{
  grant_ref_t  Ref;
  UINT16       Flags;

  ASSERT (GrantTable != NULL);
  Ref                   = XenGrantTableGetFreeEntry ();
  GrantTable[Ref].frame = (UINT32)Frame;
  GrantTable[Ref].domid = DomainId;
  MemoryFence ();
  Flags = GTF_permit_access;
  if (ReadOnly) {
    Flags |= GTF_readonly;
  }

  GrantTable[Ref].flags = Flags;

  return Ref;
}

STATIC
EFI_STATUS
XenGrantTableEndAccess (
  grant_ref_t  Ref
  )
{
  UINT16  Flags, OldFlags;

  ASSERT (GrantTable != NULL);
  ASSERT (Ref >= NR_RESERVED_ENTRIES && Ref < NR_GRANT_ENTRIES);

  OldFlags = GrantTable[Ref].flags;
  do {
    if ((Flags = OldFlags) & (GTF_reading | GTF_writing)) {
      DEBUG ((DEBUG_WARN, "WARNING: g.e. still in use! (%x)\n", Flags));
      return EFI_NOT_READY;
    }

    OldFlags = InterlockedCompareExchange16 (&GrantTable[Ref].flags, Flags, 0);
  } while (OldFlags != Flags);

  XenGrantTablePutFreeEntry (Ref);
  return EFI_SUCCESS;
}

VOID
XenGrantTableInit (
  IN XENBUS_DEVICE  *Dev
  )
{
  xen_add_to_physmap_t  Parameters;
  INTN                  Index;
  INTN                  ReturnCode;

 #ifdef GNT_DEBUG
  SetMem (GrantInUseList, sizeof (GrantInUseList), 1);
 #endif
  EfiInitializeLock (&mGrantListLock, TPL_NOTIFY);
  for (Index = NR_RESERVED_ENTRIES; Index < NR_GRANT_ENTRIES; Index++) {
    XenGrantTablePutFreeEntry ((grant_ref_t)Index);
  }

  GrantTable = (VOID *)(UINTN)Dev->XenIo->GrantTableAddress;
  for (Index = 0; Index < NR_GRANT_FRAMES; Index++) {
    Parameters.domid = DOMID_SELF;
    Parameters.idx   = Index;
    Parameters.space = XENMAPSPACE_grant_table;
    Parameters.gpfn  = (xen_pfn_t)((UINTN)GrantTable >> EFI_PAGE_SHIFT) + Index;
    ReturnCode       = XenHypercallMemoryOp (XENMEM_add_to_physmap, &Parameters);
    if (ReturnCode != 0) {
      DEBUG ((
        DEBUG_ERROR,
        "Xen GrantTable, add_to_physmap hypercall error: %Ld\n",
        (INT64)ReturnCode
        ));
    }
  }
}

VOID
XenGrantTableDeinit (
  XENBUS_DEVICE  *Dev
  )
{
  INTN                       ReturnCode, Index;
  xen_remove_from_physmap_t  Parameters;

  if (GrantTable == NULL) {
    return;
  }

  for (Index = NR_GRANT_FRAMES - 1; Index >= 0; Index--) {
    Parameters.domid = DOMID_SELF;
    Parameters.gpfn  = (xen_pfn_t)((UINTN)GrantTable >> EFI_PAGE_SHIFT) + Index;
    DEBUG ((
      DEBUG_INFO,
      "Xen GrantTable, removing %Lx\n",
      (UINT64)Parameters.gpfn
      ));
    ReturnCode = XenHypercallMemoryOp (XENMEM_remove_from_physmap, &Parameters);
    if (ReturnCode != 0) {
      DEBUG ((
        DEBUG_ERROR,
        "Xen GrantTable, remove_from_physmap hypercall error: %Ld\n",
        (INT64)ReturnCode
        ));
    }
  }

  GrantTable = NULL;
}

EFI_STATUS
EFIAPI
XenBusGrantAccess (
  IN  XENBUS_PROTOCOL  *This,
  IN  domid_t          DomainId,
  IN  UINTN            Frame, // MFN
  IN  BOOLEAN          ReadOnly,
  OUT grant_ref_t      *RefPtr
  )
{
  *RefPtr = XenGrantTableGrantAccess (DomainId, Frame, ReadOnly);
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
XenBusGrantEndAccess (
  IN XENBUS_PROTOCOL  *This,
  IN grant_ref_t      Ref
  )
{
  return XenGrantTableEndAccess (Ref);
}
