【UEFI基础】UEFI网络框架之UNDI

UNDI

UNDI全称Universal Network Driver Interface。

它并不是UEFI网络框架的一部分,甚至也可以不是UEFI的一部分。

不过目前UEFI下的网络驱动都会实现UNDI,这样UEFI就可以通过SNP来调用网卡的底层驱动。

在《UEFI Spec 2_6.pdf》中有对UNDI的详细介绍,这里简单说明下UNDI。

UNDI说到底是定义了一系列的接口,然后SNP来访问这些接口。

SNP如何获取到这些接口呢?

这需要实现了UNDI的网络设备驱动中安装一个NetworkInterfaceIdentifier(NII)协议,目前它的版本是3_10,SNP就可以通过对应的GUID来访问到它:

  //
  // Get the NII interface.
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  (VOID **) &Nii,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
          Controller,
          &gEfiDevicePathProtocolGuid,
          This->DriverBindingHandle,
          Controller
          );
    return Status;
  }

  DEBUG ((EFI_D_INFO, "Start(): UNDI3.1 found\n"));

  Pxe = (PXE_UNDI *) (UINTN) (Nii->Id);

如上面代码所示,NII中最重要的是PXE_UNDI指针。

它的结构体如下:

typedef union u_pxe_undi {
  PXE_HW_UNDI hw;
  PXE_SW_UNDI sw;
} PXE_UNDI;

可以看到它存在两种类型,从字面意思上看一种是硬件的,一种是软件的,对应的结构体如下:

这种结构体有一个奇怪的名字叫!PXE,不知道这里的叹号表示什么意思,难道是表示“非”。

上面的结构体成员不一一介绍了,可以参考《UEFI Spec 2_6.pdf》或者其它版本也可以。

从这里我们可以看出硬件UNDI和软件UNDI的一个重大区别,即硬件UNDI通过往MMIO或者IO寄存器写命令来调用底层接口,而软件UNDI通过网络设备驱动提供出来的Entry Point来调用底层接口。

从目前SNP的实现来看,硬件UNDI并不支持。

  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_HW_UNDI) != 0) {
    Snp->IsSwUndi             = FALSE;
    Snp->IssueUndi32Command   = &IssueHwUndiCommand;
  } else {
    Snp->IsSwUndi = TRUE;

    if ((Pxe->sw.Implementation & PXE_ROMID_IMP_SW_VIRT_ADDR) != 0) {
      Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) Pxe->sw.EntryPoint;
    } else {
      Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) ((UINT8) (UINTN) Pxe + Pxe->sw.EntryPoint);
    }
  }

上面的代码是用来获取访问网络驱动底层实现的接口,可以看到对于硬件UNDI直接使用了IssueHwUndiCommand()这个函数,但是它直接返回了Unsupported。

而软件UNDI的接口是从!PXE这个结构体中获取的。

对于底层接口的访问如下图所示:

从软件来看,实际上就是下面的几个步骤:

1. 填充CDB;

2. 调用Snp->IssueUndi32Command,参数就是CDB;

3. 判断返回值;

已SNP中的PxeStart()函数为例:

/**
  Call UNDI to start the interface and changes the snp state.

  @param  Snp                    pointer to snp driver structure.

  @retval EFI_SUCCESS            UNDI is started successfully.
  @retval EFI_DEVICE_ERROR       UNDI could not be started.
  
**/
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;
  }
  //
  // Issue UNDI command and check result.
  //
  DEBUG ((EFI_D_NET, "\nsnp->undi.start()  "));

  (*Snp->IssueUndi32Command) ((UINT64)(UINTN) &Snp->Cdb);

  if (Snp->Cdb.StatCode != PXE_STATCODE_SUCCESS) {
    //
    // UNDI could not be started. Return UNDI error.
    //
    DEBUG (
      (EFI_D_ERROR,
      "\nsnp->undi.start()  %xh:%xh\n",
      Snp->Cdb.StatCode,
      Snp->Cdb.StatFlags)
      );

    return EFI_DEVICE_ERROR;
  }
  //
  // Set simple network state to Started and return success.
  //
  Snp->Mode.State = EfiSimpleNetworkStarted;

  return EFI_SUCCESS;
}

CBD结构体如下:

OpCode是操作码,不同的操作对OpFlags、CPB结构体、DB结构体(就是CPBxxx,DBxxx那几个成员,它们对应到结构体中)都会有影响;

StatCode和StatFlags是返回的参数,也受到OpCode的影响;

IFnum用来处理一个NII对应多个物理网络设备的情况,值从0开始,算是一个Index;

Control可以指示使用了一个CDB还是多个,还可以指示当操作忙时是等待命令执行还是直接返回失败;

OpCode的值可以在UefiPxe.h中找到具体的值。

Snp中的所有操作,实际上都到最后都是使用上述的方式来完成的。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值