【UEFI基础】EDK网络框架(SNP)

发布时间:2024年01月04日

SNP

SNP代码综述

SNP全称是Simple Network Protocol,它是EDK代码中能够控制的最底层的网络接口。该模块的作用有以下的几个部分:

  1. 网卡操作,比如初始化网卡,打开/关闭网口等。

  2. 提供数据的底层传输接口,供上层协议使用。

SNP驱动依赖于UNDI驱动,即网卡驱动。该驱动执行后会安装gEfiNetworkInterfaceIdentifierProtocolGuid_31对应的协议。

SNP驱动执行时就会去查找该协议是否存在,如果存在,就会据此构造SNP_DRIVER结构体来表示这张网卡。后续对该网卡的操作都是通过SNP_DRIVER结构体及其中的EFI_SIMPLE_NETWORK_PROTOCOL来完成。

SNP是一个UEFI Driver Model(事实上几乎所有的UEFI网络驱动都是),所以会安装EFI_DRIVER_BINDING_PROTOCOL,代码如下:

EFI_DRIVER_BINDING_PROTOCOL  gSimpleNetworkDriverBinding = {
  SimpleNetworkDriverSupported,
  SimpleNetworkDriverStart,
  SimpleNetworkDriverStop,
  0xa,
  NULL,
  NULL
};

后续将以上述的Supported和Start函数为线索介绍其初始化流程,其对应模块NetworkPkg\SnpDxe\SnpDxe.inf。

SNP在UEFI网络协议栈中的关系图:

支持
提供
支持
支持
提供
gEfiPciIoProtocolGuid
UNDI
gEfiNetworkInterfaceIdentifierProtocolGuid_31
gEfiDevicePathProtocolGuid
SNP
gEfiSimpleNetworkProtocolGuid

SimpleNetworkDriverSupported

