首页 > 其他分享 >UEFI中的PassThru()

UEFI中的PassThru()

时间:2024-07-15 15:30:15浏览次数:13  
标签:PassThru 驱动程序 ATA EFI UEFI PASS THRU

UEFI中的PassThru()

最开始的时候,我是学习SATA-AHCI协议探索EDK2的源码,而在ATA中,PassThru 函数是实现ATA通过协议时最重要的功能,它执行以下操作:

  • 初始化内部寄存器以进行命令/数据传输。
  • 将有效的ATA命令放到特定于硬件的内存或寄存器位置。
  • 启动传输。
  • 可选择等待执行的完成。

该函数中更好的错误处理机制有助于开发一个更健壮的驱动程序。尽管大多数ATA主机控制器同时支持阻塞和非阻塞数据传输,但有些控制器可能只支持阻塞传输。

ReadUsingNCQ示例

这里演示如何使用NCQ(Native Command Queuing)执行ATA读操作的过程,我们需要理解NCQ的工作原理。NCQ允许硬盘驱动器内部重新排序命令执行顺序,以提高存储设备的性能和效率。以下是使用EFI_ATA_PASS_THRU_PROTOCOL发送NCQ读命令的简化示例。请注意,因为这是一个高度简化的示例,实际应用中可能需要更多的错误检查和设备初始化步骤。

首先,假设我们已经有了一个有效的EFI_ATA_PASS_THRU_PROTOCOL实例,称为AtaPassThru,以及我们想要通信的ATA设备的端口号和端口乘数位置(对于不使用端口乘数的情况,端口乘数位置通常为0xFFFF)。

#include <Uefi.h>
#include <Protocol/AtaPassThru.h>

EFI_STATUS ReadUsingNCQ(
    EFI_ATA_PASS_THRU_PROTOCOL *AtaPassThru, 
    UINT16 Port, 
    UINT16 PortMultiplier, 
    UINT64 Lba, 
    UINT32 SectorCount, 
    VOID *Buffer
)
{
    EFI_ATA_COMMAND_BLOCK Acb = {0};
    EFI_ATA_STATUS_BLOCK Asb = {0};
    EFI_ATA_PASS_THRU_COMMAND_PACKET Packet = {0};
    EFI_STATUS Status;

    // 填充命令块
    Acb.AtaCommand = 0x60; // READ FPDMA QUEUED
    Acb.AtaSectorNumber = (UINT8)(Lba & 0xFF);
    Acb.AtaCylinderLow = (UINT8)((Lba >> 8) & 0xFF);
    Acb.AtaCylinderHigh = (UINT8)((Lba >> 16) & 0xFF);
    Acb.AtaDeviceHead = (UINT8)(0x40 | ((Lba >> 24) & 0x0F)); // LBA模式
    Acb.AtaSectorCount = (UINT8)(SectorCount & 0xFF); // 仅使用SectorCount的低字节

    // 准备命令包
    Packet.Protocol = EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_IN;
    Packet.Length = EFI_ATA_PASS_THRU_LENGTH_BYTES | EFI_ATA_PASS_THRU_LENGTH_SECTOR_COUNT;
    Packet.Acb = &Acb;
    Packet.Asb = &Asb;
    Packet.Timeout = 3000; // 3秒超时
    Packet.InDataBuffer = Buffer;
    Packet.InTransferLength = SectorCount * 512; // 假设每个扇区512字节

    // 发送命令
    Status = AtaPassThru->PassThru(AtaPassThru, Port, PortMultiplier, &Packet, NULL);

    return Status;
}

在这个示例中,我们首先填充了一个EFI_ATA_COMMAND_BLOCK结构,指定了NCQ读命令(命令码0x60)和目标LBA地址。然后,我们创建了一个EFI_ATA_PASS_THRU_COMMAND_PACKET结构,指定了数据传输的方向、数据和命令的长度、以及指向命令和状态块的指针。最后,我们通过调用AtaPassThru->PassThru函数发送命令。

