/** @file

  Helper functions used by at least two Simple Network Protocol methods.

  Copyright (C) 2013, Red Hat, Inc.

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Library/MemoryAllocationLib.h>

#include "VirtioNet.h"

//
// The user structure for the ordered collection that will track the mapping
// info of the packets queued in TxRing
//
typedef struct {
  VOID                    *Buffer;
  EFI_PHYSICAL_ADDRESS    DeviceAddress; // lookup key for reverse mapping
  VOID                    *BufMap;
} TX_BUF_MAP_INFO;

/**
  Release RX and TX resources on the boundary of the
  EfiSimpleNetworkInitialized state.

  These functions contribute to rolling back a partial, failed initialization
  of the virtio-net SNP driver instance, or to shutting down a fully
  initialized, running instance.

  They are only callable by the VirtioNetInitialize() and the
  VirtioNetShutdown() SNP methods. See the state diagram in "VirtioNet.h".

  @param[in,out] Dev  The VNET_DEV driver instance being shut down, or whose
                      partial, failed initialization is being rolled back.
*/
VOID
EFIAPI
VirtioNetShutdownRx (
  IN OUT VNET_DEV  *Dev
  )
{
  Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Dev->RxBufMap);
  Dev->VirtIo->FreeSharedPages (
                 Dev->VirtIo,
                 Dev->RxBufNrPages,
                 Dev->RxBuf
                 );
}

VOID
EFIAPI
VirtioNetShutdownTx (
  IN OUT VNET_DEV  *Dev
  )
{
  ORDERED_COLLECTION_ENTRY  *Entry, *Entry2;
  TX_BUF_MAP_INFO           *TxBufMapInfo;
  VOID                      *UserStruct;

  Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Dev->TxSharedReqMap);
  Dev->VirtIo->FreeSharedPages (
                 Dev->VirtIo,
                 EFI_SIZE_TO_PAGES (sizeof *(Dev->TxSharedReq)),
                 Dev->TxSharedReq
                 );

  for (Entry = OrderedCollectionMin (Dev->TxBufCollection);
       Entry != NULL;
       Entry = Entry2)
  {
    Entry2 = OrderedCollectionNext (Entry);
    OrderedCollectionDelete (Dev->TxBufCollection, Entry, &UserStruct);
    TxBufMapInfo = UserStruct;
    Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, TxBufMapInfo->BufMap);
    FreePool (TxBufMapInfo);
  }

  OrderedCollectionUninit (Dev->TxBufCollection);

  FreePool (Dev->TxFreeStack);
}

/**
  Release TX and RX VRING resources.

  @param[in,out] Dev       The VNET_DEV driver instance which was using
                           the ring.
  @param[in,out] Ring      The virtio ring to clean up.
  @param[in]     RingMap   A token return from the VirtioRingMap()
*/
VOID
EFIAPI
VirtioNetUninitRing (
  IN OUT VNET_DEV  *Dev,
  IN OUT VRING     *Ring,
  IN     VOID      *RingMap
  )
{
  Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, RingMap);
  VirtioRingUninit (Dev->VirtIo, Ring);
}

/**
  Map Caller-supplied TxBuf buffer to the device-mapped address

  @param[in]    Dev               The VNET_DEV driver instance which wants to
                                  map the Tx packet.
  @param[in]    Buffer            The system physical address of TxBuf
  @param[in]    NumberOfBytes     Number of bytes to map
  @param[out]   DeviceAddress     The resulting device address for the bus
                                  master access.

  @retval EFI_OUT_OF_RESOURCES    The request could not be completed due to
                                  a lack of resources.
  @return                         Status codes from
                                  VirtioMapAllBytesInSharedBuffer()
  @retval EFI_SUCCESS             Caller-supplied buffer is successfully mapped.
*/
EFI_STATUS
EFIAPI
VirtioNetMapTxBuf (
  IN  VNET_DEV              *Dev,
  IN  VOID                  *Buffer,
  IN  UINTN                 NumberOfBytes,
  OUT EFI_PHYSICAL_ADDRESS  *DeviceAddress
  )
{
  EFI_STATUS            Status;
  TX_BUF_MAP_INFO       *TxBufMapInfo;
  EFI_PHYSICAL_ADDRESS  Address;
  VOID                  *Mapping;

  TxBufMapInfo = AllocatePool (sizeof (*TxBufMapInfo));
  if (TxBufMapInfo == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = VirtioMapAllBytesInSharedBuffer (
             Dev->VirtIo,
             VirtioOperationBusMasterRead,
             Buffer,
             NumberOfBytes,
             &Address,
             &Mapping
             );
  if (EFI_ERROR (Status)) {
    goto FreeTxBufMapInfo;
  }

  TxBufMapInfo->Buffer        = Buffer;
  TxBufMapInfo->DeviceAddress = Address;
  TxBufMapInfo->BufMap        = Mapping;

  Status = OrderedCollectionInsert (
             Dev->TxBufCollection,
             NULL,
             TxBufMapInfo
             );
  switch (Status) {
    case EFI_OUT_OF_RESOURCES:
      goto UnmapTxBuf;
    case EFI_ALREADY_STARTED:
      //
      // This should never happen: it implies
      //
      // - an identity-mapping VIRTIO_DEVICE_PROTOCOL.MapSharedBuffer()
      //   implementation -- which is fine,
      //
      // - and an SNP client that queues multiple instances of the exact same
      //   buffer address with SNP.Transmit() -- which is undefined behavior,
      //   based on the TxBuf language in UEFI-2.7,
      //   EFI_SIMPLE_NETWORK.GetStatus().
      //
      ASSERT (FALSE);
      Status = EFI_INVALID_PARAMETER;
      goto UnmapTxBuf;
    default:
      ASSERT_EFI_ERROR (Status);
      break;
  }

  *DeviceAddress = Address;
  return EFI_SUCCESS;

UnmapTxBuf:
  Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Mapping);

FreeTxBufMapInfo:
  FreePool (TxBufMapInfo);
  return Status;
}

/**
  Unmap (aka reverse mapping) device mapped TxBuf buffer to the system
  physical address

  @param[in]    Dev               The VNET_DEV driver instance which wants to
                                  reverse- and unmap the Tx packet.
  @param[out]   Buffer            The system physical address of TxBuf
  @param[in]    DeviceAddress     The device address for the TxBuf

  @retval EFI_INVALID_PARAMETER   The DeviceAddress is not mapped
  @retval EFI_SUCCESS             The TxBuf at DeviceAddress has been unmapped,
                                  and Buffer has been set to TxBuf's system
                                  physical address.

*/
EFI_STATUS
EFIAPI
VirtioNetUnmapTxBuf (
  IN  VNET_DEV              *Dev,
  OUT VOID                  **Buffer,
  IN  EFI_PHYSICAL_ADDRESS  DeviceAddress
  )
{
  ORDERED_COLLECTION_ENTRY  *Entry;
  TX_BUF_MAP_INFO           *TxBufMapInfo;
  VOID                      *UserStruct;

  Entry = OrderedCollectionFind (Dev->TxBufCollection, &DeviceAddress);
  if (Entry == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  OrderedCollectionDelete (Dev->TxBufCollection, Entry, &UserStruct);

  TxBufMapInfo = UserStruct;

  *Buffer = TxBufMapInfo->Buffer;
  Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, TxBufMapInfo->BufMap);
  FreePool (TxBufMapInfo);

  return EFI_SUCCESS;
}

/**
  Comparator function for two TX_BUF_MAP_INFO objects.

  @param[in] UserStruct1  Pointer to the first TX_BUF_MAP_INFO object.

  @param[in] UserStruct2  Pointer to the second TX_BUF_MAP_INFO object.

  @retval <0  If UserStruct1 compares less than UserStruct2.

  @retval  0  If UserStruct1 compares equal to UserStruct2.

  @retval >0  If UserStruct1 compares greater than UserStruct2.
*/
INTN
EFIAPI
VirtioNetTxBufMapInfoCompare (
  IN CONST VOID  *UserStruct1,
  IN CONST VOID  *UserStruct2
  )
{
  CONST TX_BUF_MAP_INFO  *MapInfo1;
  CONST TX_BUF_MAP_INFO  *MapInfo2;

  MapInfo1 = UserStruct1;
  MapInfo2 = UserStruct2;

  return MapInfo1->DeviceAddress < MapInfo2->DeviceAddress ? -1 :
         MapInfo1->DeviceAddress > MapInfo2->DeviceAddress ?  1 :
         0;
}

/**
  Compare a standalone DeviceAddress against a TX_BUF_MAP_INFO object
  containing an embedded DeviceAddress.

  @param[in] StandaloneKey  Pointer to DeviceAddress, which has type
                            EFI_PHYSICAL_ADDRESS.

  @param[in] UserStruct     Pointer to the TX_BUF_MAP_INFO object with the
                            embedded DeviceAddress.

  @retval <0  If StandaloneKey compares less than UserStruct's key.

  @retval  0  If StandaloneKey compares equal to UserStruct's key.

  @retval >0  If StandaloneKey compares greater than UserStruct's key.
**/
INTN
EFIAPI
VirtioNetTxBufDeviceAddressCompare (
  IN CONST VOID  *StandaloneKey,
  IN CONST VOID  *UserStruct
  )
{
  CONST EFI_PHYSICAL_ADDRESS  *DeviceAddress;
  CONST TX_BUF_MAP_INFO       *MapInfo;

  DeviceAddress = StandaloneKey;
  MapInfo       = UserStruct;

  return *DeviceAddress < MapInfo->DeviceAddress ? -1 :
         *DeviceAddress > MapInfo->DeviceAddress ?  1 :
         0;
}