Supported函数主要是判断驱动是否需要执行,它主要是寻找依赖的Protocol是否已经安装,SNP的初始化函数(即Start函数)执行依赖于以下的部分:

  1. 是否安装了Device Path Protocol:
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  NULL,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  1. 判断NII是否已经安装,且类型是否支持:
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  (VOID **)&NiiProtocol,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  //
  // check the version, we don't want to connect to the undi16
  //
  if (NiiProtocol->Type != EfiNetworkInterfaceUndi) {
    Status = EFI_UNSUPPORTED;
    goto Done;
  }
  1. 判断!PXE结构体是否正常:
  //
  // Check to see if !PXE structure is valid. Paragraph alignment of !PXE structure is required.
  //
  if ((NiiProtocol->Id & 0x0F) != 0) {
    DEBUG ((DEBUG_NET, "\n!PXE structure is not paragraph aligned.\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;
  }

  Pxe = (PXE_UNDI *)(UINTN)(NiiProtocol->Id);

  //
  //  Verify !PXE revisions.
  //
  if (Pxe->hw.Signature != PXE_ROMID_SIGNATURE) {
    DEBUG ((DEBUG_NET, "\n!PXE signature is not valid.\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;
  }

  if (Pxe->hw.Rev < PXE_ROMID_REV) {
    DEBUG ((DEBUG_NET, "\n!PXE.Rev is not supported.\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;
  }

  if (Pxe->hw.MajorVer < PXE_ROMID_MAJORVER) {
    DEBUG ((DEBUG_NET, "\n!PXE.MajorVer is not supported.\n"));
    Status = EFI_UNSUPPORTED;
    goto Done;
  } else if ((Pxe->hw.MajorVer == PXE_ROMID_MAJORVER) && (Pxe->hw.MinorVer < PXE_ROMID_MINORVER)) {
    DEBUG ((DEBUG_NET, "\n!PXE.MinorVer is not supported."));
    Status = EFI_UNSUPPORTED;
    goto Done;
  }
  //
  // Do S/W UNDI specific checks.
  //
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_HW_UNDI) == 0) {
    if (Pxe->sw.EntryPoint < Pxe->sw.Len) {
      DEBUG ((DEBUG_NET, "\n!PXE S/W entry point is not valid."));
      Status = EFI_UNSUPPORTED;
      goto Done;
    }

    if (Pxe->sw.BusCnt == 0) {
      DEBUG ((DEBUG_NET, "\n!PXE.BusCnt is zero."));
      Status = EFI_UNSUPPORTED;
      goto Done;
    }
  }

实际上SNP还需要依赖于gEfiPciIoProtocolGuid,不过并没有在Supported函数中体现。

SimpleNetworkDriverStart

SNP初始化主要做了以下的是事情:

  1. 获取EFI_PCI_IO_PROTOCOL
  2. 获取EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL
  3. EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL中得到PXE_UNDI结构体,通过检查其Checksum来确定有效性,还会判断其中的Implementation属性。
  4. 使用EFI_PCI_IO_PROTOCOL分配资源:
  // 分配资源给SNP_DRIVER,它是一个全局的结构体,每个网卡都有一个。
  Status = PciIo->AllocateBuffer (
                    PciIo,
                    AllocateAnyPages,
                    EfiBootServicesData,
                    SNP_MEM_PAGES (sizeof (SNP_DRIVER)),
                    &Address,
                    0
                    );
  // 分配内存给CPB和DB,它们是SNP和UNDI交互的缓冲区,初始化完成之后这部分内存会被释放掉。
  Status = PciIo->AllocateBuffer (
                    PciIo,
                    AllocateAnyPages,
                    EfiBootServicesData,
                    SNP_MEM_PAGES (4096),
                    &Address,
                    0
                    );
  1. 初始化SNP_DRIVER结构体,该结构体会在后面进一步说明。

  2. 执行必要的UNDI调用,完成信息获取,对应操作码:

    • PXE_OPCODE_START

    • PXE_OPCODE_GET_INIT_INFO

    • PXE_OPCODE_STATION_ADDRESS

  3. 进一步初始化SNP_DRIVER结构体。

  4. 初始化完毕之后,还会执行UNDI调用,用于暂时关闭网卡(直到后续真正使用时再打开),对应操作码:

    • PXE_OPCODE_SHUTDOWN
    • PXE_OPCODE_STOP

从这里可以看到,在完成SNP的初始化之后,网络设备并没有一直打开:

  //
  // We should not leave UNDI started and initialized here. this DriverStart()
  // routine must only find and attach the SNP interface to UNDI layer that it
  // finds on the given handle!
  // The UNDI layer will be started when upper layers call Snp->start.
  // How ever, this DriverStart() must fill up the snp mode structure which
  // contains the MAC address of the NIC. For this reason we started and
  // initialized UNDI here, now we are done, do a shutdown and stop of the
  // UNDI interface!
  //
  PxeShutdown (Snp);
  PxeStop (Snp);

所以在SNP被初始化完成之后,UEFI的网络收发并不能使用,而开启、关闭和传输的操作依赖于更上层的网络栈驱动代码。

  1. 创建ExitBootServicesEvent事件,也是执行上述的关闭操作,主要是为了在退出UEFI之前关闭网络,避免SNP驱动可能的DMA写坏了OS启动需要的内存空间。

  2. 最后安装gEfiSimpleNetworkProtocolGuid对应的Protocol,供上层接口使用。

对于初始化SNP来说,最重要的有以下几点:

  • SNP_DRIVER结构体初始化。
  • 安装EFI_SIMPLE_NETWORK_PROTOCOL接口。

后面将进一步说明。

SNP_DRIVER

SNP_DRIVER定义在NetworkPkg\SnpDxe\Snp.h文件中,如下所示:

typedef struct {
  UINT32                      Signature;
  EFI_LOCK                    Lock;
 
  EFI_SIMPLE_NETWORK_PROTOCOL Snp;
  EFI_SIMPLE_NETWORK_MODE     Mode;
 
  EFI_HANDLE                  DeviceHandle;
  EFI_DEVICE_PATH_PROTOCOL    *DevicePath;
 
  //
  //  Local instance data needed by SNP driver
  //
  //  Pointer to S/W UNDI API entry point
  //  This will be NULL for H/W UNDI
  //
  ISSUE_UNDI32_COMMAND  IssueUndi32Command;
 
  BOOLEAN               IsSwUndi;
 
  //
  // undi interface number, if one undi manages more nics
  //
  PXE_IFNUM             IfNum;
 
  //
  //  Allocated tx/rx buffer that was passed to UNDI Initialize.
  //
  UINT32                TxRxBufferSize;
  VOID                  *TxRxBuffer;
  //
  // mappable buffers for receive and fill header for undi3.0
  // these will be used if the user buffers are above 4GB limit (instead of
  // mapping the user buffers)
  //
  UINT8                 *ReceiveBufffer;
  VOID                  *ReceiveBufferUnmap;
  UINT8                 *FillHeaderBuffer;
  VOID                  *FillHeaderBufferUnmap;
 
  EFI_PCI_IO_PROTOCOL   *PciIo;
  UINT8                 IoBarIndex;
  UINT8                 MemoryBarIndex;
 
  //
  // Buffers for command descriptor block, command parameter block
  // and data block.
  //
  PXE_CDB               Cdb;
  VOID                  *Cpb;
  VOID                  *CpbUnmap;
  VOID                  *Db;
 
  //
  // UNDI structure, we need to remember the init info for a long time!
  //
  PXE_DB_GET_INIT_INFO  InitInfo;
 
  VOID                  *SnpDriverUnmap;
  //
  // when ever we map an address, we must remember it's address and the un-map
  // cookie so that we can unmap later
  //
  struct MAP_LIST {
    EFI_PHYSICAL_ADDRESS  VirtualAddress;
    VOID                  *MapCookie;
  } MapList[MAX_MAP_LENGTH];
 
  EFI_EVENT              ExitBootServicesEvent;
 
  //
  // Whether UNDI support reporting media status from GET_STATUS command,
  // i.e. PXE_STATFLAGS_GET_STATUS_NO_MEDIA_SUPPORTED or
  //      PXE_STATFLAGS_GET_STATUS_NO_MEDIA_NOT_SUPPORTED
  //
  BOOLEAN                MediaStatusSupported;
 
  //
  // Whether UNDI support cable detect for INITIALIZE command,
  // i.e. PXE_STATFLAGS_CABLE_DETECT_SUPPORTED or
  //      PXE_STATFLAGS_CABLE_DETECT_NOT_SUPPORTED
  //
  BOOLEAN                CableDetectSupported;
 
  //
  // Array of the recycled transmit buffer address from UNDI.
  //
  UINT64                 *RecycledTxBuf;
  //
  // The maximum number of recycled buffer pointers in RecycledTxBuf.
  //
  UINT32                 MaxRecycledTxBuf;
  //
  // Current number of recycled buffer pointers in RecycledTxBuf.
  //
  UINT32                 RecycledTxBufCount;
} SNP_DRIVER;

内容较多,这里只对其中比较重要的成员进行介绍。

  • Lock:是UEFI下的锁,在SNP初始化的Start函数中有锁的初始化:
EfiInitializeLock (&Snp->Lock, TPL_NOTIFY);

这个锁最终也不会在SNP代码中使用,实际上它会传递给UNDI,这主要通过传递回调函数SnpUndi32CallbackBlock()来实现:

VOID
EFIAPI
SnpUndi32CallbackBlock (
  IN UINT64  UniqueId,
  IN UINT32  Enable
  )
{
  SNP_DRIVER  *Snp;

  Snp = (SNP_DRIVER *)(UINTN)UniqueId;
  //
  // tcpip was calling snp at tpl_notify and when we acquire a lock that was
  // created at a lower level (TPL_CALLBACK) it gives an assert!
  //
  if (Enable != 0) {
    EfiAcquireLock (&Snp->Lock);
  } else {
    EfiReleaseLock (&Snp->Lock);
  }
}

// 传递给UDNI
Cpb31->Block = (UINT64)(UINTN)&SnpUndi32CallbackBlock;

UNDI的CPB结构体包含的基础操作中就有Block的操作,用来处理多线程/多核的情况。至于UNDI中具体如何使用,可以在GigUndiDxe找到部分代码,不过我们不关注网卡驱动的实现。

  • Snp:这个就是EFI_SIMPLE_NETWORK_PROTOCOL,后面会详细介绍它的所有成员。

  • Mode:它对应的结构体是EFI_SIMPLE_NETWORK_MODE,后面会进一步介绍。

  • DeviceHandle:每个SNP_DRIVER结构体对应一个设备,所以也就对应到一个Handle上,不过代码中似乎并没有实际用到。

  • DevicePath:同上,每个SNP_DRIVER结构体也对应到一个DevicePath上,也没有用到。

  • IssueUndi32Command:这个是UEFI下的执行函数,通过它可以调用UNDI中的接口,可以有软件和硬件两种形式,不过SNP目前只实现了软件形式的。

  • IsSwUndi:如果是软件形式的UNDI,就设置为TRUE。

  • IfNum:表示一个!PXE结构体控制的网卡数目。

  • TxRxBufferSizeTxRxBuffer:这两个值确定一段内存空间,是UNDI需要使用的。UEFI通过UNDIPXE_OPCODE_GET_INIT_INFO操作获取UNDI的初始化信息(即TxRxBufferSize),再通过这个初始化信息来分配一段内容空间(由TxRxBuffer指定),在SNP中调用PxeInit()函数时,会将TxRxBuffer放到Cpb中传递给UNDI。至于这段内存空间到底是干什么的,从名字上看应该是UNDI收发数据的缓存区。

  • PciIoIoBarIndexMemoryBarIndex:前面已经说过一个SNP_DRIVER对应一个网卡,所以这里的PciIo就是用来访问该设备的接口,通过它也可以确定IoBarIndexMemoryBarIndex,这两个参数指定了网卡在系统中映射的资源空间。

  • CdbCpbCpbUnmapDbCpbUnmap并没有使用,剩下的三个参数是UNDI调用需要的参数。Cdb中放置的数据表示需要进行什么操作,Cpb表示操作需要的参数,Db存放UNDI调用返回的数据。

  • InitInfo:SNP调用PXE_OPCODE_GET_INIT_INFO之后会保留到这个参数中,具体的调用函数如下:

  Snp->Cdb.OpCode     = PXE_OPCODE_GET_INIT_INFO;
  Snp->Cdb.OpFlags    = PXE_OPFLAGS_NOT_USED;
 
  Snp->Cdb.CPBsize    = PXE_CPBSIZE_NOT_USED;
  Snp->Cdb.CPBaddr    = PXE_DBADDR_NOT_USED;
 
  Snp->Cdb.DBsize     = (UINT16) sizeof (Snp->InitInfo);
  Snp->Cdb.DBaddr     = (UINT64)(UINTN) (&Snp->InitInfo);//作为参数传入,UNDI会去填充
 
  Snp->Cdb.StatCode   = PXE_STATCODE_INITIALIZE;
  Snp->Cdb.StatFlags  = PXE_STATFLAGS_INITIALIZE;
 
  Snp->Cdb.IFnum      = Snp->IfNum;
  Snp->Cdb.Control    = PXE_CONTROL_LAST_CDB_IN_LIST;
 
  DEBUG ((EFI_D_NET, "\nSnp->undi.get_init_info()  "));
 
  (*Snp->IssueUndi32Command) ((UINT64)(UINTN) &Snp->Cdb);
  • ExitBootServicesEvent:一个事件,在UEFI调用gBS->ExitBootServices()是调用,该事件最终会关闭网卡。
  • MediaStatusSupported:如果UNDI支持上报网卡的连接信息,则为TRUE。
  • CableDetectSupported:如果UNDI支持测试网线是否连接,则为TRUE。

EFI_SIMPLE_NETWORK_MODE

该结构体位于MdePkg\Include\Protocol\SimpleNetwork.h,其结构体代码如下:

typedef struct {
  ///
  /// Reports the current state of the network interface.
  ///
  UINT32             State;
  ///
  /// The size, in bytes, of the network interface's HW address.
  ///
  UINT32             HwAddressSize;
  ///
  /// The size, in bytes, of the network interface's media header.
  ///
  UINT32             MediaHeaderSize;
  ///
  /// The maximum size, in bytes, of the packets supported by the network interface.
  ///
  UINT32             MaxPacketSize;
  ///
  /// The size, in bytes, of the NVRAM device attached to the network interface.
  ///
  UINT32             NvRamSize;
  ///
  /// The size that must be used for all NVRAM reads and writes. The
  /// start address for NVRAM read and write operations and the total
  /// length of those operations, must be a multiple of this value. The
  /// legal values for this field are 0, 1, 2, 4, and 8.
  ///
  UINT32             NvRamAccessSize;
  ///
  /// The multicast receive filter settings supported by the network interface.
  ///
  UINT32             ReceiveFilterMask;
  ///
  /// The current multicast receive filter settings.
  ///
  UINT32             ReceiveFilterSetting;
  ///
  /// The maximum number of multicast address receive filters supported by the driver.
  ///
  UINT32             MaxMCastFilterCount;
  ///
  /// The current number of multicast address receive filters.
  ///
  UINT32             MCastFilterCount;
  ///
  /// Array containing the addresses of the current multicast address receive filters.
  ///
  EFI_MAC_ADDRESS    MCastFilter[MAX_MCAST_FILTER_CNT];
  ///
  /// The current HW MAC address for the network interface.
  ///
  EFI_MAC_ADDRESS    CurrentAddress;
  ///
  /// The current HW MAC address for broadcast packets.
  ///
  EFI_MAC_ADDRESS    BroadcastAddress;
  ///
  /// The permanent HW MAC address for the network interface.
  ///
  EFI_MAC_ADDRESS    PermanentAddress;
  ///
  /// The interface type of the network interface.
  ///
  UINT8              IfType;
  ///
  /// TRUE if the HW MAC address can be changed.
  ///
  BOOLEAN            MacAddressChangeable;
  ///
  /// TRUE if the network interface can transmit more than one packet at a time.
  ///
  BOOLEAN            MultipleTxSupported;
  ///
  /// TRUE if the presence of media can be determined; otherwise FALSE.
  ///
  BOOLEAN            MediaPresentSupported;
  ///
  /// TRUE if media are connected to the network interface; otherwise FALSE.
  ///
  BOOLEAN            MediaPresent;
} EFI_SIMPLE_NETWORK_MODE;

该结构体是SNP_DRIVER的成员,同时也是EFI_SIMPLE_NETWORK_PROTOCOL中的成员,两者是一致的,指向的是同一个内容。至于每个成员表示什么,从注释上基本能够看出来,这里不再一一介绍。

SimpleNetworkDriverStart()函数中会初始化该结构体中的大部分成员:

EFI_STATUS
EFIAPI
SimpleNetworkDriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  //
  //  Initialize simple network protocol mode structure
  //
  Snp->Mode.State               = EfiSimpleNetworkStopped;
  Snp->Mode.HwAddressSize       = Snp->InitInfo.HWaddrLen;
  Snp->Mode.MediaHeaderSize     = Snp->InitInfo.MediaHeaderLen;
  Snp->Mode.MaxPacketSize       = Snp->InitInfo.FrameDataLen;
  Snp->Mode.NvRamAccessSize     = Snp->InitInfo.NvWidth;
  Snp->Mode.NvRamSize           = Snp->InitInfo.NvCount * Snp->Mode.NvRamAccessSize;
  Snp->Mode.IfType              = Snp->InitInfo.IFtype;
  Snp->Mode.MaxMCastFilterCount = Snp->InitInfo.MCastFilterCnt;
  Snp->Mode.MCastFilterCount    = 0;
  if (Snp->CableDetectSupported || Snp->MediaStatusSupported) {
    Snp->Mode.MediaPresentSupported = TRUE;
  }
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_STATION_ADDR_SETTABLE) != 0) {
    Snp->Mode.MacAddressChangeable = TRUE;
  } else {
    Snp->Mode.MacAddressChangeable = FALSE;
  }
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_MULTI_FRAME_SUPPORTED) != 0) {
    Snp->Mode.MultipleTxSupported = TRUE;
  } else {
    Snp->Mode.MultipleTxSupported = FALSE;
  }
  Snp->Mode.ReceiveFilterMask = EFI_SIMPLE_NETWORK_RECEIVE_UNICAST;
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_PROMISCUOUS_MULTICAST_RX_SUPPORTED) != 0) {
    Snp->Mode.ReceiveFilterMask |= EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST;
  }
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_PROMISCUOUS_RX_SUPPORTED) != 0) {
    Snp->Mode.ReceiveFilterMask |= EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS;
  }
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_BROADCAST_RX_SUPPORTED) != 0) {
    Snp->Mode.ReceiveFilterMask |= EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST;
  }
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_FILTERED_MULTICAST_RX_SUPPORTED) != 0) {
    Snp->Mode.ReceiveFilterMask |= EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST;
  }
  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_PROMISCUOUS_MULTICAST_RX_SUPPORTED) != 0) {
    Snp->Mode.ReceiveFilterMask |= EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST;
  }
  Snp->Mode.ReceiveFilterSetting = 0;
  Snp->Mode.MediaPresent = FALSE;
}

