UEFI实战(10) Network

来源:岁月联盟 编辑:exp 时间:2012-06-16

开始编程之前,首先要配置好开发环境。分两种情况,一是使用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;
}