From a0a1c4d52c766305e310059d47b1aad5363bc38a Mon Sep 17 00:00:00 2001 From: Chun-Chieh Li Date: Tue, 6 Aug 2019 16:11:44 +0800 Subject: [PATCH] [M2351] Override NS interface by locking kernel scheduler Lock kernel scheduler rather than mutex to guarantee serialization of NS secure calls --- .../TARGET_TFM/tfm_ns_lock_rtx.c | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 targets/TARGET_NUVOTON/TARGET_M2351/TARGET_M23_NS/TARGET_TFM/tfm_ns_lock_rtx.c diff --git a/targets/TARGET_NUVOTON/TARGET_M2351/TARGET_M23_NS/TARGET_TFM/tfm_ns_lock_rtx.c b/targets/TARGET_NUVOTON/TARGET_M2351/TARGET_M23_NS/TARGET_TFM/tfm_ns_lock_rtx.c new file mode 100644 index 0000000000..dccc14776b --- /dev/null +++ b/targets/TARGET_NUVOTON/TARGET_M2351/TARGET_M23_NS/TARGET_TFM/tfm_ns_lock_rtx.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019, Nuvoton Technology Corporation + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "cmsis.h" +#include "cmsis_os2.h" +#include "tfm_ns_lock.h" +#include "mbed_critical.h" +#include "mbed_assert.h" +#include "mbed_error.h" + +/* Approach to serialization of NS secure calls required by TF-M secure world + * + * Default implementation of NS interface uses mutex to meet the requirement, + * but it cannot support NS secure call in interrupt-disabled context. Instead, + * in this override, NS secure call is guaranteed to be non-preemptive during + * this period by locking kernel scheduler. Apparently, this approach has one + * weakness: all other threads are also locked during this period. Until there's + * a better approach coming out, we can just use this expedient one. + * + * For the 'lock kernel scheduler' approach to work thoroughly, we must also + * address some side issues: + * + * - Prohibit NS secure call from ISR except SVC, so non-preemptive doesn't break. + * - Allow NS secure call in SVC context because it is synchronous. Here, we lock + * interrupt instead of kernel scheduler because svcRtxKernelLock()/svcRtxKernelRestoreLock(...) + * are inaccessible outside rtx_kernel.c. Currently, this is rare case and would cause + * little trouble (see known paths below). + * - Call into secure world straight in interrupt-disabled context. When in + * interrupt-disabled context, NS secure call is guaranteed to be non-preemptive + * naturally. + * - Call into secure world straight at pre-rtos stage. When at pre-rtos stage, + * NS secure call is guaranteed to be non-preemptive naturally. + * - osKernelLock() will error when kernel state is 'osKernelSuspended'. Address + * it separately. Known path of NS secure call when kernel state is 'osKernelSuspended': + * - default idle thread > osKernelSuspend() > lp_ticker_init > SYS_ResetModule_S/ + * CLK_SetModuleClock_S/CLK_EnableModuleClock_S + * + * Known paths of NS secure call in interrupt-disabled context: + * - mbed-os/platform/mbed_sleep_manager.c > sleep_manager_sleep_auto > + * hal_sleep/hal_deepsleep > nu_idle_s/nu_powerdown_s + * - mbed-os/hal/LowPowerTickerWrapper.cpp > LowPowerTickerWrapper::init > + * lp_ticker_init > SYS_ResetModule_S/CLK_SetModuleClock_S/CLK_EnableModuleClock_S + * - mbed-os/platform/mbed_board.c > mbed_die > pin_function_s + * - mbed-os-tests-mbed_hal-rtc > rtc_write_read_test > rtc_write > + * CLK_IsRTCClockEnabled_S + * + * Known paths of NS secure call in SVC context: + * - In tickless mode, osKernelStart > svcRtxKernelStart > OS_Tick_Enable > + * us_ticker_init/lp_ticker_init > SYS_ResetModule_S/CLK_SetModuleClock_S/ + * CLK_EnableModuleClock_S + */ + +struct ns_interface_state +{ + bool init; // Flag if kernel has initialized (and then scheduler + // has started) +}; + +static struct ns_interface_state ns_interface = { + .init = false +}; + +/* Override tfm_ns_lock_init() + * + * On Mbed OS, we expect this function is called before kernel scheduler is + * started so that we can distinguish pre-rtos and rtos stage to meet the + * requirement of serialization of NS secure calls. + */ +enum tfm_status_e tfm_ns_lock_init() +{ + if (!ns_interface.init) { + osKernelState_t kernel_state = osKernelGetState(); + MBED_ASSERT(kernel_state == osKernelInactive || kernel_state == osKernelReady); + + ns_interface.init = true; + } + + return TFM_SUCCESS; +} + +/* Override tfm_ns_lock_dispatch(...) */ +uint32_t tfm_ns_lock_dispatch(veneer_fn fn, + uint32_t arg0, uint32_t arg1, + uint32_t arg2, uint32_t arg3) +{ + /* Prohibit NS secure call from ISR except SVC, so non-preemptive doesn't break */ + uint32_t ipsr = __get_IPSR(); + if (ipsr == 11U) { + /* Allow NS secure call in SVC context because it is synchronous. Here, + * we lock interrupt instead of kernel scheduler because svcRtxKernelLock()/ + * svcRtxKernelRestoreLock(...) are inaccessible outside rtx_kernel.c. */ + core_util_critical_section_enter(); + uint32_t result = fn(arg0, arg1, arg2, arg3); + core_util_critical_section_exit(); + + return result; + } else if (ipsr) { + MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_KERNEL, MBED_ERROR_CODE_PROHIBITED_IN_ISR_CONTEXT), "Prohibited in ISR context", (uintptr_t) fn); + } + + /* Call into secure world straight in interrupt-disabled context because + * NS secure call is non-preemptive naturally */ + if (!core_util_are_interrupts_enabled()) { + return fn(arg0, arg1, arg2, arg3); + } + + /* Call into secure world straight at pre-rtos stage because NS secure + * call is non-preemptive naturally */ + if (!ns_interface.init) { + return fn(arg0, arg1, arg2, arg3); + } + + /* osKernelLock() will error when kernel state is 'osKernelSuspended'. Address + * it separately. */ + osKernelState_t kernel_state = osKernelGetState(); + if (kernel_state == osKernelSuspended) { + return fn(arg0, arg1, arg2, arg3); + } else if (kernel_state == osKernelError) { + MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_KERNEL, MBED_ERROR_CODE_UNKNOWN), "RTX kernel state error", (uintptr_t) fn); + } + + /* Lock kernel scheduler and save previous lock state for restore */ + int32_t lock_state = osKernelLock(); + if (lock_state == osError) { + MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_KERNEL, MBED_ERROR_CODE_UNKNOWN), "Unknown RTX error", (uintptr_t) fn); + } + MBED_ASSERT(lock_state >= 0); + + /* NS secure call is non-preemptive because kernel scheduler is locked */ + uint32_t result = fn(arg0, arg1, arg2, arg3); + + /* Restore previous lock state */ + lock_state = osKernelRestoreLock(lock_state); + if (lock_state == osError) { + MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_KERNEL, MBED_ERROR_CODE_UNKNOWN), "Unknown RTX error", (uintptr_t) fn); + } + MBED_ASSERT(lock_state >= 0); + + return result; +}