这些数据大部分来自UNDI

EFI_SIMPLE_NETWORK_PROTOCOL

SNP提供的接口如下:

///
/// The EFI_SIMPLE_NETWORK_PROTOCOL protocol is used to initialize access 
/// to a network adapter. Once the network adapter initializes, 
/// the EFI_SIMPLE_NETWORK_PROTOCOL protocol provides services that 
/// allow packets to be transmitted and received.
///
struct _EFI_SIMPLE_NETWORK_PROTOCOL {
  ///
  /// Revision of the EFI_SIMPLE_NETWORK_PROTOCOL. All future revisions must 
  /// be backwards compatible. If a future version is not backwards compatible 
  /// it is not the same GUID.
  ///
  UINT64                              Revision;
  EFI_SIMPLE_NETWORK_START            Start;
  EFI_SIMPLE_NETWORK_STOP             Stop;
  EFI_SIMPLE_NETWORK_INITIALIZE       Initialize;
  EFI_SIMPLE_NETWORK_RESET            Reset;
  EFI_SIMPLE_NETWORK_SHUTDOWN         Shutdown;
  EFI_SIMPLE_NETWORK_RECEIVE_FILTERS  ReceiveFilters;
  EFI_SIMPLE_NETWORK_STATION_ADDRESS  StationAddress;
  EFI_SIMPLE_NETWORK_STATISTICS       Statistics;
  EFI_SIMPLE_NETWORK_MCAST_IP_TO_MAC  MCastIpToMac;
  EFI_SIMPLE_NETWORK_NVDATA           NvData;
  EFI_SIMPLE_NETWORK_GET_STATUS       GetStatus;
  EFI_SIMPLE_NETWORK_TRANSMIT         Transmit;
  EFI_SIMPLE_NETWORK_RECEIVE          Receive;
  ///
  /// Event used with WaitForEvent() to wait for a packet to be received.
  ///
  EFI_EVENT                           WaitForPacket;
  ///
  /// Pointer to the EFI_SIMPLE_NETWORK_MODE data for the device.
  ///
  EFI_SIMPLE_NETWORK_MODE             *Mode;
};
 
