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

短命女 2023-10-17 09:21 92阅读 0赞

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来访问到它:

  1. //
  2. // Get the NII interface.
  3. //
  4. Status = gBS->OpenProtocol (
  5. Controller,
  6. &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
  7. (VOID **) &Nii,
  8. This->DriverBindingHandle,
  9. Controller,
  10. EFI_OPEN_PROTOCOL_BY_DRIVER
  11. );
  12. if (EFI_ERROR (Status)) {
  13. gBS->CloseProtocol (
  14. Controller,
  15. &gEfiDevicePathProtocolGuid,
  16. This->DriverBindingHandle,
  17. Controller
  18. );
  19. return Status;
  20. }
  21. DEBUG ((EFI_D_INFO, "Start(): UNDI3.1 found\n"));
  22. Pxe = (PXE_UNDI *) (UINTN) (Nii->Id);

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

它的结构体如下:

  1. typedef union u_pxe_undi {
  2. PXE_HW_UNDI hw;
  3. PXE_SW_UNDI sw;
  4. } PXE_UNDI;

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

20161101220104710

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

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

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

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

  1. if ((Pxe->hw.Implementation & PXE_ROMID_IMP_HW_UNDI) != 0) {
  2. Snp->IsSwUndi = FALSE;
  3. Snp->IssueUndi32Command = &IssueHwUndiCommand;
  4. } else {
  5. Snp->IsSwUndi = TRUE;
  6. if ((Pxe->sw.Implementation & PXE_ROMID_IMP_SW_VIRT_ADDR) != 0) {
  7. Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) Pxe->sw.EntryPoint;
  8. } else {
  9. Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) ((UINT8) (UINTN) Pxe + Pxe->sw.EntryPoint);
  10. }
  11. }

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

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

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

20161101231455417

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

  1. 填充CDB;

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

  3. 判断返回值;

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

  1. /**
  2. Call UNDI to start the interface and changes the snp state.
  3. @param Snp pointer to snp driver structure.
  4. @retval EFI_SUCCESS UNDI is started successfully.
  5. @retval EFI_DEVICE_ERROR UNDI could not be started.
  6. **/
  7. EFI_STATUS
  8. PxeStart (
  9. IN SNP_DRIVER *Snp
  10. )
  11. {
  12. PXE_CPB_START_31 *Cpb31;
  13. Cpb31 = Snp->Cpb;
  14. //
  15. // Initialize UNDI Start CDB for H/W UNDI
  16. //
  17. Snp->Cdb.OpCode = PXE_OPCODE_START;
  18. Snp->Cdb.OpFlags = PXE_OPFLAGS_NOT_USED;
  19. Snp->Cdb.CPBsize = PXE_CPBSIZE_NOT_USED;
  20. Snp->Cdb.DBsize = PXE_DBSIZE_NOT_USED;
  21. Snp->Cdb.CPBaddr = PXE_CPBADDR_NOT_USED;
  22. Snp->Cdb.DBaddr = PXE_DBADDR_NOT_USED;
  23. Snp->Cdb.StatCode = PXE_STATCODE_INITIALIZE;
  24. Snp->Cdb.StatFlags = PXE_STATFLAGS_INITIALIZE;
  25. Snp->Cdb.IFnum = Snp->IfNum;
  26. Snp->Cdb.Control = PXE_CONTROL_LAST_CDB_IN_LIST;
  27. //
  28. // Make changes to H/W UNDI Start CDB if this is
  29. // a S/W UNDI.
  30. //
  31. if (Snp->IsSwUndi) {
  32. Snp->Cdb.CPBsize = (UINT16) sizeof (PXE_CPB_START_31);
  33. Snp->Cdb.CPBaddr = (UINT64)(UINTN) Cpb31;
  34. Cpb31->Delay = (UINT64)(UINTN) &SnpUndi32CallbackDelay;
  35. Cpb31->Block = (UINT64)(UINTN) &SnpUndi32CallbackBlock;
  36. //
  37. // Virtual == Physical. This can be set to zero.
  38. //
  39. Cpb31->Virt2Phys = (UINT64)(UINTN) 0;
  40. Cpb31->Mem_IO = (UINT64)(UINTN) &SnpUndi32CallbackMemio;
  41. Cpb31->Map_Mem = (UINT64)(UINTN) &SnpUndi32CallbackMap;
  42. Cpb31->UnMap_Mem = (UINT64)(UINTN) &SnpUndi32CallbackUnmap;
  43. Cpb31->Sync_Mem = (UINT64)(UINTN) &SnpUndi32CallbackSync;
  44. Cpb31->Unique_ID = (UINT64)(UINTN) Snp;
  45. }
  46. //
  47. // Issue UNDI command and check result.
  48. //
  49. DEBUG ((EFI_D_NET, "\nsnp->undi.start() "));
  50. (*Snp->IssueUndi32Command) ((UINT64)(UINTN) &Snp->Cdb);
  51. if (Snp->Cdb.StatCode != PXE_STATCODE_SUCCESS) {
  52. //
  53. // UNDI could not be started. Return UNDI error.
  54. //
  55. DEBUG (
  56. (EFI_D_ERROR,
  57. "\nsnp->undi.start() %xh:%xh\n",
  58. Snp->Cdb.StatCode,
  59. Snp->Cdb.StatFlags)
  60. );
  61. return EFI_DEVICE_ERROR;
  62. }
  63. //
  64. // Set simple network state to Started and return success.
  65. //
  66. Snp->Mode.State = EfiSimpleNetworkStarted;
  67. return EFI_SUCCESS;
  68. }

CBD结构体如下:

20161101231908408

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

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

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

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

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

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

发表评论

表情:
评论列表 (有 0 条评论,92人围观)

还没有评论,来说两句吧...

相关阅读

    相关 UEFI基础】EDK框架

    什么是EDK EDK全称EFI Developer Kit,它实际上是一套实现了UEFI标准的开源代码,开发者可以在此基础上开发UEFI下的设备驱动或者其它应用。 ED