程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> UEFI實戰(10) Network

UEFI實戰(10) Network

編輯:C++入門知識

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved