UEFI——protocol服务详解


之前已经详尽的讨论过在 UEFI简单应用中由 安装protocol引出的关于gBS的相关代码 ( UEFI基础服务)但是对于其中的关键部分 InstallProtocolInterface 我们还没有深入的研究,接下来就对这个部分进行讨论学习。

InstallProtocolInterface

最常见的安装protocol 的原型,在mBootServices可以看到 CoreInstallProtocolInterface的函数原型

EFI_STATUS
EFIAPI
CoreInstallProtocolInterface (
  IN OUT EFI_HANDLE     *UserHandle,
  IN EFI_GUID           *Protocol,
  IN EFI_INTERFACE_TYPE InterfaceType,
  IN VOID               *Interface
  )
{
  return CoreInstallProtocolInterfaceNotify (
            UserHandle,
            Protocol,
            InterfaceType,
            Interface,
            TRUE
            );
}

可以看到这个函数原型看起来十分简单,只是调用了一个简单的函数CoreInstallProtocolInterfaceNotify,函数的输入参数一共有四个,我们先一一看过
ps:下面英文为Code中的注释,中文为对应的翻译+我自己的理解。并非一一对应关系。

UserHandle : The handle to install the protocol handler on,or NULL if a new handle is to be allocated
待安装 protocol handlerhandle,如果输入为NULL,则分配一个新的handle
首先注意上述加粗的部分,是两个完全不同的单词也代表了不同的东西。其次 上述的注释告诉我们两重意思:
1. 如果输入的UserHandle不是空的,便直接将protocol handler 安装在这里
2. 如果输入的UserHandle是空的,也不影响,只需要重新分配UserHandle然后继续安装protocol (这个部分在分析代码的时候我们会有所提及)
Protocol : The protocol to add to the handle
要安装到handle上的protocol (的GUID)。
根据实际代码中的使用情况来看,这个位置传入的都是对应的GUID
InterfaceType : Indicates whether Interface is supplied in native form.
指示Interface是否以native的形式被提供
这个位置参数的区别现在我还没有研究明白 以后遇到或者研究明白再说
Interface : The interface for the protocol being added
对于对应的protocol待添加的interface
其实从这个注释中我们就能够发现,在实际的使用中我们所说的添加protocol,实际上是添加某个protocol的interface

输入参数大致已经介绍完成,接下来顺藤摸瓜,见识一下CoreInstallProtocolInterfaceNotify是不是依旧这么简单。

CoreInstallProtocolInterfaceNotify

输入参数

查看CoreInstallProtocolInterfaceNotify原型,首先是输入参数情况

EFI_STATUS
CorenstallProtocolInterfaceNotify (
  IN OUT EFI_HANDLE     *UserHandle,
  IN EFI_GUID           *Protocol,
  IN EFI_INTERFACE_TYPE InterfaceType,
  IN VOID               *Interface,
  IN BOOLEAN            Notify
  )

除了常见的参数外CoreInstallProtocolInterfaceNotify还增加了一个参数Notify 属于bool类型的参数 ,在这个函数中已经被默认为true ,在此先不做过多的解释

入参判断

