Newer
Older
AMI-Aptio-BIOS-Reversed / MnpDxe / MnpConfig.c
@Ajax Dong Ajax Dong 2 days ago 36 KB Init
/**
 * @file MnpConfig.c
 *
 * @brief MNP Configuration Helpers
 *
 * Implements the internal configuration logic:
 *   - MnpInitializeDeviceData / MnpReleaseDeviceData
 *   - MnpCreateServiceData / MnpDestroyServiceData
 *   - MnpInitializeInstanceData / MnpCleanInstance
 *   - MnpConfigureInstance (per-instance configure/reset)
 *   - MnpConfigReceiveFilters (SNP receive filters)
 *   - MnpStart / MnpStop (underlying service-level start/stop)
 *   - MnpGroupOp (multicast group add/remove)
 *   - MnpReadVlanConfiguration
 *
 * Source: AmiNetworkPkg/UefiNetworkStack/Common/MnpDxe/MnpConfig.c
 */

#include "MnpDxe.h"

extern EFI_BOOT_SERVICES  *gBS;
extern EFI_HANDLE          gImageHandle;

/**
 * Free NET_BUF queue signature template.
 */
STATIC CONST NET_BUF_QUEUE  gFreeNbufQueueTemplate = {
  NET_BUF_QUEUE_SIGNATURE,
  0,
  { &gFreeNbufQueueTemplate.Buffers.Flink,
    &gFreeNbufQueueTemplate.Buffers.Blink }
};

/**
 * Default MNP config data template.
 */
STATIC CONST EFI_MNP_CONFIG_DATA  gMnpDefaultConfigData = { 0 };

/**
 * MNP instance flags: initial zero state.
 */
STATIC CONST MNP_INSTANCE_FLAGS   gMnpInstanceConfigTemplate = { 0 };

/* ====================================================================== */
/*  MnpInitializeDeviceData                                              */
/* ====================================================================== */

/**
 * Initialize the per-NIC MNP_DEVICE_DATA structure.
 *
 * Opens the SNP protocol, retrieves the MAC address string, initializes
 * the free NET_BUF queue, allocates receive buffers, and creates timer
 * events for polling, timeout check, media detection, and TX timeout.
 *
 * @param[out] MnpDeviceData   Device data to initialize.
 * @param[in]  ImageHandle     The driver image handle.
 * @param[in]  ControllerHandle  The NIC controller handle.
 *
 * @retval EFI_SUCCESS            Initialized successfully.
 * @retval EFI_UNSUPPORTED        SNP protocol not found.
 * @retval EFI_OUT_OF_RESOURCES   Memory allocation failed.
 */
EFI_STATUS
MnpInitializeDeviceData (
  OUT MNP_DEVICE_DATA  *MnpDeviceData,
  IN  EFI_HANDLE        ImageHandle,
  IN  EFI_HANDLE        ControllerHandle
  )
{
  EFI_SIMPLE_NETWORK       *Snp;
  EFI_SIMPLE_NETWORK_MODE  *SnpMode;
  EFI_STATUS                Status;

  ASSERT (MnpDeviceData != NULL);

  //
  // Initialize header fields.
  //
  MnpDeviceData->Signature                  = MNP_DEVICE_DATA_SIGNATURE;
  MnpDeviceData->ImageHandle                = ImageHandle;
  MnpDeviceData->ControllerHandle           = ControllerHandle;

  //
  // Open SNP protocol exclusively.
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gMnpSnpProtocolGuid,
                  (VOID **)&Snp,
                  ImageHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE
                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  MnpDeviceData->Snp = Snp;
  SnpMode             = Snp->Mode;

  //
  // Compute buffer and header sizes from SNP mode.
  //
  MnpDeviceData->BufferLength = SnpMode->MediaHeaderSize +
                                SnpMode->Snip.HeaderSize +
                                SnpMode->Snip.DataSize;
  MnpDeviceData->HeaderLength = ALIGN_VALUE (SnpMode->MediaHeaderSize, 4) +
                                sizeof (UINT32);

  //
  // Get MAC address string.
  //
  Status = GetMacAddressString (
             ControllerHandle,
             &MnpDeviceData->MacString
             );
  if (EFI_ERROR (Status)) {
    goto ErrorCloseSnp;
  }

  //
  // Initialize service list and group address list.
  //
  InitializeListHead (&MnpDeviceData->ServiceList);
  InitializeListHead (&MnpDeviceData->GroupAddressList);

  //
  // Initialize free NET_BUF queue.
  //
  CopyMem (&MnpDeviceData->FreeNbufQueue,
           &gFreeNbufQueueTemplate,
           sizeof (NET_BUF_QUEUE));
  InitializeListHead (&MnpDeviceData->FreeNbufQueue.Buffers);

  MnpDeviceData->FreeNbufCount = 0;
  MnpDeviceData->TotalFreeNbufs = 0;

  //
  // Allocate initial free NET_BUFs.
  //
  Status = MnpAddFreeNbuf (MnpDeviceData, MNP_DEFAULT_NBUF_COUNT);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpInitializeDeviceData: MnpAddFreeNbuf failed, %r.\n",
            Status));
    goto ErrorFreeMacString;
  }

  //
  // Allocate the receive packet buffer.
  //
  MnpDeviceData->RcvPacket = NetbufAlloc (MnpDeviceData->BufferLength);
  if (MnpDeviceData->RcvPacket == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrorFreeMacString;
  }
  NetbufTrim (MnpDeviceData->RcvPacket, MnpDeviceData->HeaderLength, 0);

  //
  // Allocate the receive buffer pool.
  //
  MnpDeviceData->RcvBufPool = AllocatePool (MnpDeviceData->BufferLength);
  if (MnpDeviceData->RcvBufPool == NULL) {
    DEBUG ((DEBUG_ERROR,
            "MnpInitializeDeviceData: AllocatePool failed.\n"));
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrorFreeRcvPacket;
  }

  //
  // Create timer events.
  //
  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  MnpPollTimer,
                  MnpDeviceData,
                  &MnpDeviceData->PollTimerEvent
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpInitializeDeviceData: CreateEvent for poll timer failed.\n"));
    goto ErrorFreePool;
  }

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  MnpCheckPacketTimeout,
                  MnpDeviceData,
                  &MnpDeviceData->TimeoutCheckEvent
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpInitializeDeviceData: CreateEvent for packet timeout "
            "check failed.\n"));
    goto ErrorCloseTimer1;
  }

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  MnpMediaDetect,
                  MnpDeviceData,
                  &MnpDeviceData->MediaDetectEvent
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpInitializeDeviceData: CreateEvent for media detection "
            "failed.\n"));
    goto ErrorCloseTimer2;
  }

  Status = gBS->CreateEvent (
                  EVT_TIMER,
                  TPL_CALLBACK,
                  NULL,
                  NULL,
                  &MnpDeviceData->TxTimeoutEvent
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpInitializeDeviceData: CreateEvent for tx timeout event "
            "failed.\n"));
    goto ErrorCloseTimer3;
  }

  return EFI_SUCCESS;

