SNP全称是Simple Network Protocol,它是EDK代码中能够控制的最底层的网络接口。该模块的作用有以下的几个部分:
网卡操作,比如初始化网卡,打开/关闭网口等。
提供数据的底层传输接口,供上层协议使用。
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网络协议栈中的关系图:
Supported函数主要是判断驱动是否需要执行,它主要是寻找依赖的Protocol是否已经安装,SNP的初始化函数(即Start函数)执行依赖于以下的部分:
Status = gBS->OpenProtocol (
Controller,
&gEfiDevicePathProtocolGuid,
NULL,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
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;
}
!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函数中体现。
SNP初始化主要做了以下的是事情:
EFI_PCI_IO_PROTOCOL
。EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL
。EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL
中得到PXE_UNDI
结构体,通过检查其Checksum来确定有效性,还会判断其中的Implementation
属性。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
);
初始化SNP_DRIVER
结构体,该结构体会在后面进一步说明。
执行必要的UNDI调用,完成信息获取,对应操作码:
PXE_OPCODE_START
PXE_OPCODE_GET_INIT_INFO
PXE_OPCODE_STATION_ADDRESS
进一步初始化SNP_DRIVER
结构体。
初始化完毕之后,还会执行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的网络收发并不能使用,而开启、关闭和传输的操作依赖于更上层的网络栈驱动代码。
创建ExitBootServicesEvent事件,也是执行上述的关闭操作,主要是为了在退出UEFI之前关闭网络,避免SNP驱动可能的DMA写坏了OS启动需要的内存空间。
最后安装gEfiSimpleNetworkProtocolGuid
对应的Protocol,供上层接口使用。
对于初始化SNP来说,最重要的有以下几点:
SNP_DRIVER
结构体初始化。EFI_SIMPLE_NETWORK_PROTOCOL
接口。后面将进一步说明。
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
结构体控制的网卡数目。
TxRxBufferSize
,TxRxBuffer
:这两个值确定一段内存空间,是UNDI需要使用的。UEFI通过UNDI的PXE_OPCODE_GET_INIT_INFO
操作获取UNDI的初始化信息(即TxRxBufferSize
),再通过这个初始化信息来分配一段内容空间(由TxRxBuffer
指定),在SNP中调用PxeInit()
函数时,会将TxRxBuffer
放到Cpb
中传递给UNDI。至于这段内存空间到底是干什么的,从名字上看应该是UNDI收发数据的缓存区。
PciIo
,IoBarIndex
,MemoryBarIndex
:前面已经说过一个SNP_DRIVER
对应一个网卡,所以这里的PciIo
就是用来访问该设备的接口,通过它也可以确定IoBarIndex
和MemoryBarIndex
,这两个参数指定了网卡在系统中映射的资源空间。
Cdb
,Cpb
,CpbUnmap
,Db
:CpbUnmap
并没有使用,剩下的三个参数是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。该结构体位于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。
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;
后面会介绍这些函数的实现。
对应的实现是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);
}
其它的实现函数也都类似,大致流程如下:
(*Snp->IssueUndi32Command)((UINT64)(UINTN)&Snp->Cdb);
EFI_SIMPLE_NETWORK_PROTOCOL
中的其它接口基本都是按照这样的形式来完成操作的,这里不再多介绍。
网络应用程序编写中通常不会直接使用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抓到,可以看到其数据与代码中填入的一致。