13-SDEI: Software Delegated Exception Interface

引流关键词: 中断、同步异常、异步异常、irq、fiq、BL1,BL2,BL3,BL31,BL32,BL33,AP_BL1,AP_BL2,AP_BL3,AP_BL31,AP_BL32,AP_BL33,SCP_BL1,SCP_BL2,BL0,BL30, optee、ATF、TF-A、Trustzone、optee3.14、MMU、VMSA、cache、TLB、arm、armv8、armv9、TEE、安全、内存管理、页表…

快速链接:
.
个人博客笔记导读目录(全部)


[专栏目录]-ATF/FF-A/specification学习

请添加图片描述

13.1. 介绍

软件委托异常接口 ( SDEI :Software Delegated Exception Interface) 是非安全世界的 Arm 规范,用于向固件注册处理程序以接收有关系统事件的通知。固件将首先通过异步异常的方式接收系统事件,并在响应中安排注册的处理程序在非安全 EL 中执行。

与 SDEI 调度程序交互(发出 SDEI 请求和接收通知)的普通世界软件称为SDEI 客户端。即使客户端在屏蔽异常的情况下执行时,它也会在注册的处理程序处收到事件通知。

下图描述了一个涉及 SDEI 客户端在 EL2 上执行的一般序列以及由触发绑定中断产生的事件调度。如下:
13-SDEI: Software Delegated Exception Interface_第1张图片
作为初始化的一部分,SDEI 客户端绑定一个非安全中断 [1],并且 SDEI 调度程序返回一个平台动态事件编号 [2]。然后,客户端为该事件注册一个处理程序 [3],启用该事件 [5],并取消屏蔽当前 PE 上的所有事件 [7]。此序列是 SDEI 客户端的典型序列,但可能涉及其他 SDEI 调用。

在稍后的时间点,当绑定中断触发 [9] 时,它会被trapped在 EL3 中。中断被移交给 SDEI 调度程序,然后安排执行注册的处理程序 [10]。客户端使用SDEI_EVENT_COMPLETE[11]终止其执行 ,然后调度程序恢复原始 EL2 执行 [13]。请注意,在客户端处理程序完成之前,SDEI 中断保持活动状态,此时 EL3 执行 EOI [12]。

除了绑定到中断的事件(如上面的序列所示)之外,还可以明确调度 SDEI 事件以响应其他异常,例如,在接收到SError或同步外部中止时(SError or Synchronous External Abort)。

13.2. 定义事件

选择包含 SDEI 调度程序的平台还必须定义平台上可用的事件及其属性。

该平台预计将提供两个事件描述符数组:一个用于private events,另一个用于shared events。该SDEI调度提供 SDEI_PRIVATE_EVENT()SDEI_SHARED_EVENT()函数来填充事件描述符。两个函数都有 3 个参数:

  • 事件编号:这必须是一个 32 位正整数。
  • 对于backing interrupt中断的事件,该事件绑定到的中断号:
    如果它不适用于某个事件,则应将其保留为0。
    如果事件是动态的,则应将其指定为SDEI_DYN_IRQ。
  • A bit map of Event flags

要定义事件 0,应使用SDEI_DEFINE_EVENT_0()宏。这个宏只需要一个参数:一个 SGI 编号来通知其他 PE。
要定义旨在显式调度的事件(即,不是作为接收 SDEI 中断的结果),SDEI_EXPLICIT_EVENT() 应使用宏。它接受两个参数:

  • 事件编号;
  • 事件优先级:SDEI_MAPF_CRITICAL或SDEI_MAPF_NORMAL

一旦定义了事件描述符数组,就应该使用REGISTER_SDEI_MAP()宏将它们导出到 SDEI 调度程序,分别将指向私有和共享事件描述符数组的指针传递给它。请注意, REGISTER_SDEI_MAP()宏必须在定义数组的同一文件中使用。关于事件描述符:

  • 对于事件 0:
    私有数组中必须只有一个描述符,而共享数组中没有。
    该事件应使用SDEI_DEFINE_EVENT_0().
    必须绑定到平台上的安全 SGI。
  • 显式事件应该只在私有数组中使用。
  • 静态绑定的共享和私有中断必须分别绑定到平台上的共享和私有中断。请参阅异常处理框架中的配置部分 。
  • 两个数组都应该是一维的。该REGISTER_SDEI_MAP()宏接受复制的私人事件平台上的每个PE的照顾。
  • 两个数组都必须按事件编号的递增顺序排序。