ErrorCloseTimer3:
  gBS->CloseEvent (MnpDeviceData->MediaDetectEvent);
ErrorCloseTimer2:
  gBS->CloseEvent (MnpDeviceData->TimeoutCheckEvent);
ErrorCloseTimer1:
  gBS->CloseEvent (MnpDeviceData->PollTimerEvent);
ErrorFreePool:
  if (MnpDeviceData->RcvBufPool != NULL) {
    FreePool (MnpDeviceData->RcvBufPool);
  }
ErrorFreeRcvPacket:
  if (MnpDeviceData->RcvPacket != NULL) {
    NetbufFree (MnpDeviceData->RcvPacket);
  }
ErrorFreeMacString:
  if (MnpDeviceData->MacString != NULL) {
    FreePool (MnpDeviceData->MacString);
  }
  gBS->CloseProtocol (
         ControllerHandle,
         &gMnpSnpProtocolGuid,
         ImageHandle,
         ControllerHandle
         );

ErrorCloseSnp:
  return Status;
}

/* ====================================================================== */
/*  MnpReleaseDeviceData                                                 */
/* ====================================================================== */

/**
 * Release resources associated with a MNP_DEVICE_DATA.
 *
 * @param[in] MnpDeviceData  Device data to release.
 * @param[in] ImageHandle    Driver image handle for closing SNP.
 */
VOID
MnpReleaseDeviceData (
  IN MNP_DEVICE_DATA  *MnpDeviceData,
  IN EFI_HANDLE        ImageHandle
  )
{
  ASSERT (MnpDeviceData->Signature == MNP_DEVICE_DATA_SIGNATURE);
  ASSERT (IsListEmpty (&MnpDeviceData->GroupAddressList));

  //
  // Close all timer events.
  //
  if (MnpDeviceData->TxTimeoutEvent != NULL) {
    gBS->CloseEvent (MnpDeviceData->TxTimeoutEvent);
  }
  if (MnpDeviceData->TimeoutCheckEvent != NULL) {
    gBS->CloseEvent (MnpDeviceData->TimeoutCheckEvent);
  }
  if (MnpDeviceData->MediaDetectEvent != NULL) {
    gBS->CloseEvent (MnpDeviceData->MediaDetectEvent);
  }
  if (MnpDeviceData->PollTimerEvent != NULL) {
    gBS->CloseEvent (MnpDeviceData->PollTimerEvent);
  }

  //
  // Free receive buffer pool.
  //
  if (MnpDeviceData->RcvBufPool != NULL) {
    FreePool (MnpDeviceData->RcvBufPool);
  }

  //
  // Return the receive packet.
  //
  if (MnpDeviceData->RcvPacket != NULL) {
    NetbufFree (MnpDeviceData->RcvPacket);
  }

  //
  // Update free queue counter.
  //
  MnpDeviceData->NbufAllocated -= MnpDeviceData->FreeNbufCount;

  //
  // Flush the free NET_BUF queue.
  //
  NetbufQueFlush (&MnpDeviceData->FreeNbufQueue);

  //
  // Close SNP protocol.
  //
  gBS->CloseProtocol (
         MnpDeviceData->ControllerHandle,
         &gMnpSnpProtocolGuid,
         ImageHandle,
         MnpDeviceData->ControllerHandle
         );

  //
  // Free MAC string.
  //
  if (MnpDeviceData->MacString != NULL) {
    FreePool (MnpDeviceData->MacString);
  }
}

