/** @file

  Copyright (c) 2020 Jared McNeill. All rights reserved.
  Copyright (c) 2020 Andrey Warkentin <andrey.warkentin@gmail.com>

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

#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/DmaLib.h>
#include <Library/IoLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include "BcmGenetDxe.h"

#define GENET_PHY_RETRY     1000

STATIC CONST
EFI_PHYSICAL_ADDRESS   mDmaAddressLimit = FixedPcdGet64 (PcdDmaDeviceLimit) -
                                          FixedPcdGet64 (PcdDmaDeviceOffset);

/**
  Read a memory-mapped device CSR.

  @param  Genet[in]   Pointer to GENERIC_PHY_PRIVATE_DATA instance.
  @param  Offset[in]  Register offset.

  @retval Value

**/
STATIC
UINT32
GenetMmioRead (
  IN GENET_PRIVATE_DATA *Genet,
  IN UINT32             Offset
  )
{
  ASSERT ((Offset & 3) == 0);

  return MmioRead32 (Genet->RegBase + Offset);
}

/**
  Write a memory-mapped device CSR.

  @param  Genet[in]   Pointer to GENERIC_PHY_PRIVATE_DATA instance.
  @param  Offset[in]  Register offset.
  @param  Data[in]    Data to write.

  @retval Value

**/
STATIC
VOID
GenetMmioWrite (
  IN GENET_PRIVATE_DATA *Genet,
  IN UINT32             Offset,
  IN UINT32             Data
  )
{
  ASSERT ((Offset & 3) == 0);

  MemoryFence ();
  MmioWrite32 (Genet->RegBase + Offset, Data);
}

/**
  Perform a GENET PHY register read.

  @param  Priv[in]     Pointer to GENET_PRIVATE_DATA.
  @param  PhyAddr[in]  PHY address.
  @param  Reg[in]      PHY register.
  @param  Data[out]    Pointer to register data read.

  @retval EFI_SUCCESS       Data read successfully.
  @retval EFI_DEVICE_ERROR  Failed to read data.

**/
EFI_STATUS
EFIAPI
GenetPhyRead (
  IN  VOID   *Priv,
  IN  UINT8  PhyAddr,
  IN  UINT8  Reg,
  OUT UINT16 *Data
  )
{
  GENET_PRIVATE_DATA   *Genet;
  UINTN                Retry;
  UINT32               Value;

  Genet = Priv;
  Value = GENET_MDIO_READ |
          GENET_MDIO_START_BUSY |
          SHIFTIN (PhyAddr, GENET_MDIO_PMD) |
          SHIFTIN (Reg, GENET_MDIO_REG);
  GenetMmioWrite (Genet, GENET_MDIO_CMD, Value);

  for (Retry = GENET_PHY_RETRY; Retry > 0; Retry--) {
    Value = GenetMmioRead (Genet, GENET_MDIO_CMD);
    if ((Value & GENET_MDIO_START_BUSY) == 0) {
      *Data = Value & 0xffff;
      break;
    }
    gBS->Stall (10);
  }

  if (Retry == 0) {
    DEBUG ((DEBUG_ERROR,
      "%a: Timeout reading PhyAddr %d, Reg %d\n", __FUNCTION__, PhyAddr, Reg));
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Perform a GENET PHY register write.

  @param  Priv[in]     Pointer to GENET_PRIVATE_DATA.
  @param  PhyAddr[in]  PHY address.
  @param  Reg[in]      PHY register.
  @param  Data[in]     Pointer to register data to write.

  @retval EFI_SUCCESS       Data written successfully.
  @retval EFI_DEVICE_ERROR  Failed to write data.

**/
EFI_STATUS
EFIAPI
GenetPhyWrite (
  IN VOID   *Priv,
  IN UINT8  PhyAddr,
  IN UINT8  Reg,
  IN UINT16 Data
  )
{
  GENET_PRIVATE_DATA    *Genet;
  UINTN                 Retry;
  UINT32                Value;

  Genet = Priv;
  Value = GENET_MDIO_WRITE |
          GENET_MDIO_START_BUSY |
          SHIFTIN (PhyAddr, GENET_MDIO_PMD) |
          SHIFTIN (Reg, GENET_MDIO_REG);
  GenetMmioWrite (Genet, GENET_MDIO_CMD, Value | Data);

  for (Retry = GENET_PHY_RETRY; Retry > 0; Retry--) {
    Value = GenetMmioRead (Genet, GENET_MDIO_CMD);
    if ((Value & GENET_MDIO_START_BUSY) == 0) {
      break;
    }
    gBS->Stall (10);
  }

  if (Retry == 0) {
    DEBUG ((DEBUG_ERROR,
      "%a: Timeout writing PhyAddr %d, Reg %d\n", __FUNCTION__, PhyAddr, Reg));
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Process a PHY link speed change (e.g. with MAC layer).

  @param  Priv[in]    Pointer to GENET_PRIVATE_DATA.
  @param  Speed[in]   Speed setting.
  @param  Duplex[in]  Duplex setting.

**/
VOID
EFIAPI
GenetPhyConfigure (
  IN VOID               *Priv,
  IN GENERIC_PHY_SPEED  Speed,
  IN GENERIC_PHY_DUPLEX Duplex
  )
{
  GENET_PRIVATE_DATA  *Genet;
  UINT32              Value;

  Genet = Priv;
  Value = GenetMmioRead (Genet, GENET_EXT_RGMII_OOB_CTRL);
  Value &= ~GENET_EXT_RGMII_OOB_OOB_DISABLE;
  Value |= GENET_EXT_RGMII_OOB_RGMII_LINK;
  Value |= GENET_EXT_RGMII_OOB_RGMII_MODE_EN;
  if (Genet->PhyMode == GENET_PHY_MODE_RGMII) {
    Value |= GENET_EXT_RGMII_OOB_ID_MODE_DISABLE;
  } else {
    Value &= ~GENET_EXT_RGMII_OOB_ID_MODE_DISABLE;
  }
  GenetMmioWrite (Genet, GENET_EXT_RGMII_OOB_CTRL, Value);

  Value = GenetMmioRead (Genet, GENET_UMAC_CMD);
  Value &= ~GENET_UMAC_CMD_SPEED;
  switch (Speed) {
    case PHY_SPEED_1000:
      Value |= SHIFTIN (GENET_UMAC_CMD_SPEED_1000, GENET_UMAC_CMD_SPEED);
      break;
    case PHY_SPEED_100:
      Value |= SHIFTIN (GENET_UMAC_CMD_SPEED_100, GENET_UMAC_CMD_SPEED);
      break;
    default:
      Value |= SHIFTIN (GENET_UMAC_CMD_SPEED_10, GENET_UMAC_CMD_SPEED);
      break;
  }
  if (Duplex == PHY_DUPLEX_FULL) {
    Value &= ~GENET_UMAC_CMD_HD_EN;
  } else {
    Value |= GENET_UMAC_CMD_HD_EN;
  }
  GenetMmioWrite (Genet, GENET_UMAC_CMD, Value);
}

/**
  Extra action to run after a PHY reset. This adds the appropriate clock
  delay based on the PHY mode.

  @param  Priv[in]  Pointer to GENET_PRIVATE_DATA.

**/
EFI_STATUS
EFIAPI
GenetPhyResetAction (
  IN VOID             *Priv
  )
{
  GENET_PRIVATE_DATA  *Genet;
  UINT16              Value;
  EFI_STATUS          Status;

  Genet = Priv;
  Status = GenetPhyWrite (Priv, Genet->Phy.PhyAddr, BRGPHY_MII_AUXCTL,
             BRGPHY_AUXCTL_SHADOW_MISC |
             (BRGPHY_AUXCTL_SHADOW_MISC << BRGPHY_AUXCTL_MISC_READ_SHIFT));
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = GenetPhyRead (Priv, Genet->Phy.PhyAddr, BRGPHY_MII_AUXCTL, &Value);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Value &= BRGPHY_AUXCTL_MISC_DATA_MASK;

  if (Genet->PhyMode == GENET_PHY_MODE_RGMII_RXID ||
      Genet->PhyMode == GENET_PHY_MODE_RGMII_ID) {
    Value |= BRGPHY_AUXCTL_MISC_RGMII_SKEW_EN;
  } else {
    Value &= ~BRGPHY_AUXCTL_MISC_RGMII_SKEW_EN;
  }

  Status = GenetPhyWrite (Priv, Genet->Phy.PhyAddr, BRGPHY_MII_AUXCTL,
             BRGPHY_AUXCTL_MISC_WRITE_EN | BRGPHY_AUXCTL_SHADOW_MISC | Value);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = GenetPhyWrite (Priv, Genet->Phy.PhyAddr, BRGPHY_MII_SHADOW_1C,
                          BRGPHY_SHADOW_1C_CLK_CTRL);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = GenetPhyRead (Priv, Genet->Phy.PhyAddr, BRGPHY_MII_SHADOW_1C, &Value);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Value &= BRGPHY_SHADOW_1C_DATA_MASK;

  if (Genet->PhyMode == GENET_PHY_MODE_RGMII_TXID ||
      Genet->PhyMode == GENET_PHY_MODE_RGMII_ID) {
    Value |= BRGPHY_SHADOW_1C_GTXCLK_EN;
  } else {
    Value &= ~BRGPHY_SHADOW_1C_GTXCLK_EN;
  }

  Status = GenetPhyWrite (Priv, Genet->Phy.PhyAddr, BRGPHY_MII_SHADOW_1C,
             BRGPHY_SHADOW_1C_WRITE_EN | BRGPHY_SHADOW_1C_CLK_CTRL | Value);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

/**
  Reset GENET.

  @param  Genet[in]  Pointer to GENET_PRIVATE_DATA.

**/
VOID
GenetReset (
  IN GENET_PRIVATE_DATA   *Genet
  )
{
  UINT32  Value;

  Value = GenetMmioRead (Genet, GENET_SYS_RBUF_FLUSH_CTRL);
  Value |= GENET_SYS_RBUF_FLUSH_RESET;
  GenetMmioWrite (Genet, GENET_SYS_RBUF_FLUSH_CTRL, Value);
  gBS->Stall (10);

  Value &= ~GENET_SYS_RBUF_FLUSH_RESET;
  GenetMmioWrite (Genet, GENET_SYS_RBUF_FLUSH_CTRL, Value);
  gBS->Stall (10);

  GenetMmioWrite (Genet, GENET_SYS_RBUF_FLUSH_CTRL, 0);
  gBS->Stall (10);

  GenetMmioWrite (Genet, GENET_UMAC_CMD, 0);
  GenetMmioWrite (Genet, GENET_UMAC_CMD,
    GENET_UMAC_CMD_LCL_LOOP_EN | GENET_UMAC_CMD_SW_RESET);
  gBS->Stall (10);
  GenetMmioWrite (Genet, GENET_UMAC_CMD, 0);

  GenetMmioWrite (Genet, GENET_UMAC_MIB_CTRL,
    GENET_UMAC_MIB_RESET_RUNT | GENET_UMAC_MIB_RESET_RX | GENET_UMAC_MIB_RESET_TX);
  GenetMmioWrite (Genet, GENET_UMAC_MIB_CTRL, 0);

  GenetMmioWrite (Genet, GENET_UMAC_MAX_FRAME_LEN, GENET_MAX_PACKET_SIZE);

  Value = GenetMmioRead (Genet, GENET_RBUF_CTRL);
  Value |= GENET_RBUF_ALIGN_2B;
  GenetMmioWrite (Genet, GENET_RBUF_CTRL, Value);

  GenetMmioWrite (Genet, GENET_RBUF_TBUF_SIZE_CTRL, 1);
}

/**
  Set the station address.

  @param  Genet[in]    Pointer to GENET_PRIVATE_DATA.
  @param  MacAddr[in]  MAC address to set.

**/
VOID
EFIAPI
GenetSetMacAddress (
  IN GENET_PRIVATE_DATA   *Genet,
  IN EFI_MAC_ADDRESS      *MacAddr
  )
{
  UINT32  Value;

  Value = MacAddr->Addr[3] |
          MacAddr->Addr[2] << 8 |
          MacAddr->Addr[1] << 16 |
          MacAddr->Addr[0] << 24;
  GenetMmioWrite (Genet, GENET_UMAC_MAC0, Value);
  Value = MacAddr->Addr[5] |
          MacAddr->Addr[4] << 8;
  GenetMmioWrite (Genet, GENET_UMAC_MAC1, Value);
}

/**
  Set a PHY mode.

  @param  Genet[in]    Pointer to GENET_PRIVATE_DATA.
  @param  PhyMode[in]  Mode to set.

**/
VOID
GenetSetPhyMode (
  IN GENET_PRIVATE_DATA   *Genet,
  IN GENET_PHY_MODE       PhyMode
  )
{
  UINT32  Value;

  switch (PhyMode) {
    case GENET_PHY_MODE_RGMII:
    case GENET_PHY_MODE_RGMII_RXID:
    case GENET_PHY_MODE_RGMII_TXID:
    case GENET_PHY_MODE_RGMII_ID:
      Value = GENET_SYS_PORT_MODE_EXT_GPHY;
      break;
    default:
      Value = 0;
      break;
  }
  GenetMmioWrite (Genet, GENET_SYS_PORT_CTRL, Value);
}

/**
  Enable TX/RX.

  @param  Genet[in]  Pointer to GENET_PRIVATE_DATA.

**/
VOID
GenetEnableTxRx (
  IN GENET_PRIVATE_DATA   *Genet
  )
{
  UINT32 Value;

  // Start TX DMA on default queue
  Value = GenetMmioRead (Genet, GENET_TX_DMA_CTRL);
  Value |= GENET_TX_DMA_CTRL_EN |
           GENET_TX_DMA_CTRL_RBUF_EN (GENET_DMA_DEFAULT_QUEUE);
  GenetMmioWrite (Genet, GENET_TX_DMA_CTRL, Value);

  // Start RX DMA on default queue
  Value = GenetMmioRead (Genet, GENET_RX_DMA_CTRL);
  Value |= GENET_RX_DMA_CTRL_EN |
           GENET_RX_DMA_CTRL_RBUF_EN (GENET_DMA_DEFAULT_QUEUE);
  GenetMmioWrite (Genet, GENET_RX_DMA_CTRL, Value);

  // Enable transmitter and receiver
  Value = GenetMmioRead (Genet, GENET_UMAC_CMD);
  Value |= GENET_UMAC_CMD_TXEN | GENET_UMAC_CMD_RXEN;
  GenetMmioWrite (Genet, GENET_UMAC_CMD, Value);

  // Enable interrupts
  GenetMmioWrite (Genet, GENET_INTRL2_CPU_CLEAR_MASK,
    GENET_IRQ_TXDMA_DONE | GENET_IRQ_RXDMA_DONE);
}

/**
  Disable TX/RX.

  @param  Genet[in]  Pointer to GENET_PRIVATE_DATA.

**/
VOID
GenetDisableTxRx (
  IN GENET_PRIVATE_DATA   *Genet
  )
{
  UINT32  Value;

  // Disable interrupts
  GenetMmioWrite (Genet, GENET_INTRL2_CPU_SET_MASK, 0xFFFFFFFF);
  GenetMmioWrite (Genet, GENET_INTRL2_CPU_CLEAR, 0xFFFFFFFF);

  // Disable receiver
  Value = GenetMmioRead (Genet, GENET_UMAC_CMD);
  Value &= ~GENET_UMAC_CMD_RXEN;
  GenetMmioWrite (Genet, GENET_UMAC_CMD, Value);

  // Stop RX DMA
  Value = GenetMmioRead (Genet, GENET_RX_DMA_CTRL);
  Value &= ~GENET_RX_DMA_CTRL_EN;
  Value &= ~GENET_RX_DMA_CTRL_RBUF_EN (GENET_DMA_DEFAULT_QUEUE);
  GenetMmioWrite (Genet, GENET_RX_DMA_CTRL, Value);

  // Stop TX DMA
  Value = GenetMmioRead (Genet, GENET_TX_DMA_CTRL);
  Value &= ~GENET_TX_DMA_CTRL_EN;
  Value &= ~GENET_TX_DMA_CTRL_RBUF_EN (GENET_DMA_DEFAULT_QUEUE);
  GenetMmioWrite (Genet, GENET_TX_DMA_CTRL, Value);

  // Flush data in the TX FIFO
  GenetMmioWrite (Genet, GENET_UMAC_TX_FLUSH, 1);
  gBS->Stall (10);
  GenetMmioWrite (Genet, GENET_UMAC_TX_FLUSH, 0);

  // Disable transmitter
  Value = GenetMmioRead (Genet, GENET_UMAC_CMD);
  Value &= ~GENET_UMAC_CMD_TXEN;
  GenetMmioWrite (Genet, GENET_UMAC_CMD, Value);
}

/**
  Change promiscuous mode state.

  @param  Genet[in]   Pointer to GENET_PRIVATE_DATA.
  @param  Enable[in]  Promiscuous mode state.

**/
VOID
GenetSetPromisc (
  IN GENET_PRIVATE_DATA   *Genet,
  IN BOOLEAN              Enable
  )
{
  UINT32 Value;

  Value = GenetMmioRead (Genet, GENET_UMAC_CMD);
  if (Enable) {
    Value |= GENET_UMAC_CMD_PROMISC;
  } else {
    Value &= ~GENET_UMAC_CMD_PROMISC;
  }
  GenetMmioWrite (Genet, GENET_UMAC_CMD, Value);
}

/**
  Enable the MAC filter for the Ethernet broadcast address

  @param  Genet[in]   Pointer to GENET_PRIVATE_DATA.
  @param  Enable[in]  Promiscuous mode state.

**/
VOID
GenetEnableBroadcastFilter (
  IN GENET_PRIVATE_DATA   *Genet,
  IN BOOLEAN              Enable
  )
{
  CONST EFI_MAC_ADDRESS   *MacAddr;
  UINT32                  Value;

  if (Enable) {
    MacAddr = &Genet->SnpMode.CurrentAddress;

    GenetMmioWrite (Genet, GENET_UMAC_MDF_ADDR0 (0),
      MacAddr->Addr[1] | MacAddr->Addr[0] << 8);
    GenetMmioWrite (Genet, GENET_UMAC_MDF_ADDR1 (0),
      MacAddr->Addr[5] | MacAddr->Addr[4] << 8 |
      MacAddr->Addr[3] << 16 | MacAddr->Addr[2] << 24);

    GenetMmioWrite (Genet, GENET_UMAC_MDF_ADDR0 (1), 0xffff);
    GenetMmioWrite (Genet, GENET_UMAC_MDF_ADDR1 (1), 0xffffffff);

    Value = BIT16 | BIT15; // enable filters 0 and 1
  } else {
    Value = 0;
  }
  GenetMmioWrite (Genet, GENET_UMAC_MDF_CTRL, Value);
}

/**
  Configure DMA TX and RX queues, enabling them.

  @param  Genet[in]  Pointer to GENET_PRIVATE_DATA.

**/
VOID
GenetDmaInitRings (
  IN GENET_PRIVATE_DATA *Genet
  )
{
  UINT8 Qid;

  Qid = GENET_DMA_DEFAULT_QUEUE;

  Genet->TxQueued = 0;
  Genet->TxNext = 0;
  Genet->TxConsIndex = 0;
  Genet->TxProdIndex = 0;

  Genet->RxConsIndex = 0;
  Genet->RxProdIndex = 0;

  // Configure TX queue
  GenetMmioWrite (Genet, GENET_TX_SCB_BURST_SIZE, 0x08);
  GenetMmioWrite (Genet, GENET_TX_DMA_READ_PTR_LO (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_READ_PTR_HI (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_CONS_INDEX (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_PROD_INDEX (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_RING_BUF_SIZE (Qid),
    SHIFTIN (GENET_DMA_DESC_COUNT, GENET_TX_DMA_RING_BUF_SIZE_DESC_COUNT) |
    SHIFTIN (GENET_MAX_PACKET_SIZE, GENET_TX_DMA_RING_BUF_SIZE_BUF_LENGTH));
  GenetMmioWrite (Genet, GENET_TX_DMA_START_ADDR_LO (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_START_ADDR_HI (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_END_ADDR_LO (Qid),
    GENET_DMA_DESC_COUNT * GENET_DMA_DESC_SIZE / 4 - 1);
  GenetMmioWrite (Genet, GENET_TX_DMA_END_ADDR_HI (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_MBUF_DONE_THRES (Qid), 1);
  GenetMmioWrite (Genet, GENET_TX_DMA_FLOW_PERIOD (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_WRITE_PTR_LO (Qid), 0);
  GenetMmioWrite (Genet, GENET_TX_DMA_WRITE_PTR_HI (Qid), 0);

  // Enable TX queue
  GenetMmioWrite (Genet, GENET_TX_DMA_RING_CFG, (1U << Qid));

  // Configure RX queue
  GenetMmioWrite (Genet, GENET_RX_SCB_BURST_SIZE, 0x08);
  GenetMmioWrite (Genet, GENET_RX_DMA_WRITE_PTR_LO (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_WRITE_PTR_HI (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_PROD_INDEX (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_CONS_INDEX (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_RING_BUF_SIZE (Qid),
    SHIFTIN (GENET_DMA_DESC_COUNT, GENET_RX_DMA_RING_BUF_SIZE_DESC_COUNT) |
    SHIFTIN (GENET_MAX_PACKET_SIZE, GENET_RX_DMA_RING_BUF_SIZE_BUF_LENGTH));
  GenetMmioWrite (Genet, GENET_RX_DMA_START_ADDR_LO (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_START_ADDR_HI (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_END_ADDR_LO (Qid),
    GENET_DMA_DESC_COUNT * GENET_DMA_DESC_SIZE / 4 - 1);
  GenetMmioWrite (Genet, GENET_RX_DMA_END_ADDR_HI (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_XON_XOFF_THRES (Qid),
    SHIFTIN (5, GENET_RX_DMA_XON_XOFF_THRES_LO) |
    SHIFTIN (GENET_DMA_DESC_COUNT >> 4, GENET_RX_DMA_XON_XOFF_THRES_HI));
  GenetMmioWrite (Genet, GENET_RX_DMA_READ_PTR_LO (Qid), 0);
  GenetMmioWrite (Genet, GENET_RX_DMA_READ_PTR_HI (Qid), 0);

  // Enable RX queue
  GenetMmioWrite (Genet, GENET_RX_DMA_RING_CFG, (1U << Qid));
}

/**
  Allocate DMA buffers for RX.

  @param  Genet[in]  Pointer to GENET_PRIVATE_DATA.

  @retval EFI_SUCCESS           DMA buffers allocated.
  @retval EFI_OUT_OF_RESOURCES  DMA buffers could not be allocated.
**/
EFI_STATUS
GenetDmaAlloc (
  IN GENET_PRIVATE_DATA   *Genet
  )
{
  EFI_STATUS              Status;

  Genet->RxBuffer = mDmaAddressLimit;
  Status = gBS->AllocatePages (AllocateMaxAddress, EfiBootServicesData,
                  EFI_SIZE_TO_PAGES (GENET_MAX_PACKET_SIZE * GENET_DMA_DESC_COUNT),
                  &Genet->RxBuffer);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
      "%a: Failed to allocate RX buffer: %r\n", __FUNCTION__, Status));
  }
  return Status;
}

/**
  Given an RX buffer descriptor index, program the IO address of the buffer into the hardware.

  @param  Genet[in]      Pointer to GENET_PRIVATE_DATA.
  @param  DescIndex[in]  Index of RX buffer descriptor.

  @retval EFI_SUCCESS  DMA buffers allocated.
  @retval Others       Programmatic errors, as buffers come from DmaAllocateBuffer, and thus
                       cannot fail DmaMap (for the expected NonCoherentDmaLib).
**/
EFI_STATUS
GenetDmaMapRxDescriptor (
  IN GENET_PRIVATE_DATA * Genet,
  IN UINT8                DescIndex
  )
{
  EFI_STATUS    Status;
  UINTN         DmaNumberOfBytes;

  ASSERT (Genet->RxBufferMap[DescIndex].Mapping == NULL);
  ASSERT (Genet->RxBuffer != 0);

  DmaNumberOfBytes = GENET_MAX_PACKET_SIZE;
  Status = DmaMap (MapOperationBusMasterWrite,
             GENET_RX_BUFFER (Genet, DescIndex),
             &DmaNumberOfBytes,
             &Genet->RxBufferMap[DescIndex].PhysAddress,
             &Genet->RxBufferMap[DescIndex].Mapping);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Failed to map RX buffer: %r\n",
      __FUNCTION__, Status));
    return Status;
  }

  GenetMmioWrite (Genet, GENET_RX_DESC_ADDRESS_LO (DescIndex),
    Genet->RxBufferMap[DescIndex].PhysAddress & 0xFFFFFFFF);
  GenetMmioWrite (Genet, GENET_RX_DESC_ADDRESS_HI (DescIndex),
    (Genet->RxBufferMap[DescIndex].PhysAddress >> 32) & 0xFFFFFFFF);
  GenetMmioWrite (Genet, GENET_RX_DESC_STATUS (DescIndex), 0);

  return EFI_SUCCESS;
}

/**
  Given an RX buffer descriptor index, undo the DmaMap operation on the buffer.

  @param  Genet[in]      Pointer to GENET_PRIVATE_DATA.
  @param  DescIndex[in]  Index of RX buffer descriptor.

**/
VOID
GenetDmaUnmapRxDescriptor (
  IN GENET_PRIVATE_DATA * Genet,
  IN UINT8                DescIndex
  )
{
  if (Genet->RxBufferMap[DescIndex].Mapping != NULL) {
    DmaUnmap (Genet->RxBufferMap[DescIndex].Mapping);
    Genet->RxBufferMap[DescIndex].Mapping = NULL;
  }
}

/**
  Free DMA buffers for RX, undoing GenetDmaAlloc.

  @param  Genet[in]      Pointer to GENET_PRIVATE_DATA.
  @param  DescIndex[in]  Index of RX buffer descriptor.

**/
VOID
GenetDmaFree (
  IN GENET_PRIVATE_DATA *Genet
  )
{
  UINTN Idx;

  for (Idx = 0; Idx < GENET_DMA_DESC_COUNT; Idx++) {
    GenetDmaUnmapRxDescriptor (Genet, Idx);
  }
  gBS->FreePages (Genet->RxBuffer,
         EFI_SIZE_TO_PAGES (GENET_MAX_PACKET_SIZE * GENET_DMA_DESC_COUNT));
}

/**
  Queue TX transmission, given a buffer to transmit and a TX descriptor index.

  @param  Genet[in]          Pointer to GENET_PRIVATE_DATA.
  @param  DescIndex[in]      TX descriptor index.
  @param  PhysAddr[in]       Buffer to transmit.
  @param  NumberOfBytes[in]  Buffer length.

**/
VOID
GenetDmaTriggerTx (
  IN GENET_PRIVATE_DATA * Genet,
  IN UINT8                DescIndex,
  IN EFI_PHYSICAL_ADDRESS PhysAddr,
  IN UINTN                NumberOfBytes
  )
{
  UINT32    DescStatus;

  DescStatus = GENET_TX_DESC_STATUS_SOP |
               GENET_TX_DESC_STATUS_EOP |
               GENET_TX_DESC_STATUS_CRC |
               GENET_TX_DESC_STATUS_QTAG |
               SHIFTIN (NumberOfBytes, GENET_TX_DESC_STATUS_BUFLEN);

  GenetMmioWrite (Genet, GENET_TX_DESC_ADDRESS_LO (DescIndex),
    PhysAddr & 0xFFFFFFFF);
  GenetMmioWrite (Genet, GENET_TX_DESC_ADDRESS_HI (DescIndex),
    (PhysAddr >> 32) & 0xFFFFFFFF);
  GenetMmioWrite (Genet, GENET_TX_DESC_STATUS (DescIndex), DescStatus);

  GenetMmioWrite (Genet, GENET_TX_DMA_PROD_INDEX (GENET_DMA_DEFAULT_QUEUE),
    Genet->TxProdIndex);
}

/**
  Simulate a "TX interrupt", return the next (completed) TX buffer to recycle.

  @param  Genet[in]   Pointer to GENET_PRIVATE_DATA.
  @param  TxBuf[out]  Location to store pointer to next TX buffer to recycle.

**/
VOID
GenetTxIntr (
  IN  GENET_PRIVATE_DATA *Genet,
  OUT VOID               **TxBuf
  )
{
  UINT32 Total;

  Total = GenetTxPending (Genet);
  if (Genet->TxQueued > 0 && Total > 0) {
    DmaUnmap (Genet->TxBufferMap[Genet->TxNext]);
    *TxBuf = Genet->TxBuffer[Genet->TxNext];
    Genet->TxQueued--;
    Genet->TxNext = (Genet->TxNext + 1) % GENET_DMA_DESC_COUNT;
    Genet->TxConsIndex = (Genet->TxConsIndex + 1) & 0xFFFF;
  } else {
    *TxBuf = NULL;
  }
}

UINT32
GenetRxPending (
  IN  GENET_PRIVATE_DATA *Genet
  )
{
  UINT32 ProdIndex;
  UINT32 ConsIndex;

  ConsIndex = GenetMmioRead (Genet,
                GENET_RX_DMA_CONS_INDEX (GENET_DMA_DEFAULT_QUEUE)) & 0xFFFF;
  ASSERT (ConsIndex == Genet->RxConsIndex);

  ProdIndex = GenetMmioRead (Genet,
                GENET_RX_DMA_PROD_INDEX (GENET_DMA_DEFAULT_QUEUE)) & 0xFFFF;
  return (ProdIndex - Genet->RxConsIndex) & 0xFFFF;
}

UINT32
GenetTxPending (
  IN  GENET_PRIVATE_DATA *Genet
  )
{
  UINT32 ConsIndex;

  ConsIndex = GenetMmioRead (Genet,
                GENET_TX_DMA_CONS_INDEX (GENET_DMA_DEFAULT_QUEUE)) & 0xFFFF;

  return (ConsIndex - Genet->TxConsIndex) & 0xFFFF;
}

VOID
GenetRxComplete (
  IN GENET_PRIVATE_DATA *Genet
  )
{
  Genet->RxConsIndex = (Genet->RxConsIndex + 1) & 0xFFFF;
  GenetMmioWrite (Genet, GENET_RX_DMA_CONS_INDEX (GENET_DMA_DEFAULT_QUEUE),
                  Genet->RxConsIndex);
}

/**
  Simulate an "RX interrupt", returning the index of a completed RX buffer and
  corresponding frame length.

  @param  Genet[in]         Pointer to GENET_PRIVATE_DATA.
  @param  DescIndex[out]    Location to store completed RX buffer index.
  @param  FrameLength[out]  Location to store frame length.

  @retval EFI_SUCCESS    Data received.
  @retval EFI_NOT_READY  No RX buffers ready as no data received.

**/
EFI_STATUS
GenetRxIntr (
  IN  GENET_PRIVATE_DATA *Genet,
  OUT UINT8              *DescIndex,
  OUT UINTN              *FrameLength
  )
{
  EFI_STATUS    Status;
  UINT32        Total;
  UINT32        DescStatus;

  Total = GenetRxPending (Genet);
  if (Total > 0) {
    *DescIndex = Genet->RxConsIndex % GENET_DMA_DESC_COUNT;
    DescStatus = GenetMmioRead (Genet, GENET_RX_DESC_STATUS (*DescIndex));
    *FrameLength = SHIFTOUT (DescStatus, GENET_RX_DESC_STATUS_BUFLEN);
    Status = EFI_SUCCESS;
  } else {
    Status = EFI_NOT_READY;
  }

  return Status;
}
