UEFI源码学习3.2 - DXE Protocol实现

1. 数据结构组织

在这里插入图片描述

Protocol主要的数据结构的组织如图所示,DXE CORE中对于Protocol的实现无非就是操作这些数据结构。

  • mProtocolDatabase是一个链表用于记录PROTOCOL_ENTRY。
LIST_ENTRY  mProtocolDatabase     = INITIALIZE_LIST_HEAD_VARIABLE (mProtocolDatabase);
  • PROTOCOL_ENTRY用于链接具体的Protocol实现和mProtocolDatabase,用于描述一个Protocol的节点。内部成员AllEntries用于链接mProtocolDatabase链表;Protocols是一个链表,用于链接PROTOCOL_ENTRY。
typedef struct {
  UINTN         Signature;
  /// Link Entry inserted to mProtocolDatabase
  LIST_ENTRY    AllEntries;
  /// ID of the protocol
  EFI_GUID      ProtocolID;
  /// All protocol interfaces
  LIST_ENTRY    Protocols;
  /// Registerd notification handlers
  LIST_ENTRY    Notify;
} PROTOCOL_ENTRY;
  • PROTOCOL_INTERFACE是具体的Protocol对象,其中包含多个成员:ByProtocol用于链接PROTOCOL_ENTRY的Protocols链表;Protocol指向其对应的PROTOCOL_ENTRY;Interface就是具体实现的函数指针集;Handle指定了该Protocol所对应的Image Handle对象;Link用于链接在Protocol对应的IHANDLE的Protocols链表,一个Image可以有多个Protocol所以IHANDLE的Protocols是一个链表。
typedef struct {
  UINTN             Signature;
  /// Link on IHANDLE.Protocols
  LIST_ENTRY        Link;
  /// Back pointer
  IHANDLE           *Handle;
  /// Link on PROTOCOL_ENTRY.Protocols
  LIST_ENTRY        ByProtocol;
  /// The protocol ID
  PROTOCOL_ENTRY    *Protocol;
  /// The interface value
  VOID              *Interface;
  /// OPEN_PROTOCOL_DATA list
  LIST_ENTRY        OpenList;
  UINTN             OpenListCount;
} PROTOCOL_INTERFACE;
  • IHANDLE就是一个具体的USER Handle。其中的Protocol链表记录了所打开的Protocol Interface对象
typedef struct {
  UINTN         Signature;
  /// All handles list of IHANDLE
  LIST_ENTRY    AllHandles;
  /// List of PROTOCOL_INTERFACE's for this handle
  LIST_ENTRY    Protocols;
  UINTN         LocateRequest;
  /// The Handle Database Key value when this handle was last created or modified
  UINT64        Key;
} IHANDLE;

2. CoreInstallProtocolInterface

edk2/MdeModulePkg/Core/Dxe/Hand/Handle.c

这个函数是gBS->InstallProtocolInterface的实现,用于安装一个Protocol。它是一个wrapper函数,实际内部就是CoreInstallProtocolInterfaceNotify。

oreInstallProtocolInterface (
  IN OUT EFI_HANDLE      *UserHandle,		--> Image自身的句柄
  IN EFI_GUID            *Protocol,			--> Protocol的GUID
  IN EFI_INTERFACE_TYPE  InterfaceType,		--> Interface类型
  IN VOID                *Interface			--> 具体Interface对象的指针
  )
{
  return CoreInstallProtocolInterfaceNotify (
           UserHandle,
           Protocol,
           InterfaceType,
           Interface,
           TRUE
           );
}

CoreInstallProtocolInterfaceNotify实现如下

  • CoreFindProtocolEntry查找于Protocol GUID对应的ProtEntry。这个函数实现后面讲,简单来说就是从mProtocolDatabase链表从找到对应ProtEntry。
  • AllocateZeroPool分配一个PROTOCOL_INTERFACE对象。
  • 根据UserHandle处理,如果UserHandle存在,那么就验证下这个UserHandle是否合法;如果不存在,就分配一个UserHandle并插入到gHandleList中。
  • 建立各个数据结构的关系。
  • 具体可看注释