/* ====================================================================== */
/*  MnpAddFreeNbuf                                                       */
/* ====================================================================== */

/**
 * Allocate additional free NET_BUFs and add them to the free queue.
 *
 * @param[in] MnpDeviceData  Device data.
 * @param[in] Count          Number of NET_BUFs to allocate.
 *
 * @retval EFI_SUCCESS           Allocated.
 * @retval EFI_OUT_OF_RESOURCES  Allocation failed.
 */
EFI_STATUS
MnpAddFreeNbuf (
  IN MNP_DEVICE_DATA  *MnpDeviceData,
  IN UINTN             Count
  )
{
  UINTN  Index;

  for (Index = 0; Index < Count; Index++) {
    NET_BUF  *Nbuf;

    Nbuf = NetbufAlloc (MnpDeviceData->BufferLength +
                        MnpDeviceData->HeaderLength);
    if (Nbuf == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    NetbufTrim (Nbuf, MnpDeviceData->HeaderLength, 0);
    NetbufQueAppend (&MnpDeviceData->FreeNbufQueue, Nbuf);
    MnpDeviceData->FreeNbufCount++;
    MnpDeviceData->NbufAllocated++;

    MnpDeviceData->TotalFreeNbufs++;
  }

  return EFI_SUCCESS;
}

/* ====================================================================== */
/*  MnpAllocNbuf (from free queue)                                       */
/* ====================================================================== */

/**
 * Allocate a NET_BUF from the free queue.
 *
 * @param[in] MnpDeviceData  Device data.
 *
 * @return NET_BUF pointer, or NULL if queue is empty.
 */
NET_BUF *
MnpAllocNbuf (
  IN MNP_DEVICE_DATA  *MnpDeviceData
  )
{
  NET_BUF  *Nbuf;

  if (IsListEmpty (&MnpDeviceData->FreeNbufQueue.Buffers)) {
    if (MnpAddFreeNbuf (MnpDeviceData, 1) != EFI_SUCCESS) {
      return NULL;
    }
  }

  Nbuf = (NET_BUF *)GetFirstNode (&MnpDeviceData->FreeNbufQueue.Buffers);
  RemoveEntryList (&Nbuf->Link);
  MnpDeviceData->FreeNbufCount--;

  return Nbuf;
}

/* ====================================================================== */
/*  MnpFreeNbuf (return to free queue)                                   */
/* ====================================================================== */

/**
 * Return a NET_BUF to the free queue.
 *
 * @param[in] MnpDeviceData  Device data.
 * @param[in] Nbuf           Buffer to return.
 */
VOID
MnpFreeNbuf (
  IN MNP_DEVICE_DATA  *MnpDeviceData,
  IN NET_BUF          *Nbuf
  )
{
  if (Nbuf->RefCount > 2) {
    //
    // Buffer is still referenced; keep it.
    //
    InsertTailList (&MnpDeviceData->FreeNbufQueue.Buffers, &Nbuf->Link);
    MnpDeviceData->FreeNbufCount++;
  }
}

/* ====================================================================== */
/*  MnpCreateServiceData                                                 */
/* ====================================================================== */

/**
 * Create a new MNP_SERVICE_DATA for a given VLAN ID.
 *
 * Allocates the service data, initializes the MNP protocol interface
 * (partial copy from template), creates a child handle if VLAN is used,
 * and installs the device path protocol on the child.
 *
 * @param[in] MnpDeviceData  Parent device data.
 * @param[in] VlanId         VLAN ID (0 = default/no VLAN).
 * @param[in] Priority       VLAN priority (0-7).
 *
 * @return Pointer to MNP_SERVICE_DATA, or NULL on failure.
 */
MNP_SERVICE_DATA *
MnpCreateServiceData (
  IN MNP_DEVICE_DATA  *MnpDeviceData,
  IN UINT16            VlanId,
  IN UINT8             Priority
  )
{
  MNP_SERVICE_DATA  *Service;
  EFI_SIMPLE_NETWORK_MODE  *SnpMode;
  EFI_STATUS         Status;

  Service = AllocateZeroPool (sizeof (MNP_SERVICE_DATA));
  if (Service == NULL) {
    DEBUG ((DEBUG_ERROR,
            "MnpCreateServiceData: Faild to allocate memory for the new "
            "Mnp Service Data.\n"));
    return NULL;
  }

  //
  // Insert into the device's service list.
  //
  InsertTailList (&MnpDeviceData->ServiceList, &Service->Link);

  Service->Signature     = MNP_SERVICE_DATA_SIGNATURE;
  Service->MnpDeviceData = MnpDeviceData;

  //
  // Copy the MNP protocol template (partial: GetModeData + Configure).
  //
  CopyMem (&Service->Protocol, &mMnpProtocolTemplate,
           sizeof (EFI_MANAGED_NETWORK_PROTOCOL));

  InitializeListHead (&Service->InstanceList);

  SnpMode              = MnpDeviceData->Snp->Mode;
  Service->MtuSize     = SnpMode->MediaHeaderSize;

  if (VlanId != 0) {
    //
    // Create a child handle for VLAN.
    //
    Status = MnpCreateVlanChildHandle (
               MnpDeviceData->ImageHandle,
               MnpDeviceData->ControllerHandle,
               VlanId,
               &Service->ControllerHandle
               );
    if (Status != EFI_SUCCESS) {
      DEBUG ((DEBUG_ERROR,
              "MnpCreateServiceData: Faild to create child handle.\n"));
      MnpDestroyServiceData (Service);
      return NULL;
    }

    //
    // Open the driver binding protocol on the child.
    //
    Status = gBS->OpenProtocol (
                    MnpDeviceData->ControllerHandle,
                    &gMnpDriverBindingGuid,
                    (VOID **)&DevicePathInterface,
                    MnpDeviceData->ImageHandle,
                    Service->ControllerHandle,
                    EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                    );
    if (EFI_ERROR (Status)) {
      MnpDestroyServiceData (Service);
      return NULL;
    }

    Service->MtuSize = (UINT32)(SnpMode->MediaHeaderSize - 4);
  } else {
    Service->ControllerHandle = MnpDeviceData->ControllerHandle;
  }

  Service->VlanId    = VlanId;
  Service->Priority  = Priority;
  Service->Handle    = Service->ControllerHandle;

  //
  // Install device path protocol on the service handle.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Service->Handle,
                  &gMnpDevicePathGuid,
                  Service + 1,  /* Device path after service struct */
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    MnpDestroyServiceData (Service);
    return NULL;
  }

  return Service;
}

