/** @file

  Implementation of the SNP.Receive() function and its private helpers if any.

  Copyright (C) 2013, Red Hat, Inc.
  Copyright (c) 2006 - 2013, Intel Corporation. All rights reserved.<BR>

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

**/

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include "VirtioNet.h"

/**
  Receives a packet from a network interface.

  @param  This       The protocol instance pointer.
  @param  HeaderSize The size, in bytes, of the media header received on the
                     network interface. If this parameter is NULL, then the
                     media header size will not be returned.
  @param  BufferSize On entry, the size, in bytes, of Buffer. On exit, the
                     size, in bytes, of the packet that was received on the
                     network interface.
  @param  Buffer     A pointer to the data buffer to receive both the media
                     header and the data.
  @param  SrcAddr    The source HW MAC address. If this parameter is NULL, the
                     HW MAC source address will not be extracted from the media
                     header.
  @param  DestAddr   The destination HW MAC address. If this parameter is NULL,
                     the HW MAC destination address will not be extracted from
                     the media header.
  @param  Protocol   The media header type. If this parameter is NULL, then the
                     protocol will not be extracted from the media header. See
                     RFC 1700 section "Ether Types" for examples.

  @retval  EFI_SUCCESS           The received data was stored in Buffer, and
                                 BufferSize has been updated to the number of
                                 bytes received.
  @retval  EFI_NOT_STARTED       The network interface has not been started.
  @retval  EFI_NOT_READY         The network interface is too busy to accept
                                 this transmit request.
  @retval  EFI_BUFFER_TOO_SMALL  The BufferSize parameter is too small.
  @retval  EFI_INVALID_PARAMETER One or more of the parameters has an
                                 unsupported value.
  @retval  EFI_DEVICE_ERROR      The command could not be sent to the network
                                 interface.
  @retval  EFI_UNSUPPORTED       This function is not supported by the network
                                 interface.

**/

EFI_STATUS
EFIAPI
VirtioNetReceive (
  IN EFI_SIMPLE_NETWORK_PROTOCOL *This,
  OUT UINTN                      *HeaderSize OPTIONAL,
  IN OUT UINTN                   *BufferSize,
  OUT VOID                       *Buffer,
  OUT EFI_MAC_ADDRESS            *SrcAddr    OPTIONAL,
  OUT EFI_MAC_ADDRESS            *DestAddr   OPTIONAL,
  OUT UINT16                     *Protocol   OPTIONAL
  )
{
  VNET_DEV   *Dev;
  EFI_TPL    OldTpl;
  EFI_STATUS Status;
  UINT16     RxCurUsed;
  UINT16     UsedElemIdx;
  UINT32     DescIdx;
  UINT32     RxLen;
  UINTN      OrigBufferSize;
  UINT8      *RxPtr;
  UINT16     AvailIdx;
  EFI_STATUS NotifyStatus;
  UINTN      RxBufOffset;

  if (This == NULL || BufferSize == NULL || Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Dev = VIRTIO_NET_FROM_SNP (This);
  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
  switch (Dev->Snm.State) {
  case EfiSimpleNetworkStopped:
    Status = EFI_NOT_STARTED;
    goto Exit;
  case EfiSimpleNetworkStarted:
    Status = EFI_DEVICE_ERROR;
    goto Exit;
  default:
    break;
  }

  //
  // virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device
  //
  MemoryFence ();
  RxCurUsed = *Dev->RxRing.Used.Idx;
  MemoryFence ();

  if (Dev->RxLastUsed == RxCurUsed) {
    Status = EFI_NOT_READY;
    goto Exit;
  }

  UsedElemIdx = Dev->RxLastUsed % Dev->RxRing.QueueSize;
  DescIdx = Dev->RxRing.Used.UsedElem[UsedElemIdx].Id;
  RxLen   = Dev->RxRing.Used.UsedElem[UsedElemIdx].Len;

  //
  // the virtio-net request header must be complete; we skip it
  //
  ASSERT (RxLen >= Dev->RxRing.Desc[DescIdx].Len);
  RxLen -= Dev->RxRing.Desc[DescIdx].Len;
  //
  // the host must not have filled in more data than requested
  //
  ASSERT (RxLen <= Dev->RxRing.Desc[DescIdx + 1].Len);

  OrigBufferSize = *BufferSize;
  *BufferSize = RxLen;

  if (OrigBufferSize < RxLen) {
    Status = EFI_BUFFER_TOO_SMALL;
    goto Exit; // keep the packet
  }

  if (RxLen < Dev->Snm.MediaHeaderSize) {
    Status = EFI_DEVICE_ERROR;
    goto RecycleDesc; // drop useless short packet
  }

  if (HeaderSize != NULL) {
    *HeaderSize = Dev->Snm.MediaHeaderSize;
  }

  RxBufOffset = (UINTN)(Dev->RxRing.Desc[DescIdx + 1].Addr -
                        Dev->RxBufDeviceBase);
  RxPtr = Dev->RxBuf + RxBufOffset;
  CopyMem (Buffer, RxPtr, RxLen);

  if (DestAddr != NULL) {
    CopyMem (DestAddr, RxPtr, SIZE_OF_VNET (Mac));
  }
  RxPtr += SIZE_OF_VNET (Mac);

  if (SrcAddr != NULL) {
    CopyMem (SrcAddr, RxPtr, SIZE_OF_VNET (Mac));
  }
  RxPtr += SIZE_OF_VNET (Mac);

  if (Protocol != NULL) {
    *Protocol = (UINT16) ((RxPtr[0] << 8) | RxPtr[1]);
  }
  RxPtr += sizeof (UINT16);

  Status = EFI_SUCCESS;

RecycleDesc:
  ++Dev->RxLastUsed;

  //
  // virtio-0.9.5, 2.4.1 Supplying Buffers to The Device
  //
  AvailIdx = *Dev->RxRing.Avail.Idx;
  Dev->RxRing.Avail.Ring[AvailIdx++ % Dev->RxRing.QueueSize] =
    (UINT16) DescIdx;

  MemoryFence ();
  *Dev->RxRing.Avail.Idx = AvailIdx;

  MemoryFence ();
  NotifyStatus = Dev->VirtIo->SetQueueNotify (Dev->VirtIo, VIRTIO_NET_Q_RX);
  if (!EFI_ERROR (Status)) { // earlier error takes precedence
    Status = NotifyStatus;
  }

Exit:
  gBS->RestoreTPL (OldTpl);
  return Status;
}