13.2.1. 事件标志
  • SDEI_MAPF_DYNAMIC:将事件标记为动态。动态事件可以在运行时通过SDEI_INTERRUPT_BIND和SDEI_INTERRUPT_RELEASE调用绑定到(或从中释放)任何非安全中断 。
  • SDEI_MAPF_BOUND:将事件标记为静态绑定到中断。这些事件不能在运行时重新绑定。
  • SDEI_MAPF_NORMAL:将事件标记为具有普通优先级。这是默认优先级。
  • SDEI_MAPF_CRITICAL:将事件标记为具有严重优先级
13.3. 事件定义示例
static sdei_ev_map_t plat_private_sdei[] = {
     /* Event 0 definition */
     SDEI_DEFINE_EVENT_0(8),

     /* PPI */
     SDEI_PRIVATE_EVENT(8, 23, SDEI_MAPF_BOUND),

     /* Dynamic private events */
     SDEI_PRIVATE_EVENT(100, SDEI_DYN_IRQ, SDEI_MAPF_DYNAMIC),
     SDEI_PRIVATE_EVENT(101, SDEI_DYN_IRQ, SDEI_MAPF_DYNAMIC)

     /* Events for explicit dispatch */
     SDEI_EXPLICIT_EVENT(2000, SDEI_MAPF_NORMAL);
     SDEI_EXPLICIT_EVENT(2000, SDEI_MAPF_CRITICAL);
};

/* Shared event mappings */
static sdei_ev_map_t plat_shared_sdei[] = {
     SDEI_SHARED_EVENT(804, 0, SDEI_MAPF_DYNAMIC),

     /* Dynamic shared events */
     SDEI_SHARED_EVENT(3000, SDEI_DYN_IRQ, SDEI_MAPF_DYNAMIC),
     SDEI_SHARED_EVENT(3001, SDEI_DYN_IRQ, SDEI_MAPF_DYNAMIC)
};

/* Export SDEI events */
REGISTER_SDEI_MAP(plat_private_sdei, plat_shared_sdei);
13.4. 异常处理框架内的配置

SDEI 调度程序与异常处理框架一起运行。这意味着平台必须为平台的正常和关键 SDEI 中断分配优先级:

  • 为正常和关键 SDEI 中断安装优先级描述符。

  • 对于那些静态绑定的中断(即定义为具有SDEI_MAPF_BOUND属性的事件),枚举它们的属性以便 GIC 驱动程序相应地配置中断。

  • 中断必须配置为以 EL3 为目标。这意味着它们应配置为Group 0。此外,在 GICv2 系统上,构建选项 GICV2_G0_FOR_EL3必须设置为1.

13.5. 确定客户端 EL

SDEI 规范要求物理SDEI 客户端在系统上实现的最高非安全 EL 中执行。这意味着调度程序将只允许从以下位置进行 SDEI 调用:

  • EL2,如果实现了 EL2。Hypervisor 预计将实现一个 虚拟SDEI 调度程序,以支持在非安全 EL1 中执行的客户操作系统中的 SDEI 客户端。

  • 非安全 EL1,如果 EL2 未实现或禁用。

请参阅 中的函数在sdei_private.h中的sdei_client_el()

13.6. 显式分派事件

通常,SDEI 事件调度是由 PE 接收绑定到 SDEI 事件的中断引起的。但是,在某些情况下,安全世界需要将 SDEI 事件作为过去活动的直接或间接结果进行调度,例如接收安全中断或异常。

SDEI 调度程序实现sdei_dispatch_event()为此提供了 API。API 具有以下签名:

int sdei_dispatch_event(int ev_num);

参数ev_num是要调度的事件编号。API0 在成功或-1失败时返回。