/* ====================================================================== */
/*  MnpDestroyServiceData                                                */
/* ====================================================================== */

/**
 * Destroy a MNP_SERVICE_DATA and all its child instances.
 *
 * @param[in] Service  Service data to destroy.
 *
 * @retval EFI_SUCCESS  Destroyed successfully.
 */
EFI_STATUS
MnpDestroyServiceData (
  IN MNP_SERVICE_DATA  *Service
  )
{
  MNP_DEVICE_DATA  *MnpDeviceData;
  LIST_ENTRY       *Entry;
  LIST_ENTRY       *NextEntry;

  ASSERT (Service->Signature == MNP_SERVICE_DATA_SIGNATURE);

  MnpDeviceData = Service->MnpDeviceData;

  //
  // Destroy all instances.
  //
  for (Entry = Service->InstanceList.Flink;
       Entry != &Service->InstanceList;
       Entry = NextEntry) {
    MNP_INSTANCE_DATA  *Instance;

    NextEntry = Entry->Flink;
    Instance = MNP_INSTANCE_DATA_FROM_LINK (Entry);
    MnpCleanInstance (Instance);
    RemoveEntryList (Entry);
    Service->InstanceCount--;
    FreePool (Instance);
  }

  //
  // Remove from service list.
  //
  RemoveEntryList (&Service->Link);

  //
  // Uninstall device path protocol.
  //
  gBS->UninstallMultipleProtocolInterfaces (
         Service->Handle,
         &gMnpDevicePathGuid,
         Service + 1,
         NULL
         );

  //
  // Close the child controller protocol if this was a VLAN service.
  //
  if (Service->VlanId != 0) {
    gBS->CloseProtocol (
           MnpDeviceData->ControllerHandle,
           &gMnpDriverBindingGuid,
           MnpDeviceData->ImageHandle,
           Service->ControllerHandle
           );
  }

  FreePool (Service);
  return EFI_SUCCESS;
}

/* ====================================================================== */
/*  MnpInitializeInstanceData                                            */
/* ====================================================================== */

/**
 * Initialize an MNP_INSTANCE_DATA.
 *
 * @param[in] Service   Parent service data.
 * @param[in] Instance  Instance data to initialize.
 */
VOID
MnpInitializeInstanceData (
  IN MNP_SERVICE_DATA   *Service,
  IN MNP_INSTANCE_DATA  *Instance
  )
{
  ASSERT (Service->Signature    == MNP_SERVICE_DATA_SIGNATURE);
  ASSERT (Instance != NULL);

  Instance->Signature     = MNP_INSTANCE_DATA_SIGNATURE;
  Instance->MnpServiceData = Service;

  //
  // Copy the full MNP protocol interface.
  //
  CopyMem (&Instance->Protocol, &mMnpProtocolTemplate,
           sizeof (EFI_MANAGED_NETWORK_PROTOCOL));

  //
  // Copy the default config data.
  //
  CopyMem (&Instance->ConfigData, &gMnpDefaultConfigData,
           sizeof (EFI_MNP_CONFIG_DATA));

  //
  // Initialize lists.
  //
  InitializeListHead (&Instance->GroupCtrlBlkList);
  InitializeListHead (&Instance->TxDataList);
  InitializeListHead (&Instance->RcvdPacketQueue);

  //
  // Initialize NET_MAP for receive tokens.
  //
  InitializeListHead (&Instance->RcvMap.Free);
  InitializeListHead (&Instance->RcvMap.Used);
  Instance->RcvMap.ItemCount = 0;
}

/* ====================================================================== */
/*  MnpCleanInstance                                                     */
/* ====================================================================== */

