string

string
posts - 26, comments - 176, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

UEFI 实战(4) protocol

Posted on 2012-03-06 07:30 djx_zh 阅读(12531) 评论(18)  编辑 收藏 引用
什么是protocol
从字面意思上看,protocol是server和client之间的一种约定,双方根据这种约定互通信息。这里的server和client是一种广义的称呼,提供服务的称为server,使用服务的称为client。 TCP是一种protocol, client(应用程序)通过一组函数来压包和解包,压包和解包是server提供的服务。COM也是一种protocol,client通过CoCreateInstance(...)和GUID获得指向COM对象的指针,然后使用该指针获得COM对象提供的服务, GUID标示了这个COM对象。现在我们对protocol有了概念上的理解,那么具体到UEFI里,protocol是什么样子呢? 如何标示一个protocol?如何得到protocol对应的对象?...容我慢慢道来.
在讲protocol什么样子之前,还要插几句C与C++的区别。我们知道UEFI是用C来开发的,C是面向过程的一种语言。而管理和使用UEFI众多的protocol完全使用面向过程的思想会使程序变得复杂。protocol作为一种对象来设计管理会比较直观。因而UEFI中的Protocol引入了面向对象的思想,用struct来模拟class, Protocol用struct来实现,用函数指针(Protocol的成员变量)模拟成员函数,此种函数的第一参数必须是指向Protocol的指针,用来模拟this指针。
Protocol的摸样
以EFI_DISKIO_PROTOCOL 来看看Protocol的样子。 
MdePkg/Include/Protocol/BlockIo.h
:
220
///
///  This protocol provides control over block devices.
///
struct _EFI_BLOCK_IO_PROTOCOL {
  
///
  
/// The revision to which the block IO interface adheres. All future
  
/// revisions must be backwards compatible. If a future version is not
  
/// back wards compatible, it is not the same GUID.
  
///
  UINT64              Revision;
  
///
  
/// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
  
///
  EFI_BLOCK_IO_MEDIA  *Media;

  EFI_BLOCK_RESET     Reset;
  EFI_BLOCK_READ      ReadBlocks;
  EFI_BLOCK_WRITE     WriteBlocks;
  EFI_BLOCK_FLUSH     FlushBlocks;

};

extern EFI_GUID gEfiBlockIoProtocolGuid;
MdePkg/Include/Protocol/BlockIo.h
:
220
#define EFI_BLOCK_IO_PROTOCOL_GUID \
  { \
    
0x964e5b210x64590x11d2, {0x8e0x390x00xa00xc90x690x720x3b } \
  }