EFI_STATUS
CoreInstallProtocolInterfaceNotify (
  IN OUT EFI_HANDLE      *UserHandle,
  IN EFI_GUID            *Protocol,
  IN EFI_INTERFACE_TYPE  InterfaceType,
  IN VOID                *Interface,
  IN BOOLEAN             Notify
  )
{
  PROTOCOL_INTERFACE  *Prot;
  PROTOCOL_ENTRY      *ProtEntry;
  IHANDLE             *Handle;
  EFI_STATUS          Status;
  VOID                *ExistingInterface;

  ...
  //查找于Protocol GUID对应的ProtEntry
  ProtEntry = CoreFindProtocolEntry (Protocol, TRUE);
  ...
  //分配一个PROTOCOL_INTERFACE对象
  Prot = AllocateZeroPool (sizeof (PROTOCOL_INTERFACE));
  ...
  Handle = (IHANDLE *)*UserHandle;
  if (Handle == NULL) {
    //UserHandle不存在,则分配一个Handle
    Handle = AllocateZeroPool (sizeof (IHANDLE));
    ...
    //初始化Handle的成员变量
    Handle->Signature = EFI_HANDLE_SIGNATURE;
    InitializeListHead (&Handle->Protocols);
    gHandleDatabaseKey++;
    Handle->Key = gHandleDatabaseKey;
    //把Handle插入到gHandleList表中
    InsertTailList (&gHandleList, &Handle->AllHandles);
  } else {
    //验证以下Handle是否合法
    Status = CoreValidateHandle (Handle);
    ...
  }
 
  Prot->Signature = PROTOCOL_INTERFACE_SIGNATURE;
  //建立Prot和Handle,ProtEntry和实现Interface的指针
  Prot->Handle    = Handle;
  Prot->Protocol  = ProtEntry;
  Prot->Interface = Interface;
  //初始化Openlist
  InitializeListHead (&Prot->OpenList);
  Prot->OpenListCount = 0;
  //把Prot的Link节点插入到Handle的Protocols链表中
  InsertHeadList (&Handle->Protocols, &Prot->Link);
  //把ByProtocol节点插入到ProtEntry的Protocols链表中
  InsertTailList (&ProtEntry->Protocols, &Prot->ByProtocol);
  if (Notify) {
  	//发出通知
    CoreNotifyProtocolEntry (ProtEntry);
  }

  Status = EFI_SUCCESS;
  ...
  return Status;
}

接下来来看一下CoreFindProtocolEntry 的实现,主要分为两步

  1. 从mProtocolDatabase查找有没有现成的ProtocolEntry,如果有匹配的GUID,就返回。
  2. 如果没有现成的ProtocolEntry,就从Memory Pool中分配一个,并插入到mProtocolDatabase链表中返回。
PROTOCOL_ENTRY  *
CoreFindProtocolEntry (
  IN EFI_GUID  *Protocol,
  IN BOOLEAN   Create
  )
{
  LIST_ENTRY      *Link;
  PROTOCOL_ENTRY  *Item;
  PROTOCOL_ENTRY  *ProtEntry;
  
  ProtEntry = NULL;
  //遍历mProtocolDatabase链表
  for (Link = mProtocolDatabase.ForwardLink;
       Link != &mProtocolDatabase;
       Link = Link->ForwardLink)
  {
    Item = CR (Link, PROTOCOL_ENTRY, AllEntries, PROTOCOL_ENTRY_SIGNATURE);
    //GUID匹配,返回
    if (CompareGuid (&Item->ProtocolID, Protocol)) {
      ProtEntry = Item;
      break;
    }
  }

  if ((ProtEntry == NULL) && Create) {
    //如果没有并且Create变量设上,则从memory pool中分配一个PROTOCOL_ENTRY对象。
    ProtEntry = AllocatePool (sizeof (PROTOCOL_ENTRY));

    if (ProtEntry != NULL) {
      //初始化成员变量
      ProtEntry->Signature = PROTOCOL_ENTRY_SIGNATURE;
      CopyGuid ((VOID *)&ProtEntry->ProtocolID, Protocol);
      InitializeListHead (&ProtEntry->Protocols);
      InitializeListHead (&ProtEntry->Notify);
      //把PROTOCOL_ENTRY插入到mProtocolDatabase链表中
      InsertTailList (&mProtocolDatabase, &ProtEntry->AllEntries);
    }
  }

  return ProtEntry;
}

3. CoreLocateProtocol

edk2/MdeModulePkg/Core/Dxe/Hand/Locate.c
这个函数是gBS->LocateHandle的实现,根据Protocol的GUID查找具体的Protocol并返回。·

  • 如果Registration 参数为NULL,那么就调用CoreFindProtocolEntry 来获取PROTOCOL_ENTRY。这个函数之前已经讲过了,根据GUID从mProtocolDatabase链表中获取PROTOCOL_ENTRY对象示例。然后再根据CoreGetNextLocateByProtocol 函数来获取具体的Interface对象出来。
  • 如果Registration 参数不为NULL, 那么就调用CoreGetNextLocateByRegisterNotify 来获取interface。
  • 最后获取IHANDE对象,如果IHANDLE存在,那么就返回成功。否则就查不到Interface。
EFI_STATUS
EFIAPI
CoreLocateProtocol (
  IN  EFI_GUID  *Protocol,
  IN  VOID      *Registration OPTIONAL,
  OUT VOID      **Interface
  )
{
  EFI_STATUS       Status;
  LOCATE_POSITION  Position;
  PROTOCOL_NOTIFY  *ProtNotify;
  IHANDLE          *Handle;


  *Interface = NULL;
  Status     = EFI_SUCCESS;


  Position.Protocol  = Protocol;
  Position.SearchKey = Registration;
  Position.Position  = &gHandleList;


  mEfiLocateHandleRequest += 1;

  if (Registration == NULL) {

    Position.ProtEntry = CoreFindProtocolEntry (Protocol, FALSE);
    if (Position.ProtEntry == NULL) {
      Status = EFI_NOT_FOUND;
      goto Done;
    }

    Position.Position = &Position.ProtEntry->Protocols;

    Handle = CoreGetNextLocateByProtocol (&Position, Interface);
  } else {
    Handle = CoreGetNextLocateByRegisterNotify (&Position, Interface);
  }

  if (Handle == NULL) {
    Status = EFI_NOT_FOUND;
  } else if (Registration != NULL) {
    ProtNotify           = Registration;
    ProtNotify->Position = ProtNotify->Position->ForwardLink;
  }
  ...
}

CoreGetNextLocateByProtocol 根据获取的PROTOCOL_ENTRY找到PROTOCOL_INTERFACE, 从而拿到接口实例以及该PROTOCOL_INTERFACE所对应的image句柄对象。

IHANDLE *
CoreGetNextLocateByProtocol (
  IN OUT LOCATE_POSITION  *Position,
  OUT VOID                **Interface
  )
{
  IHANDLE             *Handle;
  LIST_ENTRY          *Link;
  PROTOCOL_INTERFACE  *Prot;

  Handle     = NULL;
  *Interface = NULL;
  for ( ; ;) {
	//遍历PROTOCOL_ENTRY的Protocls链表
    Link               = Position->Position->ForwardLink;
    Position->Position = Link;

	//如果是链表头,则跳过。
    if (Link == &Position->ProtEntry->Protocols) {
      Handle = NULL;
      break;
    }

    //从链表节点ByProtocol拿到PROTOCOL_INTERFACE
    Prot       = CR (Link, PROTOCOL_INTERFACE, ByProtocol, PROTOCOL_INTERFACE_SIGNATURE);
    //从PROTOCOL_INTERFACE拿到image句柄
    Handle     = Prot->Handle;
    //从PROTOCOL_INTERFACE拿到Interface对象
    *Interface = Prot->Interface;

 	...
  }

  return Handle;
}

4. CoreOpenProtocol

CoreOpenProtocol是gBs->OpenProtocol的接口实现。这个函数由于Attributes参数的存在,非常复杂,限于篇幅,不去细说每一个Attributes如何处理,这里就看最核心的地方,如何去获取到Protocol接口指针。

EFI_STATUS
EFIAPI
CoreOpenProtocol (
  IN  EFI_HANDLE  UserHandle,				--> Protocol对应的image句柄
  IN  EFI_GUID    *Protocol,				--> Protocol的GUID
  OUT VOID        **Interface OPTIONAL,		--> Protocol接口指针
  IN  EFI_HANDLE  ImageHandle,				--> 当前image的句柄
  IN  EFI_HANDLE  ControllerHandle,			--> Controller句柄
  IN  UINT32      Attributes				--> 选项参数
  )
{
  EFI_STATUS          Status;
  PROTOCOL_INTERFACE  *Prot;
  LIST_ENTRY          *Link;
  OPEN_PROTOCOL_DATA  *OpenData;
  BOOLEAN             ByDriver;
  BOOLEAN             Exclusive;
  BOOLEAN             Disconnect;
  BOOLEAN             ExactMatch;
  ...  
  //判断Protocol对应的UserHandle是否合法
  Status = CoreValidateHandle (UserHandle);
  if (EFI_ERROR (Status)) {
    goto Done;
  }
  ...
  //根据UserHandle和Protocol GUID获取PROTOCOL_INTERFACE对象
  Prot = CoreGetProtocolInterface (UserHandle, Protocol);
  ...
  //从PROTOCOL_INTERFACE中拿到Interface指针并返回
  *Interface = Prot->Interface;
}

而CoreGetProtocolInterface这个函数做的非常简单,遍历IHANDE->Protocols链表,如果拿到GUID匹配的PROTOCOL_INTERFACE节点,则返回该PROTOCOL_INTERFACE节点。

PROTOCOL_INTERFACE  *
CoreGetProtocolInterface (
  IN  EFI_HANDLE  UserHandle,
  IN  EFI_GUID    *Protocol
  )
{
  EFI_STATUS          Status;
  PROTOCOL_ENTRY      *ProtEntry;
  PROTOCOL_INTERFACE  *Prot;
  IHANDLE             *Handle;
  LIST_ENTRY          *Link;
  //判断UserHandle是否合法
  Status = CoreValidateHandle (UserHandle);
  if (EFI_ERROR (Status)) {
    return NULL;
  }
  //转换指针为IHANDLE类型
  Handle = (IHANDLE *)UserHandle;

  //遍历IHANDLE中的Protocols链表
  for (Link = Handle->Protocols.ForwardLink; Link != &Handle->Protocols; Link = Link->ForwardLink) {
    Prot      = CR (Link, PROTOCOL_INTERFACE, Link, PROTOCOL_INTERFACE_SIGNATURE);
    ProtEntry = Prot->Protocol;
    //如果GUID匹配,那么就返回该节点
    if (CompareGuid (&ProtEntry->ProtocolID, Protocol)) {
      return Prot;
    }
  }
  return NULL;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是示例代码: ``` #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Protocol/Shell.h> #include <Library/BaseLib.h> #include <Library/PrintLib.h> EFI_STATUS EFIAPI UefiMain( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo; UINTN Segment, Bus, Device, Function; // Retrieve the PCI Root Bridge IO Protocol Status = gBS->LocateProtocol(&gEfiPciRootBridgeIoProtocolGuid, NULL, (VOID**)&PciRootBridgeIo); if (EFI_ERROR(Status)) { Print(L"Failed to locate PCI Root Bridge IO Protocol: %r\n", Status); return Status; } // Get the PCI bus, device, and function numbers of a device Status = PciRootBridgeIo->Pci.Read(PciRootBridgeIo, EfiPciWidthUint16, 0, 0, 0, &Segment); if (EFI_ERROR(Status)) { Print(L"Failed to read PCI configuration space: %r\n", Status); return Status; } Status = PciRootBridgeIo->Pci.Read(PciRootBridgeIo, EfiPciWidthUint16, 0, 0, 1, &Bus); if (EFI_ERROR(Status)) { Print(L"Failed to read PCI configuration space: %r\n", Status); return Status; } Status = PciRootBridgeIo->Pci.Read(PciRootBridgeIo, EfiPciWidthUint16, 0, 0, 2, &Device); if (EFI_ERROR(Status)) { Print(L"Failed to read PCI configuration space: %r\n", Status); return Status; } Status = PciRootBridgeIo->Pci.Read(PciRootBridgeIo, EfiPciWidthUint16, 0, 0, 3, &Function); if (EFI_ERROR(Status)) { Print(L"Failed to read PCI configuration space: %r\n", Status); return Status; } // Convert the PCI bus, device, and function numbers to a string CHAR16 Buffer[32]; UnicodeSPrint(Buffer, sizeof(Buffer), L"%04x:%02x:%02x.%x", Segment, Bus, Device, Function); // Execute the "echo" command with the string as an argument CHAR16 Command[64]; UnicodeSPrint(Command, sizeof(Command), L"echo %s", Buffer); Status = ShellExecute(&gEfiShellProtocolGuid, Command, NULL, NULL); if (EFI_ERROR(Status)) { Print(L"Failed to execute command: %r\n", Status); return Status; } return EFI_SUCCESS; } ``` 这个代码使用了UnicodeSPrint函数将PCI总线,设备和功能号拼接成一个字符串,然后使用ShellProtocol->Execute函数执行一个“echo”命令,并将字符串作为参数传递给该命令。在实际使用中,您需要替换PCI设备的总线,设备和功能号,并根据需要更改Shell命令。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值