/**
 * Clean instance state: flush config, reset filters, flush queues.
 *
 * @param[in] Instance  Instance to clean.
 */
VOID
MnpCleanInstance (
  IN MNP_INSTANCE_DATA  *Instance
  )
{
  MNP_DEVICE_DATA  *Device;
  MNP_CONFIG_DATA  *Config;

  ASSERT (Instance->Signature == MNP_INSTANCE_DATA_SIGNATURE);

  Device = Instance->MnpServiceData->MnpDeviceData;

  //
  // Decrement filter counts on the device.
  //
  if (Instance->EnableUnicastReceive)  Device->UnicastFilterCnt--;
  if (Instance->EnableMulticastReceive) Device->MulticastFilterCnt--;
  if (Instance->EnablePromiscuousReceive) Device->PromiscuousFilterCnt--;
  if (Instance->FlushQueue)               Device->FlushQueueCnt--;

  //
  // Apply config from template.
  //
  Config = &Instance->ConfigData;

  if (Config->EnableUnicastReceive) {
    Device->UnicastFilterCnt++;
  }
  if (Config->EnableMulticastReceive) {
    Device->MulticastFilterCnt++;
  }
  if (Config->EnablePromiscuousReceive) {
    Device->PromiscuousFilterCnt++;
  }
  if (Config->FlushQueue) {
    Device->FlushQueueCnt++;
  }

  Instance->Flags = 0;
}

/* ====================================================================== */
/*  MnpConfigureInstance                                                 */
/* ====================================================================== */

/**
 * Configure or reset an MNP instance.
 *
 * Updates the device's filter counters, flushes group addresses on reset,
 * copies the configuration data, and calls MnpStart or MnpStop as
 * appropriate.
 *
 * @param[in] Instance       Instance to configure.
 * @param[in] MnpConfigData  Configuration data, or NULL to reset.
 *
 * @retval EFI_SUCCESS           Configured/reset.
 * @retval EFI_ALREADY_STARTED   Already configured with non-NULL data.
 */
EFI_STATUS
MnpConfigureInstance (
  IN MNP_INSTANCE_DATA    *Instance,
  IN EFI_MNP_CONFIG_DATA  *MnpConfigData  OPTIONAL
  )
{
  MNP_DEVICE_DATA  *Device;
  BOOLEAN           IsFirstStart;
  MNP_CONFIG_DATA  *ApplyConfig;

  ASSERT (Instance->Signature        == MNP_INSTANCE_DATA_SIGNATURE);
  ASSERT (Instance->MnpServiceData   != NULL);
  ASSERT (Instance->MnpServiceData->Signature == MNP_SERVICE_DATA_SIGNATURE);
  ASSERT (MnpConfigData == NULL || !MnpConfigData->Reserved);

  if (MnpConfigData != NULL && MnpConfigData->Reserved != 0) {
    return EFI_ALREADY_STARTED;
  }

  Device = Instance->MnpServiceData->MnpDeviceData;
  ASSERT (Device->Signature == MNP_DEVICE_DATA_SIGNATURE);

  IsFirstStart = !Instance->IsConfigured && (MnpConfigData != NULL);

  //
  // Decrement old filter counters.
  //
  if (Instance->EnableUnicastReceive)   Device->UnicastFilterCnt--;
  if (Instance->EnableMulticastReceive) Device->MulticastFilterCnt--;
  if (Instance->EnablePromiscuousReceive) Device->PromiscuousFilterCnt--;
  if (Instance->FlushQueue)             Device->FlushQueueCnt--;

  //
  // Apply new configuration.
  //
  if (Instance->EnableRxReclaim) {
    MnpCleanInstance (Instance);
  }

  if (MnpConfigData == NULL) {
    //
    // Full reset: call stop on the service.
    //
    goto DoneApply;
  }

  ApplyConfig = MnpConfigData;

  //
  // Increment new filter counters.
  //
  if (ApplyConfig->EnableUnicastReceive) {
    Device->UnicastFilterCnt++;
    Instance->Flags |= MNP_INSTANCE_FLAG_UNICAST;
  }
  if (ApplyConfig->EnableMulticastReceive) {
    Device->MulticastFilterCnt++;
  }
  if (ApplyConfig->EnablePromiscuousReceive) {
    Device->PromiscuousFilterCnt++;
    Instance->Flags |= MNP_INSTANCE_FLAG_PROMISCUOUS;
  }
  if (ApplyConfig->FlushQueue) {
    Device->FlushQueueCnt++;
  }

  //
  // Flush all group addresses if MC receive is now disabled.
  //
  if (!ApplyConfig->EnableMulticastReceive) {
    MnpGroupOp (Instance, FALSE, NULL);
  }

DoneApply:
  //
  // Copy the config data to the instance.
  //
  CopyMem (&Instance->ConfigData, ApplyConfig, sizeof (EFI_MNP_CONFIG_DATA));
  Instance->IsConfigured = (MnpConfigData != NULL) ? TRUE : FALSE;

  if (MnpConfigData != NULL) {
    return MnpStart (Instance->MnpServiceData,
                     IsFirstStart,
                     MnpConfigData->EnablePolling);
  } else {
    return MnpStop (Instance->MnpServiceData);
  }
}