extern EFI_GUID gEfiSimpleNetworkProtocolGuid;

UNDI章节所述,上面的接口都是通过UNDI来实现的。对应的实现函数:

  Snp->Snp.Revision       = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION;
  Snp->Snp.Start          = SnpUndi32Start;
  Snp->Snp.Stop           = SnpUndi32Stop;
  Snp->Snp.Initialize     = SnpUndi32Initialize;
  Snp->Snp.Reset          = SnpUndi32Reset;
  Snp->Snp.Shutdown       = SnpUndi32Shutdown;
  Snp->Snp.ReceiveFilters = SnpUndi32ReceiveFilters;
  Snp->Snp.StationAddress = SnpUndi32StationAddress;
  Snp->Snp.Statistics     = SnpUndi32Statistics;
  Snp->Snp.MCastIpToMac   = SnpUndi32McastIpToMac;
  Snp->Snp.NvData         = SnpUndi32NvData;
  Snp->Snp.GetStatus      = SnpUndi32GetStatus;
  Snp->Snp.Transmit       = SnpUndi32Transmit;
  Snp->Snp.Receive        = SnpUndi32Receive;
  Snp->Snp.WaitForPacket  = NULL;

后面会介绍这些函数的实现。

Snp.Start

对应的实现是SnpUndi32Start(),其代码如下:

EFI_STATUS
PxeStart (
  IN SNP_DRIVER  *Snp
  )
{
  PXE_CPB_START_31  *Cpb31;

  Cpb31 = Snp->Cpb;
  //
  // Initialize UNDI Start CDB for H/W UNDI
  //
  Snp->Cdb.OpCode    = PXE_OPCODE_START;
  Snp->Cdb.OpFlags   = PXE_OPFLAGS_NOT_USED;
  Snp->Cdb.CPBsize   = PXE_CPBSIZE_NOT_USED;
  Snp->Cdb.DBsize    = PXE_DBSIZE_NOT_USED;
  Snp->Cdb.CPBaddr   = PXE_CPBADDR_NOT_USED;
  Snp->Cdb.DBaddr    = PXE_DBADDR_NOT_USED;
  Snp->Cdb.StatCode  = PXE_STATCODE_INITIALIZE;
  Snp->Cdb.StatFlags = PXE_STATFLAGS_INITIALIZE;
  Snp->Cdb.IFnum     = Snp->IfNum;
  Snp->Cdb.Control   = PXE_CONTROL_LAST_CDB_IN_LIST;

  //
  // Make changes to H/W UNDI Start CDB if this is
  // a S/W UNDI.
  //
  if (Snp->IsSwUndi) {
    Snp->Cdb.CPBsize = (UINT16)sizeof (PXE_CPB_START_31);
    Snp->Cdb.CPBaddr = (UINT64)(UINTN)Cpb31;

    Cpb31->Delay = (UINT64)(UINTN)&SnpUndi32CallbackDelay;
    Cpb31->Block = (UINT64)(UINTN)&SnpUndi32CallbackBlock;

    //
    // Virtual == Physical.  This can be set to zero.
    //
    Cpb31->Virt2Phys = (UINT64)(UINTN)0;
    Cpb31->Mem_IO    = (UINT64)(UINTN)&SnpUndi32CallbackMemio;

    Cpb31->Map_Mem   = (UINT64)(UINTN)&SnpUndi32CallbackMap;
    Cpb31->UnMap_Mem = (UINT64)(UINTN)&SnpUndi32CallbackUnmap;
    Cpb31->Sync_Mem  = (UINT64)(UINTN)&SnpUndi32CallbackSync;

    Cpb31->Unique_ID = (UINT64)(UINTN)Snp;
  }

  (*Snp->IssueUndi32Command)((UINT64)(UINTN)&Snp->Cdb);

  //
  // Set simple network state to Started and return success.
  //
  Snp->Mode.State = EfiSimpleNetworkStarted;
}

