UEFI实战(10) Network
开始编程之前,首先要配置好开发环境。分两种情况,一是使用Nt32模拟环境,二是使用真实的UEFI环境。
Nt32网络设置可以参考 UEFI Network Stack for EDK Getting Started Guide, 简略说明如下:
1。 下载并安装Winpcap
2. 下载 SnpNt32Io 并编译
c:/> cd c:/SnpNt32Io
c:/ SnpNt32Io> nmake TARGET=RELEASE
c:/ SnpNt32Io> copy /y SnpNt32Io.dll c:/edk2/build/Nt32Pkg/vs2008/IA32/
3. 启动Nt32模拟器
4。 加载网络协议
Shell > fsnt0:
fsnt0:/> load SnpNt32.efi Mnp.efi Arp.efi Ip4.efi Ip4Config.efi Udp4.efi Dhcp4.efi Mtftp4.efi Tcp4.efi
5 配置网卡
fsnt0:/> ifconfig –s eth0 Dhcp
fsnt0:/> ifconfig –s eth0 static 192.168.0.125 255.255.255.0 192.168.0.1
如果使用真实的UEFI环境,首先要加载网卡驱动, 然后加载网络协议Snp.efi Mnp.efi Arp.efi Ip4.efi Ip4Config.efi Udp4.efi Dhcp4.efi Mtftp4.efi Tcp4.efi,配置网卡.
网络协议栈
Snp(EFI_SIMPLE_NETWORK_PROTOCOL) 用于初始化和关闭网络接口,发送和接收数据包,
Mnp(EFI_MANAGED_NETWORK_PROTOCOL) 提供异步 网络包I/O操作
Arp(EFI_ARP_PROTOCOL)用于将IP地址转换为物理地址www.2cto.com
IP、TCP、UDP协议我们都耳熟能详了。
下面我们介绍一下TCP Protocol(EFI_TCP4_PROTOCOL)的用法。如果你曾经用Socket写过程序,那么会很容易理解EFI_TCP4_PROTOCOL的用法。 我们知道Socket 客户端需要这么几步:
1。 Create Socket
2。 connect
3。 Send/Recv
4。 Close
EFI_TCP4_PROTOCOL与之相似,客户端需要如下几步:
1。 Create EFI_TCP4_PROTOCOL对象
2。 Configure
3。 Connect
4。 Transmit(send)/Receive(Recv)
5。 Close
其实我们可以把Configure和Connect算成一步。
我们可以把EFI_TCP4_PROTOCOL封装成一个Socket类
#define SocketWait(e) /{
UINTN index;/
Status = gBS->WaitForEvent(1, &(e), &index); /
}
typedef EFI_STAUTS SOCKET_STATUS;
class Socket
{
public:
Socket(EFI_HANDLE ImageHandle);
~Socket();
SOCKET_STATUS Config(UINT32 Ip32, UINT16 Port);
SOCKET_STATUS Connect();
SOCKET_STATUS Connect(UINT32 Ip32, UINT16 Port){Config(Ip32, Port); reuturn Connect();};
SOCKET_STATUS Close();
SOCKET_STATUS Send(CHAR8* Data, UINTN Lenth);
SOCKET_STATUS Recv(CHAR8* Buffer, UINTN Lenth);
BOOL Ready(){return (m_pServiceBinding != NULL);}
private:
SOCKET Initialize();
EFI_HANDLE m_SocketHandle;
EFI_TCP4_PROTOCOL* m_pTcp4Protocol;
EFI_SERVICE_BINDING_PROTOCOL* m_pServiceBinding;
EFI_TCP4_CONFIG_DATA* m_pTcp4ConfigData;
EFI_TCP4_TRANSMIT_DATA* m_TransData;
EFI_TCP4_RECEIVE_DATA* m_RecvData;
EFI_TCP4_CONNECTION_TOKEN ConnectToken;
EFI_TCP4_CLOSE_TOKEN CloseToken;
EFI_TCP4_IO_TOKEN SendToken, RecvToken;
};下面一步步来看
1. 首先是产生一个EFI_TCP4_PROTOCOL对象。
EFI_TCP4_SERVICE_BINDING_PROTOCOL.CreateChild()用于产生一个Driver Handle, 此driver上挂载了EFI_TCP4_PROTOCOL, 用OpenProtocol或LocateProtocol可以获得此Handle上的EFI_TCP4_PROTOCOL对象。代码如下:
Socket::Socket(EFI_HANDLE ImageHandle)
{
EFI_STATUS Status;
memset((void*)this, 0, sizeof(Socket));
m_SocketHandle = NULL;
Status = gBS->LocateProtocol ( &gEfiTcp4ServiceBindingProtocolGuid,
NULL,
(VOID **)&m_pServiceBinding );
if(EFI_ERROR(Status))
return Status;
Status = m_pServiceBinding->CreateChild ( m_pServiceBinding,
&m_SocketHandle );
if(EFI_ERROR(Status))
return Status;
Status = gBS->OpenProtocol ( m_SocketHandle,
&gEfiTcp4ProtocolGuid,
(VOID **)&m_pTcp4Protocol,
ImageHandle,
m_SocketHandle,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL );
if(EFI_ERROR(Status))
return Status;
this -> Init();
}
2. 第二步, Configure,用于设置服务端IP和端口,本地端IP和端口,需要注意的是Configure完成之后,连接还没有建立
SOCKET_STATUS Socket::Config(UINT32 Ip32, UINT16 Port)
{
EFI_STATUS Status = EFI_NOT_FOUND;
if(m_pTcp4ConfigData == NULL) return Status;
m_pTcp4ConfigData->TypeOfService = 0;
m_pTcp4ConfigData->TimeToLive = 0;
*(UINT*)(m_pTcp4ConfigData->AccessPoint.RemoteAddress.Addr) = Ip32;
m_pTcp4ConfigData->AccessPoint.RemotePort = Port;
*(UINT32*)(m_pTcp4ConfigData->AccessPoint.SubnetMask.Addr) = (255 | 255 << 8 | 255 << 16 | 0 << 24) ;
m_pTcp4ConfigData->AccessPoint.UseDefaultAddress = TRUE;
/// 如果UseDefaultAddress 为FALSE, StationAddress 要设置
//*(UINT32*)(m_pTcp4ConfigData->AccessPoint.StationAddress.Addr) = LocalIp;
m_pTcp4ConfigData->AccessPoint.StationPort = 61558;
m_pTcp4ConfigData->AccessPoint.ActiveFlag = TRUE;
m_pTcp4ConfigData->ControlOption = NULL;
Status = m_pTcp4Protocol ->Configure(m_pTcp4Protocol, m_pTcp4ConfigData);
return Status;
}
EFI_TCP4_CONFIG_DATA* m_pTcp4ConfigData 定义如下
//
***************************************************************
// EFI_TCP4_CONFIG_DATA
//
***************************************************************
typedef struct {
// Receiving Filters
// I/O parameters
UINT8 TypeOfService;
UINT8 TimeToLive;
// Access Point
EFI_TCP4_ACCESS_POINT AccessPoint;
// TCP Control Options
EFI_TCP4_OPTION * ControlOption;
} EFI_TCP4_CONFIG_DATA;typedef struct {
BOOLEAN UseDefaultAddress; //True 表示使用本机默认IP。False则要指定StationAddress
// 本地IP和端口
EFI_IPv4_ADDRESS StationAddress;
EFI_IPv4_ADDRESS SubnetMask;
UINT16 StationPort;
// 服务端IP和端口
EFI_IPv4_ADDRESS RemoteAddress;
UINT16 RemotePort;
BOOLEAN ActiveFlag; // TRUE: Active Open; False: Passive Open(Server端)
} EFI_TCP4_ACCESS_POINT
第三步, 建立连接
EFI_STAUS Socket::Connect()
{
EFI_STATUS Status = EFI_NOT_FOUND;
if(m_pTcp4Protocol == NULL) return Status;
Status = m_pTcp4Protocol -> Connect(m_pTcp4Protocol, &ConnectToken);
if(EFI_ERROR(Status))
return Status;
SocketWait(ConnectToken.CompletionToken.Event);
return Status;
}看函数原型
typedef
EFI_STATUS
(EFIAPI *EFI_TCP4_CONNECT) (
IN EFI_TCP4_PROTOCOL *This,
IN EFI_TCP4_CONNECTION_TOKEN *ConnectionToken,
);typedef struct {
EFI_EVENT Event;
EFI_STATUS Status;
} EFI_TCP4_COMPLETION_TOKEN;
typedef struct {
EFI_TCP4_COMPLETION_TOKEN CompletionToken;
} EFI_TCP4_CONNECTION_TOKEN;
Connect是非阻塞函数,调用后立即返回。连接完成(成功或失败)后,系统会设置ConnectionToken中的事件以及状态,所以我们要在适当的时机查询或等待该事件。后面的几个函数也采用类似的机制。
3。 下面我们可以发送或接收数据了
先来看发送数据
typedef
EFI_STATUS
(EFIAPI *EFI_TCP4_TRANSMIT) (
IN EFI_TCP4_PROTOCOL *This,
IN EFI_TCP4_IO_TOKEN *Token
);
EFI_TCP4_IO_TOKEN 比EFI_TCP4_CONNECTION_TOKEN 复杂很多,发送数据要通过EFI_TCP4_IO_TOKEN 传递给EFI_TCP4_PROTOCOL。
//***************************************************************
// EFI_TCP4_IO_TOKEN
//***************************************************************
typedef struct {
EFI_TCP4_COMPLETION_TOKEN CompletionToken;
union {
EFI_TCP4_RECEIVE_DATA *RxData;
EFI_TCP4_TRANSMIT_DATA *TxData;
} Packet;
} EFI_TCP4_IO_TOKEN;//**************************************************************
// EFI_TCP4_TRANSMIT_DATA
//**************************************************************
typedef struct {
BOOLEAN Push;
BOOLEAN Urgent;
UINT32 DataLength;
UINT32 FragmentCount;
EFI_TCP4_FRAGMENT_DATA FragmentTable[1];
} EFI_TCP4_TRANSMIT_DATA;//***************************************************************
// EFI_TCP4_FRAGMENT_DATA
//***************************************************************
typedef struct {
UINT32 FragmentLength;
VOID *FragmentBuffer;
} EFI_TCP4_FRAGMENT_DATA;
待发送数据可能在几个不连续的缓冲区内,我们可以将这些缓冲区指针放到 FragmentTable数组内, 数组中每个元素表示一个缓冲区。DataLength是数据总长度(各个缓冲区长度之和), FragmentCount是缓冲区个数。
下面的代码中我们只用到了一个缓冲区。 同Connect一样, 调用Transmit之后要通过WaitForEvent等待发送完成。
SOCKET_STAUS Socket::Send(CHAR8* Data, UINTN Lenth)
{
EFI_STATUS Status = EFI_NOT_FOUND;
if(m_pTcp4Protocol == NULL) return Status;
m_TransData->Push = TRUE;
m_TransData->Urgent = TRUE;
m_TransData->DataLength = (UINT32)Lenth;
m_TransData->FragmentCount = 1;
m_TransData->FragmentTable[0].FragmentLength =m_TransData->DataLength;
m_TransData->FragmentTable[0].FragmentBuffer =Data;
SendToken.Packet.TxData= m_TransData;
Status = m_pTcp4Protocol -> Transmit(m_pTcp4Protocol, &SendToken);
if(EFI_ERROR(Status))
return Status;
SocketWait(SendToken.CompletionToken.Event);
return SendToken.CompletionToken.Status;
}
接收数据
接收与发送相似
typedef
EFI_STATUS
(EFIAPI *EFI_TCP4_RECEIVE) (
IN EFI_TCP4_PROTOCOL *This,
IN EFI_TCP4_IO_TOKEN *Token
);
//***************************************************************
// EFI_TCP4_RECEIVE_DATA
//***************************************************************
typedef struct {
BOOLEAN UrgentFlag;
UINT32 DataLength;
UINT32 FragmentCount;
EFI_TCP4_FRAGMENT_DATA FragmentTable[1];
} EFI_TCP4_RECEIVE_DATA;用户负责分配和释放缓冲区,DataLength是缓冲区总长度,接收完成时,Token中的事件被设置, DataLength也被设置为接收到的数据的长度。
SOCKET Socket::Recv(CHAR8* Buffer, UINTN Lenth)
{
EFI_STATUS Status = EFI_NOT_FOUND;
if(m_pTcp4Protocol == NULL) return Status;
m_RecvData->UrgentFlag = TRUE;
m_RecvData->DataLength = (UINT32)Lenth;
m_RecvData->FragmentCount = 1;
m_RecvData->FragmentTable[0].FragmentLength = m_RecvData->DataLength ;
m_RecvData->FragmentTable[0].FragmentBuffer = (void*)Buffer;
RecvToken.Packet.RxData= m_RecvData;
Status = m_pTcp4Protocol -> Receive(m_pTcp4Protocol, &RecvToken);
if(EFI_ERROR(Status))
return Status;
SocketWait(RecvToken.CompletionToken.Event);
return RecvToken.CompletionToken.Status;
}
回头看一下Token及m_RecvData 等相关辅助数据时如何建立的
SOCKET_STATUS Socket::Initialize()
{
EFI_STATUS Status;
// 建立Configure Data
m_pTcp4ConfigData = new EFI_TCP4_CONFIG_DATA[1];
// 建立 Connect Data
ConnectToken.CompletionToken.Status = EFI_ABORTED;
Status = gBS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, (EFI_EVENT_NOTIFY)NopNoify , (VOID*)&ConnectToken, &ConnectToken.CompletionToken.Event );
if(EFI_STATUS(Stauts)) return Status;
// 建立 Transmit Data
Status = gBS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, (EFI_EVENT_NOTIFY)NopNoify , (VOID*)&SendToken, &SendToken.CompletionToken.Event);
if(EFI_STATUS(Stauts)) return Status;
SendToken.CompletionToken.Status =EFI_ABORTED;
m_TransData = new EFI_TCP4_TRANSMIT_DATA[1];
// 建立 Recv Data
Status = gBS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, (EFI_EVENT_NOTIFY)NopNoify , (VOID*)&RecvToken, &RecvToken.CompletionToken.Event);
RecvToken.CompletionToken.Status =EFI_ABORTED;
m_RecvData = new EFI_TCP4_RECEIVE_DATA[1];
if(EFI_STATUS(Stauts)) return Status;
// 建立 Close Data
CloseToken.CompletionToken.Status = EFI_ABORTED;
Status = gBS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, (EFI_EVENT_NOTIFY)NopNoify , (VOID*)&CloseToken, &CloseToken.CompletionToken.Event );
return Status;
}
// 空函数
VOID NopNoify ( IN EFI_EVENT Event, IN VOID *Context )
{
}5。 最后要关闭EFI_TCP4_PROTOCOL, 并将m_SocketHandle 销毁。
__inline Socket::~Socket()
{
EFI_STATUS Status;
if(m_SocketHandle)
Status = m_pServiceBinding->DestroyChild ( m_pServiceBinding,
m_SocketHandle );
if(ConnectToken.CompletionToken.Event)
gBS->CloseEvent(ConnectToken.CompletionToken.Event);
if(SendToken.CompletionToken.Event)
gBS->CloseEvent(SendToken.CompletionToken.Event);
if(RecvToken.CompletionToken.Event)
gBS->CloseEvent(RecvToken.CompletionToken.Event);
if(SendToken.Packet.TxData){
delete SendToken.Packet.TxData;
SendToken.Packet.TxData = NULL;
}
if(RecvToken.Packet.RxData){
delete RecvToken.Packet.RxData;
RecvToken.Packet.RxData = NULL;
}
}
下面测试我们的Socket类:
EFI_STATUS
TestSocket(IN EFI_HANDLE ImageHandle)
{
EFI_STATUS Status = 0;
CHAR8 RequestData[]= "GET / HTTP/1.1/nHost:localhost/nAccept:* / * /nConnection:Keep-Alive/n/n";
CHAR8 *RecvBuffer = new CHAR8[1024+1];
Socket WebSocket(ImageHandle);
if( WebSocket.Ready() == TRUE){
WebSocket.Connect(192 | 168 << 8 |137 <<16 | 1 << 24, 80);
WebSocket.Send(RequestData, AsciiStrLen(RequestData)+2 );//! 必须 +2
Status = WebSocket.Recv(RecvBuffer, 1024);
WebSocket.Close();
}
delete RecvBuffer;
return Status;
}