typedef 
struct _EFI_BLOCK_IO_PROTOCOL  EFI_BLOCK_IO_PROTOCOL;
EFI_BLOCK_IO_PROTOCOL 有两个成员变量,四个成员函数(当然从C的角度来看,“成员函数”叫法不准确,它实际上也是一个成员变量,只是这个变量是函数指针).  gEfiBlockIoProtocolGuid({0x964e5b210x64590x11d2, {0x8e0x390x00xa00xc90x690x720x3b })标示了EFI_BLOCK_IO_PROTOCOL 。
来看成员函数的声明

/**
  Read BufferSize bytes from Lba into Buffer.

  @param  This       Indicates a pointer to the calling context.
  @param  MediaId    Id of the media, changes every time the media is replaced.
  @param  Lba        The starting Logical Block Address to read from
  @param  BufferSize Size of Buffer, must be a multiple of device block size.
  @param  Buffer     A pointer to the destination buffer for the data. The caller is
                     responsible for either having implicit or explicit ownership of the buffer.

  @retval EFI_SUCCESS           The data was read correctly from the device.
  @retval EFI_DEVICE_ERROR      The device reported an error while performing the read.
  @retval EFI_NO_MEDIA          There is no media in the device.
  @retval EFI_MEDIA_CHANGED     The MediaId does not matched the current device.
  @retval EFI_BAD_BUFFER_SIZE   The Buffer was not a multiple of the block size of the device.
  @retval EFI_INVALID_PARAMETER The read request contains LBAs that are not valid,
                                or the buffer is not on proper alignment.

*
*/
typedef
EFI_STATUS
(EFIAPI 
*EFI_BLOCK_READ)(
  IN EFI_BLOCK_IO_PROTOCOL          
*This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  OUT VOID                          
*Buffer
  );
EFI_BLOCK_READ具体用法我们先不看,我们来看它的第一个参数,指向EFI_BLOCK_IO_PROTOCOL  对象自己的this指针,这是成员函数区别于一般函数的重要特征。

如何使用Protocol       

使用Protocol之前,我们要弄清楚Protocol位于什么地方。首先我们要来认识一下EFI_HANDLE,
///
/// A collection of related interfaces.
///
typedef VOID                      *EFI_HANDLE;
EFI_HANDLE是指向某种对象的指针,UEFI用它来表示某个对象。 UEFI扫描总线后,会为每个设备建立一个Controller对象,用于控制设备,所有该设备的驱动以protocol的形式安装到这个controller中,这个Controller就是一个EFI_HANDLE对象。 当我们将一个.efi文件加载到内存中,UEFI也会为该文件建立一个Image对象(此Image非图像的意识), 这个Image对象也是一个EFI_HANDLE对象。 在UEFI内部,EFI_HANDLE被理解为IHANDLE
///
/// IHANDLE - contains a list of protocol handles
///
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;
每个IHANDLE中都有一个Protocols链表,存放属于自己的protocol。所有的IHANDLE通过AllHandles链接起来。
要使用Protocol,首先要找到protocol对象,可以通过BootServices的OpenProtocol(...), HandleProtocl(...), LocateProtocol(...)获得。
typedef
/**
  Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the
  handle, it opens the protocol on behalf of the calling agent.
  @param  Handle                The handle for the protocol interface that is being opened.
  @param  Protocol              The published unique identifier of the protocol.
  @param  Interface             Supplies the address where a pointer to the corresponding Protocol
                                        Interface is returned.
  @param  AgentHandle        The handle of the agent that is opening the protocol interface
                                        specified by Protocol and Interface.
  @param  ControllerHandle    If the agent that is opening a protocol is a driver that follows the
                                        UEFI Driver Model, then this parameter is the controller handle
                                        that requires the protocol interface. If the agent does not follow
                                        the UEFI Driver Model, then this parameter is optional and may
                                        be NULL.
  @param  Attributes            The open mode of the protocol interface specified by Handle
                                        and Protocol.
  @retval EFI_SUCCESS         An item was added to the open list for the protocol interface, and the
                                        protocol interface was returned in Interface.
  @retval EFI_UNSUPPORTED       Handle does not support Protocol.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
  @retval EFI_ACCESS_DENIED     Required attributes can't be supported in current environment.
  @retval EFI_ALREADY_STARTED   Item on the open list already has requierd attributes whose agent
                                                handle is the same as AgentHandle.
**/
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL)(
  IN  EFI_HANDLE                Handle,
  IN  EFI_GUID                   *Protocol,
  OUT VOID                       **Interface, OPTIONAL
  IN  EFI_HANDLE                AgentHandle,
  IN  EFI_HANDLE                ControllerHandle,
  IN  UINT32                      Attributes
  );
Handle是Protocol的提供者,如果Handle的Protocols链表中有该Potocol,Protocol对象的指针写到*Interface,并返回EFI_SUCCESS;否则 返回EFI_UNSUPPORTED
如果在驱动中调用OpenProtocol(), AgentHandle是拥有该EFI_DRIVER_BINDING_PROTOCOL对象的Handle;ControllerHandle是拥有该驱动的Controller。
如果调用OpenProtocol的是应用程序,那么AgentHandle是该应用对应的Handle,也就main函数的第一个参数。 
ControllerHandle此时可以忽略。
Attributes可以取以下5种值。
#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
#define EFI_OPEN_PROTOCOL_EXCLUSIVE                   0x00000020

HandleProtocol是OpenProtocol的简化版,因为大部分情况下我们都不需要关心AgentHandle,ControllerHandle和Attributes。
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
          );
}
LocateProtocol(...)是从内核中找出指定Protocol的第一个实例。
typedef
EFI_STATUS
LocateProtocol (
IN EFI_GUID 
*Protocol,
IN VOID       
*Registration OPTIONAL,
OUT VOID     
**Interface
);
UEFI内核中某个Protocol的实例可能不止一个,例如每个硬盘及每个分区都有一个EFI_DISK_IO_PROTOCOL实例。LocateProtocol顺序搜索HANDLE链表,返回找到的第一个该Protocol的实例。
我们可以用BootServices提供的其它函数处理HANDLE和Protocol。
typedef
EFI_STATUS
LocateHandleBuffer (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID                         
*Protocol OPTIONAL,
IN VOID
                              
 
*SearchKey OPTIONAL,
IN OUT UINTN                       
*NoHandles,
OUT EFI_HANDLE                  
**Buffer
);
可以获得所有支持指定Protocol的HANDLE,SearchType 有三种:AllHandles(查找所有HANDLE), ByRegisterNotify, ByProtocol(查找支持指定Protocol的HANDLE)。NoHandles是找到的HANDLE的数量, Buffer数组由UEFI复杂分配,由用户负责释放。
typedef
EFI_STATUS
LocateHandle (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID                        
*Protocol OPTIONAL,
IN VOID                              
*SearchKey OPTIONAL,
IN OUT UINTN                      
*BufferSize,
OUT EFI_HANDLE                  
*Buffer
);
与LocateHandleBuffer相似,只是用户负责分配和释放Buffer数组。
typedef
EFI_STATUS
ProtocolsPerHandle (
IN EFI_HANDLE Handle,
OUT EFI_GUID  
***ProtocolBuffer,
OUT UINTN      
*ProtocolBufferCount
);
获得指定Handle所支持的所有Protocol, UEFI负责分配内存给ProtocolBuffer,用户负责释放该内存。

typedef
EFI_STATUS
(EFIAPI 
*EFI_OPEN_PROTOCOL_INFORMATION) (
IN EFI_HANDLE Handle,
IN EFI_GUID    
*Protocol,
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY 
**EntryBuffer,
OUT UINTN     
*EntryCount
);
typedef struct {
  EFI_HANDLE  AgentHandle;
  EFI_HANDLE  ControllerHandle;
  UINT32        Attributes;
  UINT32        OpenCount;
} EFI_OPEN_PROTOCOL_INFORMATION_ENTRY;
OpenProtocolInformation()获得指定Handle中指定Protocol的打开信息。
SPEC2.3.1第165页有很好的例子演示了怎么打开一个Protocol, 
EFI_BOOT_SERVICES *gBS;
EFI_HANDLE ImageHandle;
EFI_DRIVER_BINDING_PROTOCOL 
*This;
IN EFI_HANDLE ControllerHandle,
extern EFI_GUID gEfiXyzIoProtocol;
EFI_XYZ_IO_PROTOCOL 
*XyzIo;
EFI_STATUS Status;

Status 
= gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);

Status 
= gBS->OpenProtocol (
ControllerHandle,
&gEfiXyzIoProtocol,
&XyzIo,
This
->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
打开Protocol之后就可以使用了,最后要通过CloseProtocol关闭打开的Protocol。
typedef
EFI_STATUS
(EFIAPI 
*EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID 
*Protocol,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle
);
通过HandleProtocol和LocateProtocol打开的Protocol因为没有指定AgentHandle,所以无法关闭。如果一定要去关闭它,要调用OpenProtocolInformation()获得AgentHandle和ControllerHandle,然后关闭它。
下面看一个完整的例子,用EFI_DISK_IO_PROTOCOL读取GPT硬盘的分区表
#include <Uefi.h> 
#include <Base.h> 
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Protocol/DiskIo.h> 
#include <Protocol/BlockIo.h> 
#include <Protocol/DevicePath.h>    
#include <Uefi/UefiGpt.h>
#include <Library/DevicePathLib.h>
EFI_STATUS
EFIAPI
UefiMain(
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        EFI_STATUS                              Status;
        UINTN                                       HandleIndex, HandleCount;
        EFI_HANDLE                               *DiskControllerHandles = NULL;
        EFI_DISK_IO_PROTOCOL               *DiskIo;

        /*找到所有提供 EFI_DISK_IO_PROTOCOL 的Controller  */
        Status = gBS->LocateHandleBuffer(
                        ByProtocol,
                        &gEfiDiskIoProtocolGuid,
                        NULL,
                        &HandleCount,
                        &DiskControllerHandles);

        if (!EFI_ERROR(Status)) {
                CHAR8 gptHeaderBuf[512];
                EFI_PARTITION_TABLE_HEADER* gptHeader = (EFI_PARTITION_TABLE_HEADER*
)gpHeaderBuf;
                for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
                       /*打开EFI_DISK_IO_PROTOCOL  */ 
                       Status = gBS->HandleProtocol(
                                        DiskControllerHandles[HandleIndex],
                                        &gEfiDiskIoProtocolGuid,
                                        (VOID**)&DiskIo);

                        if (!EFI_ERROR(Status)){
                                {
                                        EFI_DEVICE_PATH_PROTOCOL                 *DiskDevicePath;
                                        EFI_DEVICE_PATH_TO_TEXT_PROTOCOL   *Device2TextProtocol = 0;
                                        CHAR16*                                             TextDevicePath = 0;
                                          /*1. 打开EFI_DEVICE_PATH_PROTOCOL  */  
                                        Status = gBS->OpenProtocol(
                                                        DiskControllerHandles[HandleIndex],
                                                        &gEfiDevicePathProtocolGuid,
                                                        (VOID**)&DiskDevicePath,
                                                        ImageHandle,
                                                        NULL,
                                                        EFI_OPEN_PROTOCOL_GET_PROTOCOL
                                                        );
                                        if(!EFI_ERROR(Status)){
                                                if(Device2TextProtocol == 0)
                                                        Status = gBS->LocateProtocol(
                                                                        &gEfiDevicePathToTextProtocolGuid,
                                                                        NULL,
                                                                        (VOID**)&Device2TextProtocol
                                                                        );
                                                /*2. 使用 EFI_DEVICE_PATH_PROTOCOL  得到文本格式的Device Path  */  
                                                TextDevicePath = Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, TRUE, TRUE);
                                                Print(L"%s\n", TextDevicePath);
                                                if(TextDevicePath)gBS->FreePool(TextDevicePath);
                                                /*3. 关闭 EFI_DEVICE_PATH_PROTOCO */   
                                                Status = gBS->CloseProtocol(
                                                                DiskControllerHandles[HandleIndex],
                                                                &gEfiDevicePathProtocolGuid,
                                                                ImageHandle,
                                                                );
                                        }
                                }
                                {
                                        EFI_BLOCK_IO_PROTOCOL* BlockIo = *(EFI_BLOCK_IO_PROTOCOL**) (DiskIo + 1);
                                        EFI_BLOCK_IO_MEDIA* Media = BlockIo->Media;
                                        /*读1号扇区。  */   
                                        Status = DiskIo->ReadDisk(DiskIo, Media->MediaId, 512, 512, gptHeader);
                                        /*检查GPT标志。  */    
                                        if((!EFI_ERROR(Status)) &&( gptHeader -> Header.Signature == 0x5452415020494645)){
                                                UINT32 CRCsum;
                                                UINT32 GPTHeaderCRCsum =  (gptHeader->Header.CRC32);
                                                gptHeader->Header.CRC32 = 0;
                                                gBS -> CalculateCrc32(gptHeader , (gptHeader->Header.HeaderSize), &CRCsum);
                                                if(GPTHeaderCRCsum == CRCsum){
                                                // Find out a GPT Header
                                                }

                                        }

                                }

                        }

                }
                gBS->FreePool(DiskControllerHandles);
        }
}
 









Feedback

# re: UEFI 实战(4) protocol   回复  更多评论   

2012-08-13 13:55 by 刘炜
EFI_BLOCK_IO_PROTOCOL* BlockIo = *(EFI_BLOCK_IO_PROTOCOL**) (DiskIo + 1);

这是说明两个协议的数据结构连续地放在一起吗?

# re: UEFI 实战(4) protocol [未登录]  回复  更多评论   

2012-08-15 22:02 by djx_zh
@刘炜
是的。
MdeModulePkg\Universal\Disk\DiskIoDxe\DiskIo.h:36
typedef struct {
UINTN Signature;
EFI_DISK_IO_PROTOCOL DiskIo;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
} DISK_IO_PRIVATE_DATA

# re: UEFI 实战(4) protocol   回复  更多评论   

2012-10-04 16:29 by 关于 gptHeader-> Header.Signature
你好 我接触 UFEI 不长时间
有个问题
CHAR8 gptHeader[512];
gptHeader-> Header.Signature
这样写可以吗?
gptHeader 应该是个 struct吧
还有能否把inf文件也发上来呢 谢谢了

# re: UEFI 实战(4) protocol   回复  更多评论   

2012-10-04 17:44 by snowman1101
你好
我复制了你的代码
编译后提示 未声明 DevicePathToText 于是我加入了
#include <Protocol/DevicePathToText.h>
修改所有错误以后
又说 外部 gEfiDevicePathToTextProtocolGuid
请问怎么解决呢。可以发一下完整代码吗
谢谢了

# re: UEFI 实战(4) protocol   回复  更多评论   