在UEFI规范中,EFI_ATA_PASS_THRU_PROTOCOL用于提供一个标准方式来发送ATA命令到ATA设备。这个协议中使用了几个结构体来封装命令的细节。下面是对EFI_ATA_COMMAND_BLOCK (Acb)、EFI_ATA_STATUS_BLOCK (Asb)、EFI_ATA_PASS_THRU_COMMAND_PACKET (Packet)以及LBA的介绍。

EFI_ATA_COMMAND_BLOCK (Acb)

这个结构体包含了发送到ATA设备的命令的具体细节。它直接映射到ATA命令的结构,包括命令码、LBA地址、扇区数等。这些字段直接对应于ATA命令协议。

  • **AtaCommand: **ATA命令码,比如读命令0x25代表读取扇区。
  • **AtaFeatures: **用于指定命令的特定功能。
  • **AtaSectorNumber: **LBA的低8位。
  • AtaCylinderLow 和 AtaCylinderHigh: 这两个字段共同与AtaSectorNumberAtaDeviceHead一起定义了28位或48位的LBA地址。
  • AtaDeviceHead: 包含了设备/头选择位。
  • **AtaSectorCount: **指定要操作的扇区数。
EFI_ATA_STATUS_BLOCK (Asb)

这个结构体包含了执行ATA命令后的状态信息。它包括了错误码和状态码,可以用来判断命令是否成功执行。

  • **AtaStatus: **包含了设备的状态信息,比如命令是否成功完成。
  • **AtaError: **如果AtaStatus指示命令执行出错,这个字段包含错误的具体信息。
EFI_ATA_PASS_THRU_COMMAND_PACKET (Packet)

这个结构体封装了一个ATA命令的所有信息,包括命令本身、数据传输的方向、超时时间等。它是EFI_ATA_PASS_THRU_PROTOCOL中用于发送命令的主要数据结构。

  • **Timeout: **命令执行的超时时间,以100纳秒单位表示。
  • **Protocol: **指定数据传输协议,如PIO或DMA。
  • **Length: **指定Acb、Asb和数据缓冲区的长度。
  • **Acb: **指向EFI_ATA_COMMAND_BLOCK的指针,定义了要发送的命令。
  • **Asb: **指向EFI_ATA_STATUS_BLOCK的指针,接收命令执行的状态。
  • **InDataBuffer/OutDataBuffer: **数据传输的缓冲区。
  • **InTransferLength/OutTransferLength: **数据传输的长度。
LBA (Logical Block Addressing)

LBA是一种硬盘寻址方式,它允许系统以线性方式寻址硬盘上的扇区,而不是传统的柱面-磁头-扇区(CHS)寻址方式。LBA使得操作系统和应用程序不需要知道硬盘的物理几何结构。在ATA命令中,LBA用来指定要读写的数据的位置。

  • LBA地址通常为28位或48位,允许访问的存储容量远大于CHS寻址方式。
AllocateAlignedPages()// 分配对齐的内存页
AllocateAlignedPages(EFI_SIZE_TO_PAGES(sizeof(EFI_ATA_PASS_THRU_COMMAND_PACKET));

EFI_SIZE_TO_PAGES(sizeof(EFI_ATA_PASS_THRU_COMMAND_PACKET)):
EFI_SIZE_TO_PAGES 是一个宏,用于将字节数转换为页数。
sizeof(EFI_ATA_PASS_THRU_COMMAND_PACKET) 计算 EFI_ATA_PASS_THRU_COMMAND_PACKET 结构的大小(以字节为单位)。
该宏会根据系统页大小(通常为4KB)计算需要多少页来容纳这个结构。
AllocateAlignedPages:
这是一个用于分配对齐的内存页的UEFI函数。
这个函数通常有两个参数:页数和对齐要求。

Passthru()源码到底在哪?

在刚开始学习EDK2时,不理解驱动的加载和启动过程,想要找到相关协议的具体流程也十分困难。因为最近在学习Nvme相关协议,因此更以Nvme为例探究一下。
在NvmExpressPassthru.h中存在与ATA类似的EFI_NVM_EXPRESS_PASS_THRU_PASSTHRU
它的功能为向 NVM Express 控制器或命名空间发送 NVM Express 命令包。该功能支持阻塞 I/O 和非阻塞 I/O。阻塞 I/O 功能是必需的,而非阻塞 I/O 功能是可选的。

typedef
EFI_STATUS
(EFIAPI *EFI_NVM_EXPRESS_PASS_THRU_PASSTHRU)(
  IN     EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL          *This,
  IN     UINT32                                      NamespaceId,
  IN OUT EFI_NVM_EXPRESS_PASS_THRU_COMMAND_PACKET    *Packet,
  IN     EFI_EVENT                                   Event OPTIONAL
  );
  1. 这个typedef定义了一个函数指针类型,名为EFI_NVM_EXPRESS_PASS_THRU_PASSTHRU。
  2. 这个函数指针类型的签名(参数列表和返回类型)与NvmExpressPassThru()函数完全匹配。
  3. 在UEFI驱动模型中,这种typedef通常用于定义协议接口的函数。
  4. EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL结构体中会有一个这种类型的成员,通常命名为PassThru。
  5. 当驱动程序初始化时,它会将NvmExpressPassThru函数的地址赋值给协议实例的PassThru成员。
    所以,它的实现其实是在NvmExpressPassthru.c中的NvmExpressPassThru函数,在驱动程序的某块地方是实现了绑定的。
    例如,在NvmExpress.c中的NvmExpressDriverBindingStart函数实现中存在如下
EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL  *NvmePassThru;

// ... 初始化代码 ...

NvmePassThru->PassThru = NvmExpressPassThru;

如此,当其他组件通过协议接口调用PassThru函数时,实际上就是在调用NvmExpressPassThru函数。
这种设计模式允许UEFI在不知道具体实现的情况下,通过统一的接口调用特定驱动程序的功能。它是UEFI驱动程序模型中实现多态性和模块化的关键机制之一。
那么进一步 NvmExpressDriverBindingStart 函数又在什么时候使用了呢?在NvmExpress.c的最开头有

EFI_DRIVER_BINDING_PROTOCOL gNvmExpressDriverBinding = {
    NvmExpressDriverBindingSupported,
    NvmExpressDriverBindingStart,
    NvmExpressDriverBindingStop,
    0x10,
    NULL,
    NULL
    };

这段代码定义了一个EFI_DRIVER_BINDING_PROTOCOL结构体实例,名为gNvmExpressDriverBinding。这是UEFI驱动程序模型中的一个关键组件。它的结构和用途:

  1. 结构解释:
    • NvmExpressDriverBindingSupported: 用于检测驱动程序是否支持特定设备的函数。
    • NvmExpressDriverBindingStart: 用于启动驱动程序管理设备的函数。
    • NvmExpressDriverBindingStop: 用于停止驱动程序管理设备的函数。
    • 0x10: 驱动程序的版本号。
    • NULL, NULL: 预留字段,通常设为NULL。
  2. 使用时机:
    • 驱动程序加载:当UEFI固件加载驱动程序时,它会查找并使用这个结构。
    • 设备检测:系统会调用Supported函数来确定驱动程序是否支持某个设备。
    • 驱动程序启动:如果Supported返回成功,系统会调用Start函数来初始化设备。
    • 驱动程序停止:当需要卸载驱动程序或重新配置设备时,会调用Stop函数。
  3. 实际应用:
    • 在驱动程序的入口点函数中,通常会安装这个协议:
EFI_STATUS
EFIAPI
NvmExpressDriverEntry (
    IN EFI_HANDLE        ImageHandle,
    IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
    return EfiLibInstallDriverBindingComponentName2 (
        ImageHandle,
        SystemTable,
        &gNvmExpressDriverBinding,
        ImageHandle,
        &gNvmExpressComponentName,
        &gNvmExpressComponentName2
    );
}
  1. 工作流程:
    • UEFI固件加载驱动程序并调用其入口点函数。
    • 入口点函数安装DriverBinding协议。
    • 当系统检测到新设备时,它会遍历所有已安装的DriverBinding协议。
    • 对每个协议,系统调用Supported函数检查兼容性。
    • 如果Supported返回成功,系统调用Start函数初始化设备。
    • 如果需要停止驱动程序管理设备,系统调用Stop函数。

这个结构是UEFI驱动程序与系统其他部分交互的主要接口,它定义了驱动程序的生命周期管理方法。通过这个机制,UEFI可以动态地加载、启动和停止设备驱动程序,实现了高度的模块化和灵活性。

而在EDK2的代码中,我并没有找到NvmExpressDriverEntry函数的显示调用,为什么它能够生效?我一开始猜想是不是在 gBS->LocateHandleBuffer的时候底层有实现,然而查询资料后发现并不是。

实际上,NvmExpressDriverEntry 函数通常不会在代码中被显式调用。这是因为它是驱动程序的入口点函数,由 UEFI 固件自动调用。解释一下这个过程:

  1. 驱动程序加载: 当 UEFI 固件加载驱动程序时(通常是在系统启动过程中),它会自动调用驱动程序的入口点函数,即 NvmExpressDriverEntry。
  2. 入口点函数的作用: NvmExpressDriverEntry 函数通常会安装 DriverBinding 协议和其他必要的协议(如 ComponentName)。
  3. DriverBinding 协议的安装: 入口点函数安装 DriverBinding 协议后,UEFI 固件就能够通过这个协议与驱动程序交互。
  4. gBS->LocateHandleBuffer 的作用: 这个函数不是用来调用 NvmExpressDriverEntry 的。它通常用于查找已安装的协议或设备句柄。在驱动程序的上下文中,它可能用于:
    • 查找支持特定协议的设备句柄
    • 检测系统中是否存在某种特定类型的设备
  5. 实际调用过程:
    • 系统启动时,UEFI 固件加载驱动程序并调用 NvmExpressDriverEntry
    • NvmExpressDriverEntry 安装 DriverBinding 协议
    • 当系统需要管理 NVMe 设备时,它会通过已安装的 DriverBinding 协议找到并使用 NVMe 驱动程序
  6. 为什么看不到显式调用: 因为入口点函数的调用是由 UEFI 固件自动处理的,所以在驱动程序的源代码中通常看不到对它的直接调用。
  7. 编译和链接: 在编译和链接过程中,驱动程序的入口点会被正确地设置,使得 UEFI 固件能够找到并调用它。

总之,NvmExpressDriverEntry 函数是驱动程序与 UEFI 系统集成的关键点,但它的调用是由系统自动处理的,而不是在代码中显式调用的。gBS->LocateHandleBuffer 是用于其他目的的 UEFI 引导服务函数,不直接涉及驱动程序入口点的调用。

标签:PassThru,驱动程序,ATA,EFI,UEFI,PASS,THRU
From: https://blog.csdn.net/Kev1nnn/article/details/140439365

相关文章

  • BIOS和UEFI
    BIOS和UEFI来源:https://www.bilibili.com/video/BV16f4y1U7dw/?vd_source=9eb4bfe03031a37efb5ee2d5c74dba21BIOS(基本输入输出系统)在老旧主板上使用了,界面:蓝底白字,没有图形化界面位于软硬件之间的桥梁开机——BIOS初始化——BIOS自检——引导操作系统MBR(主引导记录),磁盘的......
  • 32位和64位的Windows7均不支持UEFI启动方式?试试看!
    前言今天小白突然想起:自己已经接近8年没有安装过32位的Windows系统了,这8年装的上百台电脑都是用的64位Windows。今天 闲来无事  嗯……应该算是有小伙伴提出了个问题:这位小伙伴表示:自己无论安装32位还是64位的Windows7都无法使用UEFI的启动方式。但按照小白之前的装......
  • [分享]OffensiveCon24 UEFI 和翻译器的任务:使用跨架构 UEFI Quines 作为 UEFI 漏洞开
    链接:OffensiveCon24-uefi-task-of-the-translator目录x64assemblyx64程序集x64assemblysourcecodeforBGGP4entry:bggp4winningentry-x64assemblysourcecodeBGGP4UEFISelf-replicatingapp:bggp4winningentry-UEFIself-replicatingapp,compiledfromx64......
  • EasyUEFI、Bootice、Bcdedit和EfiVarCLI都是用于管理计算机启动项和UEFI设置的工具,但
    EasyUEFI、Bootice、Bcdedit和EfiVarCLI都是用于管理计算机启动项和UEFI设置的工具,但它们在功能和用途上有一些区别:EasyUEFI:EasyUEFI是一个用户友好的图形界面工具,主要用于管理UEFI引导项,在Windows操作系统下操作更加方便。EasyUEFI提供了添加、删除、编辑UEFI引导项的......
  • BIOS 与 UEFI 引导流程
    引用:https://www.cnblogs.com/larry1024/p/17645208.html,非常详细,不过图片我没拷贝过来,可以直接到源站阅读BIOS与UEFI引导流程前言现代计算机的整个启动过程可以概括为:计算机通电;CPU读取保存在主板上ROM芯片里的BIOS或UEFI程序(BootLoader);该程序加载指......
  • 进入ThinkServer RQ940服务器的UEFI HII SAS RAID阵列配置界面
    内容导航 一、进入SASRAID阵列配置界面 二、硬盘选择操作 三、确认操作 一、进入SASRAID阵列配置界面 启动服务器,在出现ThinkServerlogo的时候按F2或者Delete键进入BIOS配置界面; 移至"Boot"选项卡,设置"Bootmodeselect"设置为"UEFI";  再移至"Advanced......
  • EasyUEFI 离线注册分析
    离线注册分析仅做离线分析,文件版本5.3.0.2目录离线注册分析一、注册码分析register_CUpgradeDlg_21_id40_448DE0check_rsa_pubk_dec_47D1D0rsa_dec_47CEF0校验注册码信息sub_47D430二、离线激活码分析ok_440920unline_check_450320Init_490DD0校验激活码sub_491730py一、注册......
  • bootmgfw.efi 是 Windows 操作系统中的一个关键文件,它是用于启动 UEFI(统一扩展固件接
    bootmgfw.efi是Windows操作系统中的一个关键文件,它是用于启动UEFI(统一扩展固件接口)计算机的WindowsBootManager。这个文件通常位于Windows安装的EFI系统分区(ESP)中的\EFI\Microsoft\Boot\目录下。在UEFI计算机上,bootmgfw.efi负责加载Windows操作系统的启动程......
  • EasyUEFI 初步分析
    EasyUEFI初步分析GUI采用foxtoolkit,分析主要关注对应类的FX::FXMetaClass,结合构造函数定位控件对应FXMapEntry中的事件函数fox-toolkit.orgpatch1根据字符串信息可定位到版本判断函数,关键点在454E10patch2完成上一步后发现启动后弹出注册框,关闭后不影响使用,可定位注册......
  • WDS+MDT网络启动自动部署windows(三)UEFI & BIOS 双PXE引导
    简介:我们可以通过调整启动文件来兼容不同的硬件(UEFI&BIOS),能否不手动调整呢?自动调整也是可以的。本来是是想将DHCP放在H3C5500上的,但是咨询过H3C的售前顾问后,没有任何一个型号支持这个功能,前面已经折腾过自动识别客户端类型,发送不同的启动文件了。为了更好的完成这个系列文章......