/** @file

  Copyright (c) 2016 - 2017, Socionext Inc. All rights reserved.<BR>
  Copyright (c) 2017, Linaro, Ltd. All rights reserved.<BR>

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

**/

#include "pfdep.h"

#include <Library/DebugLib.h>
#include <Library/DmaLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/NetLib.h>
#include <Library/SynchronizationLib.h>

/**********************************************************************
 * Variable definitions
 **********************************************************************/
pfdep_debug_level_t pfdep_debug_level = PFDEP_DEBUG_LEVEL_NOTICE;


/**********************************************************************
 * Function definitions
 **********************************************************************/

VOID*
pfdep_malloc (
  IN  pfdep_uint32    len
  )
{
  return AllocatePool (len);
}

VOID
pfdep_free (
  IN  VOID       *addr
  )
{
  FreePool (addr);
}

pfdep_err_t
pfdep_dma_malloc (
  IN  pfdep_dev_handle_t        dev_handle,
  IN  pfdep_uint32              len,
  OUT VOID                      **addr_p,
  OUT pfdep_phys_addr_t         *phys_addr_p
  )
{
  EFI_STATUS    Status;
  UINTN         NumPages;
  UINTN         NumBytes;
  VOID          **Mapping;

  NumPages = EFI_SIZE_TO_PAGES (ALIGN_VARIABLE(len) + sizeof *Mapping);
  Status = DmaAllocateBuffer (EfiBootServicesData, NumPages, addr_p);
  if (EFI_ERROR (Status)) {
    return PFDEP_ERR_ALLOC;
  }

  //
  // Stash the address of the MAPINFO struct at the end of the buffer,
  // but make sure it appears aligned (the memory may be mapped uncached)
  //
  Mapping = (VOID **)((UINTN)*addr_p + ALIGN_VARIABLE(len));

  NumBytes = EFI_PAGES_TO_SIZE (NumPages);
  Status = DmaMap (MapOperationBusMasterCommonBuffer, *addr_p, &NumBytes,
             phys_addr_p, Mapping);

  if (EFI_ERROR (Status) || NumBytes < len) {
    DmaFreeBuffer (NumPages, *addr_p);
    return PFDEP_ERR_ALLOC;
  }

  return PFDEP_ERR_OK;
}

VOID
pfdep_dma_free (
  IN  pfdep_dev_handle_t        dev_handle,
  IN  pfdep_uint32              len,
  IN  VOID                      *addr,
  IN  pfdep_phys_addr_t         phys_addr
  )
{
  VOID          *Mapping;

  Mapping = *(VOID **)((UINTN)addr + ALIGN_VARIABLE(len));

  DmaUnmap (Mapping);
  DmaFreeBuffer (EFI_SIZE_TO_PAGES (ALIGN_VARIABLE(len) + sizeof Mapping),
    addr);
}

//
// On the receive path, we allocate a new packet and link it into the RX ring
// before returning the received packet to the caller. This means we perform
// one allocation and one free operation for each buffer received.
// So let's cache a single packet, and get rid of most of the alloc/free
// overhead on the RX path.
//
STATIC pfdep_pkt_handle_t mSparePacketBuffer;
STATIC UINT32 mSparePacketBufferSize;

pfdep_err_t
pfdep_alloc_pkt_buf (
  IN  pfdep_dev_handle_t        dev_handle,
  IN  pfdep_uint16              len,
  OUT VOID                      **addr_p,
  OUT pfdep_phys_addr_t         *phys_addr_p,
  OUT pfdep_pkt_handle_t        *pkt_handle_p
  )
{
  EFI_STATUS    Status;
  UINTN         NumBytes;

  NumBytes = ALIGN_VALUE (len, mCpu->DmaBufferAlignment);

  if (InterlockedCompareExchange32 (&mSparePacketBufferSize, len, 0) == len) {
    *pkt_handle_p = mSparePacketBuffer;
  } else {
    *pkt_handle_p = AllocateZeroPool (NumBytes + sizeof(PACKET_HANDLE) +
                                      (mCpu->DmaBufferAlignment - 8));
    if (*pkt_handle_p == NULL) {
      return PFDEP_ERR_ALLOC;
    }

    (*pkt_handle_p)->Buffer = ALIGN_POINTER (*pkt_handle_p + 1,
                                             mCpu->DmaBufferAlignment);
  }

  *addr_p = (*pkt_handle_p)->Buffer;
  Status = DmaMap (MapOperationBusMasterWrite, *addr_p, &NumBytes, phys_addr_p,
             &(*pkt_handle_p)->Mapping);
  if (EFI_ERROR (Status) || NumBytes < len) {
    FreePool (*pkt_handle_p);
    return PFDEP_ERR_ALLOC;
  }
  return PFDEP_ERR_OK;
}

VOID
pfdep_free_pkt_buf (
  IN  pfdep_dev_handle_t        dev_handle,
  IN  pfdep_uint16              len,
  IN  VOID                      *addr,
  IN  pfdep_phys_addr_t         phys_addr,
  IN  pfdep_bool                last_flag,
  IN  pfdep_pkt_handle_t        pkt_handle
  )
{
  if (last_flag != PFDEP_TRUE) {
    return;
  }

  if (pkt_handle->Mapping != NULL) {
    DmaUnmap (pkt_handle->Mapping);
  }

  if (pkt_handle->RecycleForTx) {
      pkt_handle->Released = TRUE;
  } else if (!InterlockedCompareExchange32 (&mSparePacketBufferSize, 0, len)) {
    mSparePacketBuffer = pkt_handle;
  } else {
    FreePool (pkt_handle);
  }
}