/* ====================================================================== */
/*  MnpStart                                                             */
/* ====================================================================== */

/**
 * Start the underlying SNP adapter. Only the first config call actually
 * starts the hardware; subsequent calls just update polling timers.
 *
 * @param[in] Service          Service data.
 * @param[in] IsFirstStart     TRUE if this is the first instance starting.
 * @param[in] EnablePolling    TRUE to enable polling timer.
 *
 * @retval EFI_SUCCESS   Started successfully.
 */
EFI_STATUS
MnpStart (
  IN MNP_SERVICE_DATA  *Service,
  IN BOOLEAN            IsFirstStart,
  IN BOOLEAN            EnablePolling
  )
{
  MNP_DEVICE_DATA  *Device;
  EFI_SIMPLE_NETWORK  *Snp;
  EFI_STATUS           Status;

  ASSERT (Service->Signature == MNP_SERVICE_DATA_SIGNATURE);

  Device = Service->MnpDeviceData;
  Snp    = Device->Snp;

  if (!IsFirstStart) {
    //
    // Not the first child; skip hardware initialization.
    //
    goto SetPolling;
  }

  if (Snp == NULL) {
    ASSERT (Snp != NULL);
    return EFI_DEVICE_ERROR;
  }

  //
  // Start and initialize the SNP adapter.
  //
  Status = Snp->Start (Snp);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "MnpStart: MnpStartSnp failed, %r.\n", Status));
    return Status;
  }

  Status = Snp->Initialize (Snp, 0, 0);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "MnpStart: MnpStartSnp failed, %r.\n", Status));
    return Status;
  }

  //
  // Set timer for timeout check (500ms).
  //
  Status = gBS->SetTimer (
                  Device->TimeoutCheckEvent,
                  TimerPeriodic,
                  500 * 10000   /* 500ms in 100ns units */
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpStart, gBS->SetTimer for TimeoutCheckTimer %r.\n",
            Status));
    return Status;
  }

  //
  // Set timer for media detection (5s).
  //
  Status = gBS->SetTimer (
                  Device->MediaDetectEvent,
                  TimerPeriodic,
                  5000 * 10000  /* 5s in 100ns units */
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpStart, gBS->SetTimer for MediaDetectTimer %r.\n",
            Status));
    return Status;
  }

SetPolling:
  //
  // Update polling timer if changed.
  //
  if (Device->PollingEnabled != EnablePolling) {
    Status = gBS->SetTimer (
                    Device->PollTimerEvent,
                    EnablePolling ? TimerPeriodic : TimerCancel,
                    100000       /* 10ms in 100ns units */
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR,
              "MnpStart: gBS->SetTimer for PollTimer failed, %r.\n",
              Status));
      return Status;
    }
    Device->PollingEnabled = EnablePolling;
  }

  //
  // Reconfigure SNP receive filters to match current config.
  //
  return MnpConfigReceiveFilters (Device);
}

/* ====================================================================== */
/*  MnpStop                                                              */
/* ====================================================================== */

/**
 * Stop the underlying SNP adapter. Only the last instance actually stops
 * the hardware.
 *
 * @param[in] Service  Service data.
 *
 * @retval EFI_SUCCESS  Stopped successfully.
 */
EFI_STATUS
MnpStop (
  IN MNP_SERVICE_DATA  *Service
  )
{
  MNP_DEVICE_DATA    *Device;
  EFI_SIMPLE_NETWORK  *Snp;
  EFI_STATUS           Status;

  ASSERT (Service->Signature == MNP_SERVICE_DATA_SIGNATURE);

  Device = Service->MnpDeviceData;
  ASSERT (Device->ConfiguredChildrenNumber > 0);

  //
  // Reconfigure filters.
  //
  MnpConfigReceiveFilters (Device);

  if (--Device->ConfiguredChildrenNumber != 0) {
    //
    // Still other children active.
    //
    return EFI_SUCCESS;
  }

  //
  // Stop polling timer.
  //
  if (Device->PollingEnabled) {
    gBS->SetTimer (Device->PollTimerEvent, TimerCancel, 0);
    Device->PollingEnabled = FALSE;
  }

  //
  // Cancel timeout and media detect timers.
  //
  gBS->SetTimer (Device->TimeoutCheckEvent, TimerCancel, 0);
  gBS->SetTimer (Device->MediaDetectEvent,  TimerCancel, 0);

  //
  // Reset and stop SNP.
  //
  Snp = Device->Snp;
  ASSERT (Snp != NULL);

  Status = Snp->Reset (Snp);
  if (!EFI_ERROR (Status)) {
    Status = Snp->Shutdown (Snp);
  }

  return Status;
}

/* ====================================================================== */
/*  MnpConfigReceiveFilters                                              */
/* ====================================================================== */

/**
 * Configure the SNP receive filters based on the current device state.
 *
 * @param[in] MnpDeviceData  Device data.
 *
 * @retval EFI_SUCCESS           Filters configured.
 * @retval EFI_OUT_OF_RESOURCES  Memory allocation for MC filter list failed.
 */