EFI_STATUS
EFIAPI
SnpUndi32Start (
  IN EFI_SIMPLE_NETWORK_PROTOCOL  *This
  )
{
  Status = PxeStart (Snp);
}

其它的实现函数也都类似,大致流程如下:

  1. 状态判断,SNP的状态都在EFI_SIMPLE_NETWORK_MODE这个结构体中。
  2. 构建CPB、CDB、DB等需要传递给UNDI的数据结构。
  3. UNDI调用:
 (*Snp->IssueUndi32Command)((UINT64)(UINTN)&Snp->Cdb);
  1. 调用执行后对返回数据的判断,也在CDB中。如果有返回数据,则在DB中。

EFI_SIMPLE_NETWORK_PROTOCOL中的其它接口基本都是按照这样的形式来完成操作的,这里不再多介绍。

SNP代码示例

网络应用程序编写中通常不会直接使用SNP,因为它发送的是RAW数据,并没有网络协议相关的数据信息,所以即使发送出去也不会被正常解析。

不过我们也可以对RAW数据进行自己的编码,让它成为有效的数据,然后通过Snp->Transmit()发送,在NetworkPkg\Library\DxeNetLib\DxeNetLib.c中的NetDebugOutput()就是一个示例,下面是示例代码(位于BeniPkg\DynamicCommand\TestDynamicCommand\TestSnp.c):

NET_DEBUG_ERROR ("TestSnp", ("This is a test!"));

这里的NET_DEBUG_ERROR是一个宏,它调用了NetDebugOutput()

#define NET_DEBUG_ERROR(Module, PrintArg) \
  NetDebugOutput ( \
    NETDEBUG_LEVEL_ERROR, \
    Module, \
    __FILE__, \
    DEBUG_LINE_NUMBER, \
    NetDebugASPrint PrintArg \
    )

编译后启动BIOS,在Shell下执行test snp,这样就发送了数据。为了能够抓取到这个数据,需要使用Wireshark工具。在主机上安装该工具,打开后选择tap0,开始抓取数据:

在这里插入图片描述

BIOS Shell下执行命令之后,得到如下的数据:

在这里插入图片描述

通过代码构建的Syslog包可以被Wireshark抓到,可以看到其数据与代码中填入的一致。

文章来源:https://blog.csdn.net/jiangwei0512/article/details/135396527
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。