向下看函数主体的内容:

  // returns EFI_INVALID_PARAMETER if InterfaceType is invalid.
  // Also added check for invalid UserHandle and Protocol pointers.
  //
  if (UserHandle == NULL || Protocol == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (InterfaceType != EFI_NATIVE_INTERFACE) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Print debug message
  //
  DEBUG((DEBUG_INFO, "InstallProtocolInterface: %g %p\n", Protocol, Interface));

注意输入的第一个参数UserHandle 是一个指向EFI_HANDLE的指针,我们首先要保证其取值是有意义的,也就是*UserHandle要有意义,是指向某一个EFI_HANDLE的,既然如此输入就一定不能为NULL。
故在函数体中 我们首先要判断这个输入的指针是否为空 如果为空 则直接返回
然后在后面我们会判断这个指针指向的EFI_HANDLE是否为NULL,注意EFI_HANDLE的本质也是一个指针,也就是此时判断EFI_HANDLE是否为空,如果为空,分配内存。
以上这两者无非都是要保证输入的UserHandle一定指向了正确的位置——指向某位置的EFI_HANDLE,也就是说EFI_HANDLE是一定要存在的,这样才能够为他分配相应内存


这个部分主要是排除了一些输入参数为空的异常情况 没有什么特殊需要讲的 继续

  Status = EFI_OUT_OF_RESOURCES;
  Prot = NULL;
  Handle = NULL;

  if (*UserHandle != NULL) {
    Status = CoreHandleProtocol (*UserHandle, Protocol, (VOID **)&ExistingInterface);
    if (!EFI_ERROR (Status)) {
      return EFI_INVALID_PARAMETER;
    }
  }

这个位置引入了一个新的函数CoreHandleProtocol,这个函数也是一个非常常用的函数。想要继续向下研究,就需要将这个新函数理解明白,故此处详细分析一下这个函数(深入的分析对理解handle protocol也是很有益的)

CoreHandleProtocol

老规矩先看函数的原型

// 查询handle是否支持特定的protocol
/**
  Queries a handle to determine if it supports a specified protocol.

  @param  UserHandle             The handle being queried.
  @param  Protocol               The published unique identifier of the protocol.
  @param  Interface              Supplies the address where a pointer to the
                                 corresponding Protocol Interface is returned.

  @retval EFI_SUCCESS            The interface information for the specified protocol was returned.
  @retval EFI_UNSUPPORTED        The device does not support the specified protocol.
  @retval EFI_INVALID_PARAMETER  Handle is NULL..
  @retval EFI_INVALID_PARAMETER  Protocol is NULL.
  @retval EFI_INVALID_PARAMETER  Interface is NULL.

**/

EFI_HANDLE            gDxeCoreImageHandle = NULL;

EFI_STATUS
EFIAPI
CoreHandleProtocol (
  IN EFI_HANDLE       UserHandle,
  IN EFI_GUID         *Protocol,
  OUT VOID            **Interface
  )
{
  return CoreOpenProtocol (
          UserHandle,
          Protocol,
          Interface,
          gDxeCoreImageHandle,
          NULL,
          EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
          );
}

emm … 没想到这个函数也是套娃的形式 但是完全不需要担心 这样也就意味着我们搞清楚了CoreOpenProtocol这个函数也就了解了CoreHandleProtocol。(UEFI Code中90%的代码都是在套娃 所以 不要害怕套娃 慢慢找就有收获)
这个函数的输入参数其实很简单,只有三个,其中两个是真正的入参 :UserHandleProtocol,另一个Interface用来保存输出结果——这个结果具体是什么,我们往后看就知道了。


此处仅仅强调一下,在使用这个函数的时候,其输入参数并非 EFI_HANDLE指针,所以,当我们想要在CoreInstallProtocolInterfaceNotify中使用这个函数的时候,我们将传入的 EFI_HANDLE *UserHandle 进行了取值的操作,传入函数的是 * UserHandle


接下来 来看CoreOpenProtocol 在当前函数中,CoreOpenProtocol的六个输入参数已经有三个(后三个)为固定值,只有三个参数是当前可以变换的,做到了和CoreHandleProtocol的统一。

CoreOpenProtocol

简介:查询指定的Handle是否支持指定的protocol 并且打开对应的protocol interface
原型:

EFI_STATUS
EFIAPI
CoreOpenProtocol (
  IN  EFI_HANDLE  UserHandle,
  IN  EFI_GUID    *Protocol,
  OUT VOID        **Interface OPTIONAL,
  IN  EFI_HANDLE  ImageHandle,
  IN  EFI_HANDLE  ControllerHandle,
  IN  UINT32      Attributes
  )                Attributes

再次简单的说明一下该函数的输入参数的情况:

UserHandle : 待查询的 Handle
protocol : 目标 protocol 的GUID
Interface : 返回打开的protocol
ImageHandle : 打开此protocol的Image
ControllerHandle : 使用此protocol的控制器 (这个参数的意义还有待继续深入的了解 现在还没有十分理解具体这个参数指的是什么)
Attribute: 打开protocol的方式 ,一共有六种

下面是UEFI中定义的 六种 Attribute

//MdePkg\Include\Uefi\UefiSpec.h
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL  0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL        0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL       0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER           0x00000010

//表示独占这个protocol 不和其他的driver共享
//如果protocol 已经打开 再次打开的时候就会失败
#define EFI_OPEN_PROTOCOL_EXCLUSIVE           0x00000020

输入参数简单介绍完成,开始说下面的具体代码实现
代码中 首先检查了protocol Interface 和 attribute 三个参数的有效性 当输入的这三个参数不满足有效性的要求的时候,直接结束返回

  //
  // Check for invalid Protocol
  //
  if (Protocol == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check for invalid Interface
  //
  if ((Attributes != EFI_OPEN_PROTOCOL_TEST_PROTOCOL) && (Interface == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

接下来有一个为了操作安全而进行的上锁的操作 函数大致作用提升当前操作的优先级 锁住protocol 数据库 避免出现冲突 具体这个函数么有详细的研究 先忽略 影响不大按下不表

  //
  // Lock the protocol database
  //
  CoreAcquireProtocolLock ();

接下来进行了输入的UserHandle有效性的检查,这个函数简单的说一下

CoreValidateHandle

简介:在Handledatabase链表中去遍历所有的成员寻找目标UserHandle 确保其确实存在

  // Check for invalid UserHandle
  //
  Status = CoreValidateHandle (UserHandle);
  if (EFI_ERROR (Status)) {
    return Status;
  }
CoreValidateHandle 的原型

观察函数 CoreValidateHandle 的原型

/**
  Check whether a handle is a valid EFI_HANDLE

  @param  UserHandle             The handle to check

  @retval EFI_INVALID_PARAMETER  The handle is NULL or not a valid EFI_HANDLE.
  @retval EFI_SUCCESS            The handle is valid EFI_HANDLE.

**/
EFI_STATUS
CoreValidateHandle (
  IN  EFI_HANDLE                UserHandle
  )
{
  IHANDLE             *Handle;
  LIST_ENTRY          *Link;

  if (UserHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  for (Link = gHandleList.BackLink; Link != &gHandleList; Link = Link->BackLink) {
    Handle = CR (Link, IHANDLE, AllHandles, EFI_HANDLE_SIGNATURE);
    if (Handle == (IHANDLE *) UserHandle) {
      return EFI_SUCCESS;
    }
  }

  return EFI_INVALID_PARAMETER;
}

其实这个函数很简单 ,就是在 之前讲过的Handledatabase链表中去遍历所有的成员寻找目标UserHandle是否存在于Handledatabase中 ,找到了就返回EFI_SUCCESS告知已经找到,若没有找到就返回EFI_INVALID_PARAMETER表示在链表中根本不存在当前目标的UserHandle
链表去看protocol详解的那个网图 很容易就能明白 我自认为画的还是挺清晰 这就是个非常简单的遍历操作
(没啥技巧 越看代码越发现其实真正的奇技淫巧很少 大都是将具象的东西进行了抽象化的处理,其是真的难度在抽象这个部分)


Tips about CR

这个部分还有一个UEFI中常见的宏定义 CR

  #define CR(Record, TYPE, Field, TestSignature) //此处的TestSignature 单纯判断是否输出错误信息                                        \
    BASE_CR (Record, TYPE, Field)

  #define BASE_CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - OFFSET_OF (TYPE, Field)))
  #define OFFSET_OF(TYPE, Field) ((UINTN) &(((TYPE *)0)->Field))

这个地方详细的解释一下这个宏 按照从里到外的顺序进行

首先是OFFSET_OF(TYPE, Field)

这个宏定义有几点需要注意的

  1. (TYPE *)0表示将数字0强制类型转换为TYPE类型 (TYPE为结构体类型) 的指针。因此这里的0代表内存地址0,即我们认为内存地址0开始的sizeof(TYPE)个字节内存储的是一个TYPE类型的变量。
  2. ((TYPE )0)->Field得到该结构体变量中的MEMBER成员变量,而 &(((TYPE)0)->Field) 使用取地址符&取得了Field成员变量的地址,(UINTN)加在前面表示将地址的类型进行了一个强制转换 并且将转换的结果作为返回值进行返回

然后再看BASE_CR

  1. Record表示当前实例化的结构体中的目标成员变量的 实际地址

  2. 用成员变量的实际地址减去之前利用OFFSET_OF计算出的偏移量 得到的就是当前这个实例化结构体的起始地址

  3. 最终将得到的地址转换成(TYPE *)类型 进行返回就能够得到最终结构体的起始位置指针

总结一下 :

BASE_CR就是根据实例化结构体中的某成员返回一个指向该结构体起始的指针

TYPE是结构体类型的定义

Record是结构体实体成员

Field是结构体定义中的成员名称


接下来根据给定的Attributes 依次对输入参数的有效性进行检查,核心都是使用CoreValidateHandle这个函数

 //
  // Check for invalid Attributes
  //
  switch (Attributes) {
    case EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER:
      Status = CoreValidateHandle (ImageHandle);
      if (EFI_ERROR (Status)) {
        goto Done;
      }

      Status = CoreValidateHandle (ControllerHandle);
      if (EFI_ERROR (Status)) {
        goto Done;
      }

      if (UserHandle == ControllerHandle) {
        Status = EFI_INVALID_PARAMETER;
        goto Done;
      }

      break;
    case EFI_OPEN_PROTOCOL_BY_DRIVER:
    case EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE:
      Status = CoreValidateHandle (ImageHandle);
      if (EFI_ERROR (Status)) {
        goto Done;
      }

      Status = CoreValidateHandle (ControllerHandle);
      if (EFI_ERROR (Status)) {
        goto Done;
      }

      break;
    case EFI_OPEN_PROTOCOL_EXCLUSIVE:
      Status = CoreValidateHandle (ImageHandle);
      if (EFI_ERROR (Status)) {
        goto Done;
      }

      break;
    case EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL:
    case EFI_OPEN_PROTOCOL_GET_PROTOCOL:
    case EFI_OPEN_PROTOCOL_TEST_PROTOCOL:
      break;
    default:
      Status = EFI_INVALID_PARAMETER;
      goto Done;
  }

如果你不记得了 往前翻看一下 ,当前使用CoreOpenProtocol时的 Attributes已经被定义成EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL,所以这个部分应该是直接break的。
继续向下 ,又是一个新的函数 CoreGetProtocolInterface

CoreGetProtocolInterface

简介:在给定的UserHandle 后查找目标GUIDprotocol interface 返回给Port

  //
  // Look at each protocol interface for a match
  //
  Prot = CoreGetProtocolInterface (UserHandle, Protocol);
CoreGetProtocolInterface原型
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;
  }

  Handle = (IHANDLE *)UserHandle;
  //在链表中查找符合传入的Protocol的protocol-interface
  //
  // Look at each protocol interface for a match
  //
  for (Link = Handle->Protocols.ForwardLink; Link != &Handle->Protocols; Link = Link->ForwardLink) {
    //首先找到链表中每个结构体的头指针
    Prot = CR(Link, PROTOCOL_INTERFACE, Link, PROTOCOL_INTERFACE_SIGNATURE);
    //找到这个protocol interface 对应的protocol entry
    ProtEntry = Prot->Protocol;
    //比较目标protocol 的 GUID和当前链表中结构体的GUID是否一致
    if (CompareGuid (&ProtEntry->ProtocolID, Protocol)) {
        //如果一致返回找到的protocol interface  
        return Prot;
    }
  }
  return NULL;
}

这个函数和之前的有效性验证非常相似,都是遍历链表去查找目标,所以不过多的讲解,需要注意的是这次遍历的是 HANDLE-PROTOCOL-INTERFACE 链表

总结: CoreGetProtocolInterface就是在给定的UserHandle上面查找是否存在符合GUID要求的protocol-interface 如果存在 返回当前的protocol-interface结构体 (代码中记为 port) 如果不存在 返回一个NULL指针

port(protocol-interface) 的处理

我们接着将思路折回到之前的位置 ,

  //
  // Look at each protocol interface for a match
  //
  Prot = CoreGetProtocolInterface (UserHandle, Protocol);

函数执行完毕之后,只可能存在两种结果 : Prot 存在或者不存在。
代码首先处理了Prot不存在的情况:

  if (Prot == NULL) {
    Status = EFI_UNSUPPORTED;
    goto Done;
  }

Done的情况

Done:

  if (Attributes != EFI_OPEN_PROTOCOL_TEST_PROTOCOL) {
    //
    // Keep Interface unmodified in case of any Error
    // except EFI_ALREADY_STARTED and EFI_UNSUPPORTED.
    //
    if (!EFI_ERROR (Status) || (Status == EFI_ALREADY_STARTED)) {
      //
      // According to above logic, if 'Prot' is NULL, then the 'Status' must be
      // EFI_UNSUPPORTED. Here the 'Status' is not EFI_UNSUPPORTED, so 'Prot'
      // must be not NULL.
      //
      // The ASSERT here is for addressing a false positive NULL pointer
      // dereference issue raised from static analysis.
      //
      ASSERT (Prot != NULL);
      //
      // EFI_ALREADY_STARTED is not an error for bus driver.
      // Return the corresponding protocol interface.
      //
      *Interface = Prot->Interface;
    } else if (Status == EFI_UNSUPPORTED) {
      //
      // Return NULL Interface if Unsupported Protocol.
      //
      *Interface = NULL;
    }
  }

显而易见,此时的*Interface = NULL; Status == EFI_UNSUPPORTED 就是最终的执行结果 ,向前推导:

EFI_STATUS
EFIAPI
CoreHandleProtocol (
  IN EFI_HANDLE       UserHandle,
  IN EFI_GUID         *Protocol,
  OUT VOID            **Interface
  )
{
  return CoreOpenProtocol (
          UserHandle,
          Protocol,
          Interface,
          gDxeCoreImageHandle,
          NULL,
          EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
          );

CoreHandleProtocol中的Interface返回结果为NULL 最终返回的Status == EFI_UNSUPPORTED
在往前推:

Status = CoreHandleProtocol (*UserHandle, Protocol, (VOID **)&ExistingInterface);

CoreInstallProtocolInterfaceNotify中的这句话返回状态ExistingInterface为空,Status == EFI_UNSUPPORTED (这个地方千万梳理清楚 !!!)

接下来是Prot存在的情况:
将状态更改为successful 表示成功的找到了目标的 protocol interface

 Status = EFI_SUCCESS;

接下来对找到了的Prot进行Open的操作

 ByDriver  = FALSE;
 Exclusive = FALSE;
  for ( Link = Prot->OpenList.ForwardLink; Link != &Prot->OpenList; Link = Link->ForwardLink) {
    OpenData   = CR (Link, OPEN_PROTOCOL_DATA, Link, OPEN_PROTOCOL_DATA_SIGNATURE);
    ExactMatch =  (BOOLEAN)((OpenData->AgentHandle == ImageHandle) &&
                            (OpenData->Attributes == Attributes)  &&
                            (OpenData->ControllerHandle == ControllerHandle));
    if ((OpenData->Attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) != 0) {
      ByDriver = TRUE;
      if (ExactMatch) {
        Status = EFI_ALREADY_STARTED;
        goto Done;
      }
    }

    if ((OpenData->Attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) != 0) {
      Exclusive = TRUE;
    } else if (ExactMatch) {
      OpenData->OpenCount++;
      Status = EFI_SUCCESS;
      goto Done;
    }
  }

这个部分首先是一个遍历操作,目的是对两种特殊Attribute的打开方式进行排除。
对能够找到的 OPEN_PROTOCOL_DATA的参数进行比较,如果其参数完全和输入的参数相同,则判断是否为两种特殊类型Attribute

  1. 如果符合EFI_OPEN_PROTOCOL_BY_DRIVER,那么此时就不能再次执行Open操作 直接goto Done;
  2. 如果符合EFI_OPEN_PROTOCOL_EXCLUSIVE,那么此时在OpenData->OpenCount增加一,表示再一次有人访问过这个protocol interface了 然后直接goto Done;

下面的代码仍旧以 Attribute 为标准分别进行处理


  switch (Attributes) {
    case EFI_OPEN_PROTOCOL_BY_DRIVER:
      if (Exclusive || ByDriver) {
        Status = EFI_ACCESS_DENIED;
        goto Done;
      }

      break;
    case EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE:
    case EFI_OPEN_PROTOCOL_EXCLUSIVE:
      if (Exclusive) {
        Status = EFI_ACCESS_DENIED;
        goto Done;
      }

      if (ByDriver) {
        do {
          Disconnect = FALSE;
          for (Link = Prot->OpenList.ForwardLink; Link != &Prot->OpenList; Link = Link->ForwardLink) {
            OpenData = CR (Link, OPEN_PROTOCOL_DATA, Link, OPEN_PROTOCOL_DATA_SIGNATURE);
            if ((OpenData->Attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) != 0) {
              Disconnect = TRUE;
              CoreReleaseProtocolLock ();
              Status = CoreDisconnectController (UserHandle, OpenData->AgentHandle, NULL);
              CoreAcquireProtocolLock ();
              if (EFI_ERROR (Status)) {
                Status = EFI_ACCESS_DENIED;
                goto Done;
              } else {
                break;
              }
            }
          }
        } while (Disconnect);
      }

      break;
    case EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER:
    case EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL:
    case EFI_OPEN_PROTOCOL_GET_PROTOCOL:
    case EFI_OPEN_PROTOCOL_TEST_PROTOCOL:
      break;
  }

这个位置详细的分析就不说了 主要是我也没有对EFI_OPEN_PROTOCOL_BY_DRIVER EFI_OPEN_PROTOCOL_EXCLUSIVE这两个属性深入的了解。以后有机会再说
我们之前传入函数的参数为EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL,所以这个位置对我们当前的分析没有影响。继续向下看

  if (ImageHandle == NULL) {
    Status = EFI_SUCCESS;
    goto Done;
  }

此处对于ImageHandle == NULL的情况做了特殊处理 我相信这种处理有其实际的意义,但是我还不明白

接下来就是最终的一个部分了 就是创建一个新的OpenData并插入到原来的链表中 并且将protocol-interface结构体中的OpenListCount参数进行修改
这个OpenData的数据结构是

 OpenData = AllocatePool (sizeof(OPEN_PROTOCOL_DATA));
  if (OpenData == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
  } else {
    OpenData->Signature         = OPEN_PROTOCOL_DATA_SIGNATURE;
    OpenData->AgentHandle       = ImageHandle;
    OpenData->ControllerHandle  = ControllerHandle;
    OpenData->Attributes        = Attributes;
    OpenData->OpenCount         = 1;
    InsertTailList (&Prot->OpenList, &OpenData->Link);
    Prot->OpenListCount++;
    Status = EFI_SUCCESS;
  }

综合前面说的,总结一下 看起来这个OpenData像是一个记录, 每一个OpenData都表示有不同的Controller 打开了对应的protocol-interface , OpenListCountOpenData的结构体个数应该是对应的
OpenData结构体中也有一个参数OpenCount 表示这个结构体被打开了几次了。根据代码中的情况 如果已经存在完全相同的 OpenData结构体 那么只需要将OpenData结构体中的OpenCount++ 然后退出函数 ,但是如果不存在完全相同的 ,那么就需要创建一个新的OpenData结构体 然后将OpenListCount++
那么这个OpenData就是一个记录链表 :记录了都有谁打开了这个protocol-interface 打开了几次 打开的方式等等 (具体的作用可能还需要再进一步的学习才能知道)
不要忘记我们还是需要看一下最终的返回值的情况,当前

*Interface = Prot->Interface; Status == EFI_SUCCESS 就是最终的执行结果 ,向前推导:

CoreHandleProtocol中的Interface返回结果非NULL 最终返回的Status == EFI_SUCCESS
在往前推:

Status = CoreHandleProtocol (*UserHandle, Protocol, (VOID **)&ExistingInterface);

CoreInstallProtocolInterfaceNotify中的这句话返回状态ExistingInterface非空,Status == EFI_SUCCESS


小结

总结上面两个函数:

首先CoreHandleProtocolCoreOpenProtocol 两个函数的都是查找指定的Handle是否支持目标protocol,区别就在于CoreHandleProtocol使用的参数相对简单 ,因为有几个参数固定了不需要我们再去进行设置。

在代码中我们能够具象化的说,这两个函数就是在指定的handle上,查找是否存在目标GUID的protocol-interface,而所谓的成功打开protocol,就是在已经找到的protocol-interface所带的OpenData(OpenData记录的是这次打开该protocol的controller信息)链表进行操作


前面两个函数是比较常用的函数,我觉得已经大致说的比较清楚了。接着回到CoreInstallProtocolInterfaceNotify,

  if (*UserHandle != NULL) {
    Status = CoreHandleProtocol (*UserHandle, Protocol, (VOID **)&ExistingInterface);
    if (!EFI_ERROR (Status)) {
      return EFI_INVALID_PARAMETER;
    }
  }

在这一步中,我们当前如果想要成功的安装一个新的protocol ,就必须保证之前没有存在的完全相同的 protocol,那么就意味着:ExistingInterface必须为NULL,并且 Status不能为EFI_SUCCESS。也就是CoreGetProtocolInterface时没有找到目标的protocol interface port,换言之 HANDLE-PROTOCOL-INTERFACE 链表中没有这个protocol interface

既然链表中不存在目标protocol ,接下来就是向其中安装
首先需要确定 ProtocolDatabase链表中确实存在 目标protocol GUID的protocolEntry

CoreFindProtocolEntry

  //
  // Lookup the Protocol Entry for the requested protocol
  //
  ProtEntry = CoreFindProtocolEntry (Protocol, TRUE);
  if (ProtEntry == NULL) {
    goto Done;
  }

函数的原型非常简单 还是一个链表的遍历

PROTOCOL_ENTRY  *
CoreFindProtocolEntry (
  IN EFI_GUID  *Protocol,
  IN BOOLEAN   Create
  )
{
  LIST_ENTRY      *Link;
  PROTOCOL_ENTRY  *Item;
  PROTOCOL_ENTRY  *ProtEntry;

  ASSERT_LOCKED (&gProtocolDatabaseLock);

  //
  // Search the database for the matching GUID
  //

  ProtEntry = NULL;
  for (Link = mProtocolDatabase.ForwardLink;
       Link != &mProtocolDatabase;
       Link = Link->ForwardLink)
  {
    Item = CR (Link, PROTOCOL_ENTRY, AllEntries, PROTOCOL_ENTRY_SIGNATURE);
    if (CompareGuid (&Item->ProtocolID, Protocol)) {
      //
      // This is the protocol entry
      //

      ProtEntry = Item;
      break;
    }
  }

  //
  // If the protocol entry was not found and Create is TRUE, then
  // allocate a new entry
  //
  if ((ProtEntry == NULL) && Create) {
    ProtEntry = AllocatePool (sizeof (PROTOCOL_ENTRY));

    if (ProtEntry != NULL) {
      //
      // Initialize new protocol entry structure
      //
      ProtEntry->Signature = PROTOCOL_ENTRY_SIGNATURE;
      CopyGuid ((VOID *)&ProtEntry->ProtocolID, Protocol);
      InitializeListHead (&ProtEntry->Protocols);
      InitializeListHead (&ProtEntry->Notify);

      //
      // Add it to protocol database
      //
      InsertTailList (&mProtocolDatabase, &ProtEntry->AllEntries);
    }
  }

  return ProtEntry;
}

函数很简单 不详细展开 只要注意 就算没有在当前的链表中找到GUID符合的protocol entry,函数也会创建一个然后返回 就可以了。

创建/查找完成protocol entry之后,就需要创建一个新的protocol interface了

  // 分配一个新的protocol interface 结构体 
  // Allocate a new protocol interface structure
  //
  Prot = AllocateZeroPool (sizeof (PROTOCOL_INTERFACE));
  if (Prot == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }
  //
  // If caller didn't supply a handle, allocate a new one
  //
  Handle = (IHANDLE *)*UserHandle;
  if (Handle == NULL) {
    Handle = AllocateZeroPool (sizeof (IHANDLE));
    if (Handle == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    //
    // Initialize new handler structure
    //
    Handle->Signature = EFI_HANDLE_SIGNATURE;
    InitializeListHead (&Handle->Protocols);

    //
    // Initialize the Key to show that the handle has been created/modified
    //
    gHandleDatabaseKey++;
    Handle->Key = gHandleDatabaseKey;

    //
    // Add this handle to the list global list of all handles
    // in the system
    //
    InsertTailList (&gHandleList, &Handle->AllHandles);
  } else {
    Status = CoreValidateHandle (Handle);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "InstallProtocolInterface: input handle at 0x%x is invalid\n", Handle));
      goto Done;
    }
  }

这一段代码时处理了输入的Userhandle为空的情况此时我们就分配一个新的IHANDLE结构体 ,并且将其插入 Handledatabase 链表中。

有一个位置需要关注一下就是 gHandleDatabaseKey 这个变量,我们可以在代码中发现这是一个全局变量,每次创建一个新的IHANDLE都会将其加一然后将其赋值给Handle->Key,这样就保证了key的唯一性,并且也能更加形象的解释了之前我们文章中说的

每个由唯一handle number 编号标识的 UEFI handle 由系统固件维护。 handle number为handle database中的每个entry都提供了“key”。

我们可以将结构体中的 key理解为每一个handle 的唯一编号,作为查找、区分的标志。

下面是将protocol interface进行初始化并且插入的过程

//
  // Each interface that is added must be unique
  //
  ASSERT (CoreFindProtocolInterface (Handle, Protocol, Interface) == NULL);

  //
  // Initialize the protocol interface structure
  //
  Prot->Signature = PROTOCOL_INTERFACE_SIGNATURE;
  Prot->Handle    = Handle;
  Prot->Protocol  = ProtEntry;
  Prot->Interface = Interface;

  //
  // Initalize OpenProtocol Data base
  //
  InitializeListHead (&Prot->OpenList);
  Prot->OpenListCount = 0;

  //
  // Add this protocol interface to the head of the supported
  // protocol list for this handle
  //
  InsertHeadList (&Handle->Protocols, &Prot->Link);

  //
  // Add this protocol interface to the tail of the
  // protocol entry
  //
  InsertTailList (&ProtEntry->Protocols, &Prot->ByProtocol);

以上我们可以看出 每一次protocol的插入都会新建一个对应的PROTOCOL-INTERFACE

每一次打开或者查找protocol 都是对已经存在的PROTOCOL-INTERFACE以及其成员Database的操作

这个位置需要强调的一点就是 初始化protocol interface结构体的时候,我们是将待插入的能够实现功能的目标Interface 链接到了 protocol interface 的Prot->Interface 这一位置。也就是说

当我们日常在使用InstallProtocolInterface进行所谓的安装protocol的时候,实际上是生成一个 protocol interface 并且在该interface上安装我们自己定义的能够实现功能的 protocol 实例

以前述的 driver为例:

  Status = gBS->InstallProtocolInterface (
                    &ImageHandle,
                    &gEfiHelloWorldProtocolGuid,
                    EFI_NATIVE_INTERFACE,
                    Protocol
                    );

实际上我们就是安装了一个自己定义的Protocol实例在生成的protocol interface上 ,并且这个protocol安装在了ImageHandle上,其protocol entry 对应的GUID 值为gEfiHelloWorldProtocolGuid

最后

  if (Notify) {
    CoreNotifyProtocolEntry (ProtEntry);
  }
  Status = EFI_SUCCESS;

最后notify一下当前这个protocol (这个地方用到了Notify链表以及Event的知识 前后的勾连比较多 后续学吧)

总结

至此我们详详细细的说完了InstallProtocolInterface ,其本质就是生成一个 protocol interface 并且在该interface上安装我们自己定义的能够实现功能的 protocol 实例

在install一个protocol的时候,首先需要排除当前整个链表中确实不存在待插入的protocol ,确认这一点之后 在从protocol GUID入手 查找/创建符合条件的protocol entry 。之后在创建对应的PROTOCOL-INTERFACE结构体 ,查找/创建handle结构体,然后将待插入的protocol 实例挂载到PROTOCOL-INTERFACE结构体上,将PROTOCOL-INTERFACE结构体插入整个大的链表网中,最后注册一个protocol的Event,这样才算完全将目标的protocol插入完成

在介绍这个函数的过程中,还穿插介绍了常用的两个函数 CoreOpenProtocol CoreHandleProtocol。二者都是查找Userhandle上是否存在目标protocol ,CoreHandleProtocol由于其较少的参数更为常用

下面在介绍一个常用的protocol相关的函数 基本上的常用函数都已经介绍过了

CoreLocateProtocol

从链表中中找到指定protocol的第一个实例
函数原型

// MdeModulePkg\Core\Dxe\Hand\Locate.c
EFI_STATUS
EFIAPI
CoreLocateProtocol (
  IN  EFI_GUID  *Protocol,
  IN  VOID      *Registration OPTIONAL,
  OUT VOID      **Interface
  )

首先先从ProtocolDatabase中遍历查找目标protocol GUID的 protocol entry ,如果没有找到直接返回

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

找到GUID相同的protocol后,遍历protocol-interface链表 找到对应的 Handle->LocateRequest符合标准的直接返回当前对应的interface 接口

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

  Handle      = NULL;
  *Interface  = NULL;
  for (; ;) {
    //
    // Next entry
    //
    Link = Position->Position->ForwardLink;
    Position->Position = Link;

    //
    // If not at the end, return the handle
    //
    if (Link == &Position->ProtEntry->Protocols) {
      Handle = NULL;
      break;
    }

    //
    // Get the handle
    //
    Prot = CR(Link, PROTOCOL_INTERFACE, ByProtocol, PROTOCOL_INTERFACE_SIGNATURE);
    Handle = Prot->Handle;
    *Interface = Prot->Interface;

    //
    // If this handle has not been returned this request, then
    // return it now
    //
    if (Handle->LocateRequest != mEfiLocateHandleRequest) {
      Handle->LocateRequest = mEfiLocateHandleRequest;
      break;
    }
  }

  return Handle;
}

可以看到 通过这个函数我们找到的是 PROTOCOL_INTERFACE结构体成员Interface这个指针指向的东西 那具体这个位置指向的是什么 在前述就有说过 实例

小结

CoreOpenProtocol 根据给定的条件(最详细条件) 找到interface指向的东西

CoreHandleProtocol 根据给定的条件(部分省略条件) 找到interface指向的东西

CoreLocateProtocol 根据给定的protocol GUID 找到第一个符合条件的protocol-interface 然后 找到interface指向的东西

本篇文章都是按照我自己学习的顺序来写的 而且和前面写过的protocol handle联系紧密 之前的那一张大图对于理解这些函数非常有用。现在初步了解了protocol 的安装,driver的编写之后,下一步就需要更深入的思考,application 和 driver的区别是什么? 我们一直提到的 image是什么? UEFI是怎么找到image的

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值