下图描述了一个涉及显式分派 SDEI 事件的场景。评论如下:
13-SDEI: Software Delegated Exception Interface_第2张图片

作为初始化的一部分,SDEI 客户端为平台事件 [1] 注册一个处理程序,启用事件 [3],并取消屏蔽当前 PE [5]。请注意,与一般的 SDEI 调度不同,这不涉及中断绑定,因为绑定或动态事件不能被显式调度(参见下面的部分)。

在稍后的时间点,关键事件2被困在 EL3 [7] 中。EL3 执行事件的第一级分类,RAS 组件承担进一步处理 [8]。分派完成,但打算让非安全世界参与进一步处理,因此决定显式分派一个事件 [10](客户端已经为 [1] 注册了该事件)。其余的顺序与一般的 SDEI 分派类似:将请求的事件分派给客户端(假设所有条件都满足),当处理程序完成时,抢占执行恢复。

13.6.1. 事件派发的条件
必须满足以下所有要求才能返回 API0并分派事件:

  • 必须在 PE 上取消屏蔽 SDEI 事件。即客户必须 PE_UNMASK事先打电话。

  • 无法分派事件 0。

  • 必须使用上述SDEI_EXPLICIT_EVENT()宏声明事件。

  • 该事件必须是 PE 私有的。

  • 该事件必须已注册并启用。

  • 同一事件的调度不得未完成。即它还没有被发送并且还没有完成。

  • 事件的优先级(严重或正常,由平台在构建时配置)不应导致优先级反转。这意味着:

  • 如果它是 Normal 优先级,那么在 PE 上,Normal 和 Critical 优先级调度都必须是未完成的。

  • 如果它具有关键优先级,则 PE 上必须没有未完成的关键优先级调度。

此外,调用者应了解调度程序所做的以下假设:

  • API的调用者是运行在EL3中的组件;例如,RAS 驱动程序。

  • 异常处理框架将允许请求的调度。即调用者必须确保请求的调度具有足够的优先级,以免在异常处理框架内引起优先级倒置。

  • 调用者必须为 SDEI 调度程序恢复非安全上下文做好准备,并将其标记为活动上下文。

  • 调用将阻塞,直到 SDEI 客户端完成事件(即当客户端调用SDEI_EVENT_COMPLETE或时SDEI_COMPLETE_AND_RESUME)。

  • 调用者必须准备好让这个 API 返回失败并进行相应的处理。

13.7. 移植要求

移植指南中概述了 SDEI 调度程序的移植要求 。

13.8. 编写 SDEI 事件处理程序的注意事项

本节与一般的 SDEI 事件处理程序有关,而不仅仅是在使用 TF-A SDEI 调度程序时。

SDEI 规范要求事件处理程序保留所有寄存器的内容,x0除了x17. 如果事件处理程序是用 C 编写的,这很重要:编译器通常会在 C 函数的开头和结尾调整堆栈帧。例如,AArch64 GCC 通常会产生以下函数 prologue 和 epilogue:

c_event_handler:
        stp     x29, x30, [sp,#-32]!
        mov     x29, sp

        ...

        bl      ...

        ...

        ldp     x29, x30, [sp],#32
        ret

该寄存器x29在序言中用作帧指针。因为无论是有效的SDEI_EVENT_COMPLETE还是SDEI_EVENT_COMPLETE_AND_RESUME调用都不会返回到处理程序,结语永远不会被执行,并且寄存器x29 和x30(在上面的情况下)被无意中破坏了。这违反了 SDEI 规范,之后的正常执行将导致意外行为。

为了解决这个问题,建议顶级事件处理程序在程序集中实现,遵循以下类似模式:

asm_event_handler:
        /* Save link register whilst maintaining stack alignment */
        stp     xzr, x30, [sp, #-16]!
        bl      c_event_handler

        /* Restore link register */
        ldp     xzr, x30, [sp], #16

        /* Complete call */
        ldr     x0, =SDEI_EVENT_COMPLETE
        smc     #0
        b       .

你可能感兴趣的:(atf_doc,atf,armv9,SEDI)