EFI_STATUS
MnpConfigReceiveFilters (
  IN MNP_DEVICE_DATA  *MnpDeviceData
  )
{
  EFI_SIMPLE_NETWORK          *Snp;
  EFI_SIMPLE_NETWORK_MODE     *SnpMode;
  UINT32                       FilterSetting;
  UINT32                       OldFilterSetting;
  UINT32                       McastFilterCnt;
  EFI_MAC_ADDRESS             *McastFilter;
  BOOLEAN                      ResetMCastFilters;
  LIST_ENTRY                  *Entry;
  UINTN                        Index;
  EFI_STATUS                   Status;

  ASSERT (MnpDeviceData->Signature == MNP_DEVICE_DATA_SIGNATURE);

  Snp     = MnpDeviceData->Snp;
  SnpMode = Snp->Mode;

  ResetMCastFilters = TRUE;

  //
  // Build the filter setting:
  //   bit 0 = unicast
  //   bit 1 = multicast (if MC filter count > hw limit, use promiscuous)
  //   bit 2 = promiscuous
  //
  FilterSetting = (MnpDeviceData->UnicastFilterCnt    != 0) ? 1u : 0u;
  if (MnpDeviceData->PromiscuousFilterCnt != 0) {
    FilterSetting |= 4u;
  }
  if (MnpDeviceData->MulticastFilterCnt != 0) {
    UINT32  MaxMcast = SnpMode->MaxMCastFilterCount;

    if (MnpDeviceData->GroupMCastFilterCnt != 0) {
      //
      // Check if we can use perfect filtering.
      //
      if (MnpDeviceData->MCastFilterCnt <= MaxMcast) {
        FilterSetting |= 2u;   /* perfect filter */
        ResetMCastFilters = FALSE;
        McastFilterCnt = MnpDeviceData->MCastFilterCnt;

        McastFilter = AllocatePool (sizeof (EFI_MAC_ADDRESS) *
                                    McastFilterCnt);
        if (McastFilter == NULL) {
          DEBUG ((DEBUG_ERROR,
                  "MnpConfigReceiveFilters: Failed to allocate memory "
                  "resource for MCastFilter.\n"));
          return EFI_OUT_OF_RESOURCES;
        }

        //
        // Copy multicast addresses from the device list.
        //
        Index = 0;
        for (Entry = MnpDeviceData->GroupAddressList.Flink;
             Entry != &MnpDeviceData->GroupAddressList;
             Entry = Entry->Flink) {
          MNP_GROUP_ADDRESS  *Group;

          Group = MNP_GROUP_ADDRESS_FROM_LINK (Entry);
          CopyMem (&McastFilter[Index], &Group->Address,
                   sizeof (EFI_MAC_ADDRESS));
          Index++;
          ASSERT (Index <= McastFilterCnt);
        }
      } else {
        if (SnpMode->MediaType & MEDIA_TYPE_MULTICAST_PROMISCUOUS) {
          FilterSetting |= 0x10u;
        } else {
          FilterSetting |= 8u;   /* disable multicast entirely */
        }
      }
    } else {
      FilterSetting |= 8u;   /* no group addresses to receive */
    }
  }

  if (MnpDeviceData->PromiscuousFilterCnt == 0) {
    FilterSetting |= 8u;   /* disable promiscuous */
  }

  //
  // Call SNP to set the receive filters.
  //
  Status = Snp->ReceiveFilters (
                  Snp,
                  FilterSetting,
                  FilterSetting ^ (SnpMode->ReceiveFilterSetting &
                                   ~EFI_SIMPLE_NETWORK_RECEIVE_UNICAST &
                                   ~EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST &
                                   ~EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS &
                                   ~EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST),
                  ResetMCastFilters,
                  McastFilterCnt,
                  McastFilter
                  );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
            "MnpConfigReceiveFilters: Snp->ReceiveFilters failed, %r.\n",
            Status));
  }

  if (McastFilter != NULL) {
    FreePool (McastFilter);
  }

  return Status;
}

/* ====================================================================== */
/*  MnpGroupOp                                                           */
/* ====================================================================== */

/**
 * Add or remove a multicast group address for an instance.
 *
 * @param[in] Instance     The MNP instance.
 * @param[in] Flag         TRUE = add, FALSE = remove.
 * @param[in] MacAddress   MAC address to add/remove (NULL = flush all).
 *
 * @retval EFI_SUCCESS           Operation completed.
 * @retval EFI_ALREADY_STARTED   Group already exists (on add).
 * @retval EFI_NOT_FOUND         Group not found (on remove).
 */