2012-10-05 08:37 by djx_zh
代码中少了一句
EFI_PARTITION_TABLE_HEADER* gptHeader = (EFI_PARTITION_TABLE_HEADER*
)gpHeaderBuf;

在你.inf文件的[Protocols]下面加上
gEfiDevicePathToTextProtocolGuid

# re: UEFI 实战(4) protocol [未登录]  回复  更多评论   

2012-10-06 22:06 by snowman1101
多谢指教,已经可以了


# re: UEFI 实战(4) protocol [未登录]  回复  更多评论   

2012-11-15 11:47 by ktfun
亲,find out a GPT header 后面的内容呢?

# re: UEFI 实战(4) protocol   回复  更多评论   

2013-01-07 17:43 by linhao
关于:EFI_BLOCK_IO_PROTOCOL* BlockIo = *(EFI_BLOCK_IO_PROTOCOL**) (DiskIo + 1);
我认为这样更方便理解:
DISK_IO_PRIVATE_DATA *Private_Data;
Private_Data = DISK_IO_PRIVATE_DATA_FROM_THIS (DiskIo);
BlockIo = Private_Data;

# re: UEFI 实战(4) protocol   回复  更多评论   

2013-01-07 17:45 by linhao
修改:
BlockIo = Private_Data->BlockIo;

# re: UEFI 实战(4) protocol   回复  更多评论   

2013-01-07 22:12 by djxzh
@linhao
是的。如果你看DiskIO的源码会发现EDK2就是这样做的。

# re: UEFI 实战(4) protocol   回复  更多评论   

2013-02-28 06:44 by howard
hi, 请问如何选择特定的device进行读写?我想parse EFI_DEVICE_PATH_PROTOCOL 的bus id 部分,然后找到目标进行读写,但貌似没有合适的API可以用

Thanks in advance!

# re: UEFI 实战(4) protocol   回复  更多评论   

2013-03-06 06:08 by djxzh
@howard

EFI_DEVICE_PATH_PROTOCOL 是一个办法, 确实没有顺手的API, 你要一个node一个node的分析。

也可以用PciIo读取PCI设备的配置空间,来判断设备。

# re: UEFI 实战(4) protocol   回复  更多评论   

2013-03-08 03:29 by howard
恩,我现在暂时用capacity来找我的device. 不知楼主用过EADK吗,把C标准库加到EDKII里。 我试了一下,build出来的efi放在shell那一跑就crash with exception, sample efi也一样问题。。

# re: UEFI 实战(4) protocol   回复  更多评论   

2014-12-27 03:54 by asam
【UEFI是用C来开发的,C是面向过程的一种语言。而管理和使用UEFI众多的protocol完全使用面向过程的思想会使程序变得复杂。protocol作为一种对象来设计管理会比较直观。因而UEFI中的Protocol引入了面向对象的思想,用struct来模拟class, Protocol用struct来实现,用函数指针(Protocol的成员变量)模拟成员函数,此种函数的第一参数必须是指向Protocol的指针,用来模拟this指针。。。。。。】



怪不得,看了hello world,没有消息机制。特别是和硬件打交道,对这个uefi表示亚历山大。

# re: UEFI 实战(4) protocol   回复  更多评论   

2015-05-29 15:07 by 周彬彬
@djx_zh
这个在EDK2 SPE2.4的MdeModulePkg\Universal\Disk\DiskIoDxe\DiskIo.h
这样定义的:二者并没有相邻
#define DISK_IO_PRIVATE_DATA_SIGNATURE SIGNATURE_32 ('d', 's', 'k', 'I')
typedef struct {
UINT32 Signature;

EFI_DISK_IO_PROTOCOL DiskIo;
EFI_DISK_IO2_PROTOCOL DiskIo2;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_BLOCK_IO2_PROTOCOL *BlockIo2;

UINT8 *SharedWorkingBuffer;

EFI_LOCK TaskQueueLock;
LIST_ENTRY TaskQueue;
} DISK_IO_PRIVATE_DATA;

# re: UEFI 实战(4) protocol   回复  更多评论   

2015-06-01 04:38 by djx_zh
@周彬彬
对。本文中的方法依赖于 DISK_IO_PRIVATE_DATA的实现,而这个实现没有一个标准,故不能保证向前兼容。这不是一个安全的方法,只是一个快速的方法。安全的方法还是调用OpenPrototocol或LocateProtocol。

# re: UEFI 实战(4) protocol   回复  更多评论   

2015-06-02 19:58 by 夏涛
怎么添加关注的啊?

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理