開始編程之前,首先要配置好開發環境。分兩種情況,一是使用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;
}