diff --git a/bl31/bl31.mk b/bl31/bl31.mk index 51a8312..307ddab 100644 --- a/bl31/bl31.mk +++ b/bl31/bl31.mk @@ -42,7 +42,8 @@ ifeq (${EL3_EXCEPTION_HANDLING},0) $(error EL3_EXCEPTION_HANDLING must be 1 for SDEI support) endif -BL31_SOURCES += services/std_svc/sdei/sdei_event.c \ +BL31_SOURCES += services/std_svc/sdei/sdei_dispatch.S \ + services/std_svc/sdei/sdei_event.c \ services/std_svc/sdei/sdei_intr_mgmt.c \ services/std_svc/sdei/sdei_main.c \ services/std_svc/sdei/sdei_state.c diff --git a/docs/plantuml/sdei_explicit_dispatch.puml b/docs/plantuml/sdei_explicit_dispatch.puml index c80fcd1..90ff23c 100644 --- a/docs/plantuml/sdei_explicit_dispatch.puml +++ b/docs/plantuml/sdei_explicit_dispatch.puml @@ -1,5 +1,5 @@ /' - ' Copyright (c) 2017, ARM Limited and Contributors. All rights reserved. + ' Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved. ' ' SPDX-License-Identifier: BSD-3-Clause '/ @@ -9,7 +9,8 @@ autonumber "[#]" participant "SDEI client" as EL2 participant EL3 -participant "Secure Partition" as SP +participant SDEI +participant "RAS Driver" as RAS activate EL2 EL2->EL3: **SDEI_EVENT_REGISTER**(ev, handler, ...) @@ -24,21 +25,26 @@ EL3<--]: **CRITICAL EVENT** activate EL3 #red note over EL3: Critical event triage -EL3->SP: dispatch -activate SP #salmon -note over SP: Critical event handling -SP->EL3: done -deactivate SP -EL3-->EL3: sdei_dispatch_event(ev) -note over EL3: Prepare SDEI dispatch -EL3->EL2: dispatch +EL3->RAS: dispatch to handle +deactivate EL3 +activate RAS #salmon +note over RAS: Critical event handling +RAS-->SDEI: sdei_dispatch_event(ev) +deactivate RAS +activate SDEI #salmon +note over SDEI: Prepare SDEI dispatch +SDEI->EL2: dispatch activate EL2 #salmon note over EL2: SDEI handler -EL2->EL3: **SDEI_EVENT_COMPLETE()** +EL2->SDEI: **SDEI_EVENT_COMPLETE()** deactivate EL2 -note over EL3: Complete SDEI dispatch +note over SDEI: Complete SDEI dispatch +SDEI-->RAS: return +deactivate SDEI +activate RAS #salmon +RAS->EL3: error handling done +deactivate RAS EL3->EL2: resumes preempted execution -deactivate EL3 ... <> ... diff --git a/docs/plantuml/sdei_explicit_dispatch.svg b/docs/plantuml/sdei_explicit_dispatch.svg index 182df0a..e12cae2 100644 --- a/docs/plantuml/sdei_explicit_dispatch.svg +++ b/docs/plantuml/sdei_explicit_dispatch.svg @@ -1 +1 @@ -SDEI clientSDEI clientEL3EL3Secure PartitionSecure Partition[1]SDEI_EVENT_REGISTER(ev, handler, ...)[2]success[3]SDEI_EVENT_ENABLE(ev)[4]success[5]SDEI_PE_UNMASK()[6]1<<Business as usual>>[7]CRITICAL EVENTCritical event triage[8]dispatchCritical event handling[9]done[10]sdei_dispatch_event(ev)Prepare SDEI dispatch[11]dispatchSDEI handler[12]SDEI_EVENT_COMPLETE()Complete SDEI dispatch[13]resumes preempted execution<<Normal execution resumes>> \ No newline at end of file +SDEI clientSDEI clientEL3EL3SDEISDEIRAS DriverRAS Driver[1]SDEI_EVENT_REGISTER(ev, handler, ...)[2]success[3]SDEI_EVENT_ENABLE(ev)[4]success[5]SDEI_PE_UNMASK()[6]1<<Business as usual>>[7]CRITICAL EVENTCritical event triage[8]dispatch to handleCritical event handling[9]sdei_dispatch_event(ev)Prepare SDEI dispatch[10]dispatchSDEI handler[11]SDEI_EVENT_COMPLETE()Complete SDEI dispatch[12]return[13]error handling done[14]resumes preempted execution<<Normal execution resumes>> \ No newline at end of file diff --git a/docs/sdei.rst b/docs/sdei.rst index ed6a85a..531145f 100644 --- a/docs/sdei.rst +++ b/docs/sdei.rst @@ -233,14 +233,10 @@ :: - int sdei_dispatch_event(int ev_num, unsigned int preempted_sec_state); + int sdei_dispatch_event(int ev_num); -- The parameter ``ev_num`` is the event number to dispatch; - -- The parameter ``preempted_sec_state`` indicates the context that was - preempted. This must be either ``SECURE`` or ``NON_SECURE``. - -The API returns ``0`` on success, or ``-1`` on failure. +The parameter ``ev_num`` is the event number to dispatch. The API returns ``0`` +on success, or ``-1`` on failure. The following figure depicts a scenario involving explicit dispatch of SDEI event. A commentary is provided below: @@ -253,22 +249,18 @@ bound or dynamic events can't be explicitly dispatched (see the section below). At a later point in time, a critical event [#critical-event]_ is trapped into -EL3 [7]. EL3 performs a first-level triage of the event, and decides to dispatch -to a Secure Partition [#secpart]_ for further handling [8]. The dispatch -completes, but intends to involve Non-secure world in further handling, and -therefore decides to explicitly dispatch an event [10] (which the client had -already registered for [1]). The rest of the sequence is similar to that in the -`general SDEI dispatch`_: the requested event is dispatched to the client -(assuming all the conditions are met), and when the handler completes, the -preempted execution resumes. +EL3 [7]. EL3 performs a first-level triage of the event, and a RAS component +assumes further handling [8]. The dispatch completes, but intends to involve +Non-secure world in further handling, and therefore decides to explicitly +dispatch an event [10] (which the client had already registered for [1]). The +rest of the sequence is similar to that in the `general SDEI dispatch`_: the +requested event is dispatched to the client (assuming all the conditions are +met), and when the handler completes, the preempted execution resumes. .. [#critical-event] Examples of critical event are *SError*, *Synchronous External Abort*, *Fault Handling interrupt*, or *Error Recovery interrupt* from one of RAS nodes in the system. -.. [#secpart] Dispatching to Secure Partition involves *Secure Partition - Manager*, which isn't depicted in the sequence. - Conditions for event dispatch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -302,28 +294,22 @@ Further, the caller should be aware of the following assumptions made by the dispatcher: -- The caller of the API is a component running in EL3; for example, the *Secure - Partition Manager*. +- The caller of the API is a component running in EL3; for example, a RAS + driver. - The requested dispatch will be permitted by the Exception Handling Framework. I.e. the caller must make sure that the requested dispatch has sufficient priority so as not to cause priority level inversion within Exception Handling Framework. -- At the time of the call, the active context is Secure, and it has been saved. +- The caller must be prepared for the SDEI dispatcher to restore the Non-secure + context, and mark that the active context. -- Upon returning success, the Non-secure context will be restored and setup for - the event dispatch, and it will be the active context. The Non-secure context - should not be modified further by the caller. +- The call will block until the SDEI client completes the event (i.e. when the + client calls either ``SDEI_EVENT_COMPLETE`` or ``SDEI_COMPLETE_AND_RESUME``). -- The API returning success only means that the dispatch is scheduled at the - next ``ERET``, and not immediately performed. Also, the caller must be - prepared for this API to return failure and handle accordingly. - -- Upon completing the event (i.e. when the client calls either - ``SDEI_EVENT_COMPLETE`` or ``SDEI_COMPLETE_AND_RESUME``), the preempted - context is resumed (as indicated by the ``preempted_sec_state`` parameter of - the API). +- The caller must be prepared for this API to return failure and handle + accordingly. Porting requirements -------------------- diff --git a/include/services/sdei.h b/include/services/sdei.h index ee3531c..79d1d06 100644 --- a/include/services/sdei.h +++ b/include/services/sdei.h @@ -183,6 +183,6 @@ void sdei_init(void); /* Public API to dispatch an event to Normal world */ -int sdei_dispatch_event(int ev_num, unsigned int preempted_sec_state); +int sdei_dispatch_event(int ev_num); #endif /* __SDEI_H__ */ diff --git a/services/std_svc/sdei/sdei_dispatch.S b/services/std_svc/sdei/sdei_dispatch.S new file mode 100644 index 0000000..a7a4a40 --- /dev/null +++ b/services/std_svc/sdei/sdei_dispatch.S @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + + .globl begin_sdei_synchronous_dispatch + +/* + * void begin_sdei_synchronous_dispatch(struct jmpbuf *buffer); + * + * Begin SDEI dispatch synchronously by setting up a jump point, and exiting + * EL3. This jump point is jumped to by the dispatcher after the event is + * completed by the client. + */ +func begin_sdei_synchronous_dispatch + stp x30, xzr, [sp, #-16]! + bl setjmp + cbz x0, 1f + ldp x30, xzr, [sp], #16 + ret +1: + b el3_exit +endfunc begin_sdei_synchronous_dispatch diff --git a/services/std_svc/sdei/sdei_intr_mgmt.c b/services/std_svc/sdei/sdei_intr_mgmt.c index d6324b9..6acb129 100644 --- a/services/std_svc/sdei/sdei_intr_mgmt.c +++ b/services/std_svc/sdei/sdei_intr_mgmt.c @@ -31,9 +31,8 @@ /* Structure to store information about an outstanding dispatch */ typedef struct sdei_dispatch_context { sdei_ev_map_t *map; - unsigned int sec_state; - unsigned int intr_raw; uint64_t x[SDEI_SAVED_GPREGS]; + struct jmpbuf *dispatch_jmp; /* Exception state registers */ uint64_t elr_el3; @@ -153,8 +152,8 @@ return &state->dispatch_stack[state->stack_top - 1]; } -static void save_event_ctx(sdei_ev_map_t *map, void *tgt_ctx, int sec_state, - unsigned int intr_raw) +static sdei_dispatch_context_t *save_event_ctx(sdei_ev_map_t *map, + void *tgt_ctx) { sdei_dispatch_context_t *disp_ctx; gp_regs_t *tgt_gpregs; @@ -166,26 +165,14 @@ disp_ctx = push_dispatch(); assert(disp_ctx); - disp_ctx->sec_state = sec_state; disp_ctx->map = map; - disp_ctx->intr_raw = intr_raw; /* Save general purpose and exception registers */ memcpy(disp_ctx->x, tgt_gpregs, sizeof(disp_ctx->x)); disp_ctx->spsr_el3 = read_ctx_reg(tgt_el3, CTX_SPSR_EL3); disp_ctx->elr_el3 = read_ctx_reg(tgt_el3, CTX_ELR_EL3); -#if DYNAMIC_WORKAROUND_CVE_2018_3639 - cve_2018_3639_t *tgt_cve_2018_3639; - tgt_cve_2018_3639 = get_cve_2018_3639_ctx(tgt_ctx); - - /* Save CVE-2018-3639 mitigation state */ - disp_ctx->disable_cve_2018_3639 = read_ctx_reg(tgt_cve_2018_3639, - CTX_CVE_2018_3639_DISABLE); - - /* Force SDEI handler to execute with mitigation enabled by default */ - write_ctx_reg(tgt_cve_2018_3639, CTX_CVE_2018_3639_DISABLE, 0); -#endif + return disp_ctx; } static void restore_event_ctx(sdei_dispatch_context_t *disp_ctx, void *tgt_ctx) @@ -249,13 +236,12 @@ * SDEI client. */ static void setup_ns_dispatch(sdei_ev_map_t *map, sdei_entry_t *se, - cpu_context_t *ctx, int sec_state_to_resume, - unsigned int intr_raw) + cpu_context_t *ctx, struct jmpbuf *dispatch_jmp) { - el3_state_t *el3_ctx = get_el3state_ctx(ctx); + sdei_dispatch_context_t *disp_ctx; /* Push the event and context */ - save_event_ctx(map, ctx, sec_state_to_resume, intr_raw); + disp_ctx = save_event_ctx(map, ctx); /* * Setup handler arguments: @@ -267,8 +253,8 @@ */ SMC_SET_GP(ctx, CTX_GPREG_X0, map->ev_num); SMC_SET_GP(ctx, CTX_GPREG_X1, se->arg); - SMC_SET_GP(ctx, CTX_GPREG_X2, read_ctx_reg(el3_ctx, CTX_ELR_EL3)); - SMC_SET_GP(ctx, CTX_GPREG_X3, read_ctx_reg(el3_ctx, CTX_SPSR_EL3)); + SMC_SET_GP(ctx, CTX_GPREG_X2, disp_ctx->elr_el3); + SMC_SET_GP(ctx, CTX_GPREG_X3, disp_ctx->spsr_el3); /* * Prepare for ERET: @@ -279,6 +265,20 @@ cm_set_elr_spsr_el3(NON_SECURE, (uintptr_t) se->ep, SPSR_64(sdei_client_el(), MODE_SP_ELX, DISABLE_ALL_EXCEPTIONS)); + +#if DYNAMIC_WORKAROUND_CVE_2018_3639 + cve_2018_3639_t *tgt_cve_2018_3639; + tgt_cve_2018_3639 = get_cve_2018_3639_ctx(ctx); + + /* Save CVE-2018-3639 mitigation state */ + disp_ctx->disable_cve_2018_3639 = read_ctx_reg(tgt_cve_2018_3639, + CTX_CVE_2018_3639_DISABLE); + + /* Force SDEI handler to execute with mitigation enabled by default */ + write_ctx_reg(tgt_cve_2018_3639, CTX_CVE_2018_3639_DISABLE, 0); +#endif + + disp_ctx->dispatch_jmp = dispatch_jmp; } /* Handle a triggered SDEI interrupt while events were masked on this PE */ @@ -348,6 +348,7 @@ unsigned int sec_state; sdei_cpu_state_t *state; uint32_t intr; + struct jmpbuf dispatch_jmp; /* * To handle an event, the following conditions must be true: @@ -481,29 +482,60 @@ ctx = restore_and_resume_ns_context(); } - setup_ns_dispatch(map, se, ctx, sec_state, intr_raw); + /* Synchronously dispatch event */ + setup_ns_dispatch(map, se, ctx, &dispatch_jmp); + begin_sdei_synchronous_dispatch(&dispatch_jmp); /* - * End of interrupt is done in sdei_event_complete, when the client - * signals completion. + * We reach here when client completes the event. + * + * If the cause of dispatch originally interrupted the Secure world, and + * if Non-secure world wasn't allowed to preempt Secure execution, + * resume Secure. + * + * No need to save the Non-secure context ahead of a world switch: the + * Non-secure context was fully saved before dispatch, and has been + * returned to its pre-dispatch state. */ + if ((sec_state == SECURE) && (ehf_is_ns_preemption_allowed() == 0)) + restore_and_resume_secure_context(); + + /* + * The event was dispatched after receiving SDEI interrupt. With + * the event handling completed, EOI the corresponding + * interrupt. + */ + if ((map->ev_num != SDEI_EVENT_0) && is_map_bound(map)) { + ERROR("Invalid SDEI mapping: ev=%u\n", map->ev_num); + panic(); + } + plat_ic_end_of_interrupt(intr_raw); + + if (is_event_shared(map)) + sdei_map_unlock(map); + return 0; } -/* Explicitly dispatch the given SDEI event */ -int sdei_dispatch_event(int ev_num, unsigned int preempted_sec_state) +/* + * Explicitly dispatch the given SDEI event. + * + * When calling this API, the caller must be prepared for the SDEI dispatcher to + * restore and make Non-secure context as active. This call returns only after + * the client has completed the dispatch. Then, the Non-secure context will be + * active, and the following ERET will return to Non-secure. + * + * Should the caller require re-entry to Secure, it must restore the Secure + * context and program registers for ERET. + */ +int sdei_dispatch_event(int ev_num) { sdei_entry_t *se; sdei_ev_map_t *map; - cpu_context_t *ctx; + cpu_context_t *ns_ctx; sdei_dispatch_context_t *disp_ctx; sdei_cpu_state_t *state; - - /* Validate preempted security state */ - if ((preempted_sec_state != SECURE) && - (preempted_sec_state != NON_SECURE)) { - return -1; - } + struct jmpbuf dispatch_jmp; /* Can't dispatch if events are masked on this PE */ state = sdei_get_this_pe_state(); @@ -549,21 +581,31 @@ ehf_activate_priority(sdei_event_priority(map)); /* - * We assume the current context is SECURE, and that it's already been - * saved. + * Prepare for NS dispatch by restoring the Non-secure context and + * marking that as active. */ - ctx = restore_and_resume_ns_context(); + ns_ctx = restore_and_resume_ns_context(); + + /* Dispatch event synchronously */ + setup_ns_dispatch(map, se, ns_ctx, &dispatch_jmp); + begin_sdei_synchronous_dispatch(&dispatch_jmp); /* - * The caller has effectively terminated execution. Record to resume the - * preempted context later when the event completes or - * complete-and-resumes. + * We reach here when client completes the event. + * + * Deactivate the priority level that was activated at the time of + * explicit dispatch. */ - setup_ns_dispatch(map, se, ctx, preempted_sec_state, 0); + ehf_deactivate_priority(sdei_event_priority(map)); return 0; } +static void end_sdei_explicit_dispatch(struct jmpbuf *buffer) +{ + longjmp(buffer); +} + int sdei_event_complete(int resume, uint64_t pc) { sdei_dispatch_context_t *disp_ctx; @@ -636,38 +678,8 @@ } } - /* - * If the cause of dispatch originally interrupted the Secure world, and - * if Non-secure world wasn't allowed to preempt Secure execution, - * resume Secure. - * - * No need to save the Non-secure context ahead of a world switch: the - * Non-secure context was fully saved before dispatch, and has been - * returned to its pre-dispatch state. - */ - if ((disp_ctx->sec_state == SECURE) && - (ehf_is_ns_preemption_allowed() == 0)) { - restore_and_resume_secure_context(); - } - - if ((map->ev_num == SDEI_EVENT_0) || is_map_bound(map)) { - /* - * The event was dispatched after receiving SDEI interrupt. With - * the event handling completed, EOI the corresponding - * interrupt. - */ - plat_ic_end_of_interrupt(disp_ctx->intr_raw); - } else { - /* - * An unbound event must have been dispatched explicitly. - * Deactivate the priority level that was activated at the time - * of explicit dispatch. - */ - ehf_deactivate_priority(sdei_event_priority(map)); - } - - if (is_event_shared(map)) - sdei_map_unlock(map); + /* End the outstanding dispatch */ + end_sdei_explicit_dispatch(disp_ctx->dispatch_jmp); return 0; } diff --git a/services/std_svc/sdei/sdei_private.h b/services/std_svc/sdei/sdei_private.h index ec4148c..ea60287 100644 --- a/services/std_svc/sdei/sdei_private.h +++ b/services/std_svc/sdei/sdei_private.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -240,5 +241,6 @@ int sdei_intr_handler(uint32_t intr, uint32_t flags, void *handle, void *cookie); bool can_sdei_state_trans(sdei_entry_t *se, sdei_action_t act); +void begin_sdei_synchronous_dispatch(struct jmpbuf *buffer); #endif /* __SDEI_PRIVATE_H__ */