EFI_STATUS
MnpGroupOp (
  IN MNP_INSTANCE_DATA  *Instance,
  IN BOOLEAN             Flag,
  IN EFI_MAC_ADDRESS    *MacAddress   OPTIONAL
  )
{
  MNP_DEVICE_DATA  *Device;
  MNP_GROUP_ADDRESS *Group;
  LIST_ENTRY       *Entry;
  EFI_STATUS        Status;

  Device = Instance->MnpServiceData->MnpDeviceData;

  if (MacAddress != NULL) {
    //
    // Search the device's group list.
    //
    for (Entry = Device->GroupAddressList.Flink;
         Entry != &Device->GroupAddressList;
         Entry = Entry->Flink) {
      Group = MNP_GROUP_ADDRESS_FROM_LINK (Entry);

      if (!CompareMem (MacAddress, &Group->Address,
                       Device->Snp->Mode->HwAddressSize)) {
        //
        // Found matching address.
        //
        if (Flag) {
          // Already present.
          return EFI_ALREADY_STARTED;
        }
        //
        // Remove group.
        //
        RemoveEntryList (&Group->InstanceLink);
        if (IsListEmpty (&Group->InstanceLinkList)) {
          //
          // No more instances reference this group; remove from device.
          //
          RemoveEntryList (&Group->Link);
          Device->GroupMCastFilterCnt--;
        }
        FreePool (Group);

        Status = MnpConfigReceiveFilters (Device);
        if (EFI_ERROR (Status)) {
          return Status;
        }

        return EFI_SUCCESS;
      }
    }

    if (!Flag) {
      return EFI_NOT_FOUND;
    }

    //
    // Add new group.
    //
    Group = AllocateZeroPool (sizeof (MNP_GROUP_ADDRESS));
    if (Group == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    CopyMem (&Group->Address, MacAddress, Device->Snp->Mode->HwAddressSize);
    InitializeListHead (&Group->InstanceLinkList);
    InsertTailList (&Device->GroupAddressList, &Group->Link);
    InsertTailList (&Group->InstanceLinkList, &Group->InstanceLink);
    Device->GroupMCastFilterCnt++;

    Status = MnpConfigReceiveFilters (Device);
    if (EFI_ERROR (Status)) {
      //
      // Rollback.
      //
      RemoveEntryList (&Group->Link);
      Device->GroupMCastFilterCnt--;
      FreePool (Group);
      return Status;
    }

    return EFI_SUCCESS;
  }

  //
  // MacAddress == NULL: flush all groups for this instance.
  //
  return MnpGroupOpFlush (Instance);
}

/* ====================================================================== */
/*  MnpGroupOpFlush                                                      */
/* ====================================================================== */

/**
 * Flush all group addresses for the given instance.
 *
 * @param[in] Instance  The MNP instance.
 *
 * @retval EFI_SUCCESS  All groups flushed.
 */
EFI_STATUS
MnpGroupOpFlush (
  IN MNP_INSTANCE_DATA  *Instance
  )
{
  MNP_DEVICE_DATA  *Device;

  Device = Instance->MnpServiceData->MnpDeviceData;

  //
  // Iterate all groups and remove those belonging to this instance.
  //
  LIST_ENTRY  *Entry;
  LIST_ENTRY  *NextEntry;

  for (Entry = Device->GroupAddressList.Flink;
       Entry != &Device->GroupAddressList;
       Entry = NextEntry) {
    MNP_GROUP_ADDRESS  *Group;

    NextEntry = Entry->Flink;
    Group = MNP_GROUP_ADDRESS_FROM_LINK (Entry);

    RemoveEntryList (&Group->InstanceLink);
    if (IsListEmpty (&Group->InstanceLinkList)) {
      RemoveEntryList (&Group->Link);
      Device->GroupMCastFilterCnt--;
      FreePool (Group);
    }
  }

  return MnpConfigReceiveFilters (Device);
}

/* ====================================================================== */
/*  MnpReadVlanConfiguration                                            */
/* ====================================================================== */

/**
 * Read VLAN configuration from the UEFI variable "NetworkStackVar".
 *
 * @param[in]  MnpDeviceData    Device data.
 * @param[out] NumberOfVlan     Number of VLAN entries.
 * @param[out] VlanVariable     Buffer of VLAN entries (UINT16 per entry).
 *
 * @retval EFI_SUCCESS           Read successfully.
 * @retval EFI_NOT_FOUND         Variable does not exist.
 */
EFI_STATUS
MnpReadVlanConfiguration (
  IN  MNP_DEVICE_DATA  *MnpDeviceData,
  OUT UINTN            *NumberOfVlan,
  OUT UINT16           **VlanVariable
  )
{
  CHAR16    VariableName[] = L"NetworkStackVar";
  UINTN     BufferSize;
  UINT16    *Buffer;
  EFI_STATUS  Status;

  BufferSize = 0;
  Buffer     = NULL;

  Status = gRT->GetVariable (
                  VariableName,
                  &gMnpVlanConfigGuid,
                  NULL,
                  &BufferSize,
                  NULL
                  );

  if (Status != EFI_BUFFER_TOO_SMALL) {
    *NumberOfVlan = 0;
    *VlanVariable = NULL;
    return EFI_NOT_FOUND;
  }

  Buffer = AllocatePool (BufferSize);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = gRT->GetVariable (
                  VariableName,
                  &gMnpVlanConfigGuid,
                  NULL,
                  &BufferSize,
                  Buffer
                  );
  if (EFI_ERROR (Status)) {
    FreePool (Buffer);
    *NumberOfVlan = 0;
    *VlanVariable = NULL;
    return Status;
  }

  *NumberOfVlan = BufferSize / sizeof (UINT16);
  *VlanVariable = Buffer;

  return EFI_SUCCESS;
}