diff options
-rw-r--r-- | CMakeLists.txt | 10 | ||||
-rw-r--r-- | README.md | 30 | ||||
-rw-r--r-- | include/ethosu_driver.h | 37 | ||||
-rw-r--r-- | src/ethosu_driver.c | 87 | ||||
-rw-r--r-- | src/ethosu_pmu.c | 31 |
5 files changed, 149 insertions, 46 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index bb9f22d..350ac40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ set(LOG_NAMES err warning info debug) set(ETHOSU_LOG_ENABLE ON CACHE BOOL "Toggle driver logs on/off (Defaults to ON)") set(ETHOSU_LOG_SEVERITY "warning" CACHE STRING "Driver log severity level ${LOG_NAMES} (Defaults to 'warning')") set(ETHOSU_TARGET_NPU_CONFIG "ethos-u55-128" CACHE STRING "Default NPU configuration") +set(ETHOSU_INFERENCE_TIMEOUT "" CACHE STRING "Inference timeout (unit is implementation defined)") set_property(CACHE ETHOSU_LOG_SEVERITY PROPERTY STRINGS ${LOG_NAMES}) # @@ -75,7 +76,13 @@ else() message(FATAL_ERROR "Invalid NPU configuration") endif() - +if(NOT "${ETHOSU_INFERENCE_TIMEOUT}" STREQUAL "") + target_compile_definitions(ethosu_core_driver PRIVATE + ETHOSU_SEMAPHORE_WAIT_INFERENCE=${ETHOSU_INFERENCE_TIMEOUT}) + set(ETHOSU_INFERENCE_TIMEOUT_TEXT ${ETHOSU_INFERENCE_TIMEOUT}) +else() + set(ETHOSU_INFERENCE_TIMEOUT_TEXT "Default (no timeout)") +endif() # Set the log level for the target target_compile_definitions(ethosu_core_driver PRIVATE ETHOSU_LOG_SEVERITY=${LOG_SEVERITY} @@ -100,4 +107,5 @@ message(STATUS "CMAKE_SYSTEM_PROCESSOR : ${CMAKE_SYSTEM_PROCESSO message(STATUS "CMSIS_PATH : ${CMSIS_PATH}") message(STATUS "ETHOSU_LOG_ENABLE : ${ETHOSU_LOG_ENABLE}") message(STATUS "ETHOSU_LOG_SEVERITY : ${ETHOSU_LOG_SEVERITY}") +message(STATUS "ETHOSU_INFERENCE_TIMEOUT : ${ETHOSU_INFERENCE_TIMEOUT_TEXT}") message(STATUS "*******************************************************") @@ -160,6 +160,23 @@ environemnts where multi-threading is possible, e.g., RTOS, the user is responsible to provide implementation for mutexes and semaphores to be used by the driver. +The mutex and semaphores are used as synchronisation mechanisms and unless +specified, the timeout is required to be 'forever'. + +The driver allows for an RTOS to set a timeout for the NPU interrupt semaphore. +The timeout can be set with the CMake variable `ETHOSU_INFERENCE_TIMEOUT`, which +is then used as `timeout` argument for the interrupt semaphore take call. Note +that the unit is implementation defined, the value is shipped as is to the +`ethosu_semaphore_take()` function and an override implementation should cast it +to the appropriate type and/or convert it to the unit desired. + +A macro `ETHOSU_SEMAPHORE_WAIT_FOREVER` is defined in the driver header file, +and should be made sure to map to the RTOS' equivalent of +'no timeout/wait forever'. Inference timeout value defaults to this if left +unset. The macro is used internally in the driver for the available NPU's, thus +the driver does NOT support setting a timeout other than forever when waiting +for an NPU to become available (global ethosu_semaphore). + The mutex and semaphore APIs are defined as weak linked functions that can be overridden by the user. The APIs are the usual ones and described below: @@ -167,16 +184,16 @@ overridden by the user. The APIs are the usual ones and described below: // create a mutex by returning back a handle void *ethosu_mutex_create(void); // lock the given mutex -void ethosu_mutex_lock(void *mutex); +int ethosu_mutex_lock(void *mutex); // unlock the given mutex -void ethosu_mutex_unlock(void *mutex); +int ethosu_mutex_unlock(void *mutex); // create a (binary) semaphore by returning back a handle void *ethosu_semaphore_create(void); -// take from the given semaphore -void ethosu_semaphore_take(void *sem); +// take from the given semaphore, accepting a timeout (unit impl. defined) +int ethosu_semaphore_take(void *sem, uint64_t timeout); // give from the given semaphore -void ethosu_semaphore_give(void *sem); +int ethosu_semaphore_give(void *sem); ``` ## Begin/End inference callbacks @@ -187,6 +204,9 @@ To avoid memory leaks, any allocations done in the ethosu_inference_begin() must be balanced by a corresponding free of the memory in the ethosu_inference_end() callback. +The end callback will always be called if the begin callback has been called, +including in the event of an interrupt semaphore take timeout. + ```[C] void ethosu_inference_begin(struct ethosu_driver *drv, void *user_arg); void ethosu_inference_end(struct ethosu_driver *drv, void *user_arg); diff --git a/include/ethosu_driver.h b/include/ethosu_driver.h index 9c9f173..e2d3f5b 100644 --- a/include/ethosu_driver.h +++ b/include/ethosu_driver.h @@ -1,6 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2019-2023 Arm Limited and/or its affiliates <open-source-office@arm.com> - * + * SPDX-FileCopyrightText: Copyright 2019-2024 Arm Limited and/or its affiliates <open-source-office@arm.com> * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the License); you may @@ -41,6 +40,12 @@ extern "C" { #define ETHOSU_DRIVER_VERSION_MINOR 16 ///< Driver minor version #define ETHOSU_DRIVER_VERSION_PATCH 0 ///< Driver patch version +#define ETHOSU_SEMAPHORE_WAIT_FOREVER (UINT64_MAX) + +#ifndef ETHOSU_SEMAPHORE_WAIT_INFERENCE +#define ETHOSU_SEMAPHORE_WAIT_INFERENCE ETHOSU_SEMAPHORE_WAIT_FOREVER +#endif + /****************************************************************************** * Types ******************************************************************************/ @@ -55,9 +60,17 @@ enum ethosu_job_state ETHOSU_JOB_DONE }; +enum ethosu_job_result +{ + ETHOSU_JOB_RESULT_OK = 0, + ETHOSU_JOB_RESULT_TIMEOUT, + ETHOSU_JOB_RESULT_ERROR +}; + struct ethosu_job { volatile enum ethosu_job_state state; + volatile enum ethosu_job_result result; const void *custom_data_ptr; int custom_data_size; const uint64_t *base_addr; @@ -75,7 +88,6 @@ struct ethosu_driver uint64_t fast_memory; size_t fast_memory_size; uint32_t power_request_counter; - bool status_error; bool reserved; }; @@ -134,6 +146,13 @@ void ethosu_invalidate_dcache(uint32_t *p, size_t bytes); void *ethosu_mutex_create(void); /** + * Destroy mutex. + * + * @param mutex Pointer to mutex handle + */ +void ethosu_mutex_destroy(void *mutex); + +/** * Minimal sempahore implementation for baremetal applications. See * ethosu_driver.c. * @@ -142,6 +161,13 @@ void *ethosu_mutex_create(void); void *ethosu_semaphore_create(void); /** + * Destroy semaphore. + * + * @param sem Pointer to semaphore handle + */ +void ethosu_semaphore_destroy(void *sem); + +/** * Lock mutex. * * @param mutex Pointer to mutex handle @@ -161,9 +187,10 @@ int ethosu_mutex_unlock(void *mutex); * Take semaphore. * * @param sem Pointer to semaphore handle - * @returns 0 on success, else negative error code + * @param timeout Timeout value (unit impl. defined) + * @returns 0 on success else negative error code */ -int ethosu_semaphore_take(void *sem); +int ethosu_semaphore_take(void *sem, uint64_t timeout); /** * Give semaphore. diff --git a/src/ethosu_driver.c b/src/ethosu_driver.c index 8fac936..5128455 100644 --- a/src/ethosu_driver.c +++ b/src/ethosu_driver.c @@ -1,6 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2019-2023 Arm Limited and/or its affiliates <open-source-office@arm.com> - * + * SPDX-FileCopyrightText: Copyright 2019-2024 Arm Limited and/or its affiliates <open-source-office@arm.com> * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the License); you may @@ -160,7 +159,8 @@ static void *ethosu_semaphore; void *__attribute__((weak)) ethosu_mutex_create(void) { - return NULL; + static uint8_t mutex_placeholder; + return &mutex_placeholder; } void __attribute__((weak)) ethosu_mutex_destroy(void *mutex) @@ -197,12 +197,21 @@ void __attribute__((weak)) ethosu_semaphore_destroy(void *sem) } // Baremetal simulation of waiting/sleeping for and then taking a semaphore using intrisics -int __attribute__((weak)) ethosu_semaphore_take(void *sem) +int __attribute__((weak)) ethosu_semaphore_take(void *sem, uint64_t timeout) { + UNUSED(timeout); + // Baremetal pseudo-example on how to trigger a timeout: + // if (timeout != ETHOSU_SEMAPHORE_WAIT_FOREVER) { + // setup_a_timer_to_call_SEV_after_time(timeout); + // } struct ethosu_semaphore_t *s = sem; while (s->count == 0) { __WFE(); + // Baremetal pseudo-example check if timeout triggered: + // if (SEV_timer_triggered()) { + // return -1; + // } } s->count--; return 0; @@ -263,7 +272,7 @@ static int ethosu_deregister_driver(struct ethosu_driver *drv) { *prev = curr->next; LOG_INFO("NPU driver handle %p deregistered.", drv); - ethosu_semaphore_take(ethosu_semaphore); + ethosu_semaphore_take(ethosu_semaphore, ETHOSU_SEMAPHORE_WAIT_FOREVER); break; } @@ -317,7 +326,7 @@ static int handle_command_stream(struct ethosu_driver *drv, const uint8_t *cmd_s { if (0 != (drv->job.base_addr[i] & MASK_16_BYTE_ALIGN)) { - LOG_ERR("Base addr %d: 0x%llx not aligned to 16 bytes", i, drv->job.base_addr[i]); + LOG_ERR("Base addr %d: 0x%" PRIx64 "not aligned to 16 bytes", i, drv->job.base_addr[i]); return -1; } } @@ -365,11 +374,15 @@ void __attribute__((weak)) ethosu_irq_handler(struct ethosu_driver *drv) { LOG_DEBUG("Got interrupt from Ethos-U"); - drv->job.state = ETHOSU_JOB_DONE; - if (!ethosu_dev_handle_interrupt(drv->dev)) + // Prevent race condition where interrupt triggered after a timeout waiting + // for semaphore, but before NPU is reset. + if (drv->job.result == ETHOSU_JOB_RESULT_TIMEOUT) { - drv->status_error = true; + return; } + + drv->job.state = ETHOSU_JOB_DONE; + drv->job.result = ethosu_dev_handle_interrupt(drv->dev) ? ETHOSU_JOB_RESULT_OK : ETHOSU_JOB_RESULT_ERROR; ethosu_semaphore_give(drv->semaphore); } @@ -395,6 +408,11 @@ int ethosu_init(struct ethosu_driver *drv, if (!ethosu_mutex) { ethosu_mutex = ethosu_mutex_create(); + if (!ethosu_mutex) + { + LOG_ERR("Failed to create global driver mutex"); + return -1; + } } if (!ethosu_semaphore) @@ -407,7 +425,7 @@ int ethosu_init(struct ethosu_driver *drv, } } - drv->fast_memory = (uint32_t)fast_memory; + drv->fast_memory = (uintptr_t)fast_memory; drv->fast_memory_size = fast_memory_size; drv->power_request_counter = 0; @@ -429,8 +447,6 @@ int ethosu_init(struct ethosu_driver *drv, return -1; } - drv->status_error = false; - ethosu_reset_job(drv); ethosu_register_driver(drv); @@ -530,29 +546,48 @@ int ethosu_wait(struct ethosu_driver *drv, bool block) case ETHOSU_JOB_DONE: // Wait for interrupt in blocking mode. In non-blocking mode // the interrupt has already triggered - ethosu_semaphore_take(drv->semaphore); + ret = ethosu_semaphore_take(drv->semaphore, ETHOSU_SEMAPHORE_WAIT_INFERENCE); + if (ret < 0) + { + drv->job.result = ETHOSU_JOB_RESULT_TIMEOUT; + + // There's a race where the NPU interrupt can have fired between semaphore + // timing out and setting the result above (checked in interrupt handler). + // By checking if the job state has been changed (only set to DONE by interrupt + // handler), we know if the interrupt handler has run, if so decrement the + // semaphore count by one (given in interrupt handler). + if (drv->job.state == ETHOSU_JOB_DONE) + { + drv->job.result = ETHOSU_JOB_RESULT_TIMEOUT; // Reset back to timeout + ethosu_semaphore_take(drv->semaphore, ETHOSU_SEMAPHORE_WAIT_INFERENCE); + } + } - // Inference done callback + // Inference done callback - always called even in case of timeout ethosu_inference_end(drv, drv->job.user_arg); - // Relase power gating disabled requirement + // Release power gating disabled requirement ethosu_release_power(drv); // Check NPU and interrupt status - if (drv->status_error) + if (drv->job.result) { - LOG_ERR("NPU error(s) occured during inference."); - ethosu_dev_print_err_status(drv->dev); + if (drv->job.result == ETHOSU_JOB_RESULT_ERROR) + { + LOG_ERR("NPU error(s) occured during inference."); + ethosu_dev_print_err_status(drv->dev); + } + else + { + LOG_ERR("NPU inference timed out."); + } // Reset the NPU (void)ethosu_soft_reset(drv); - // NPU is no longer in error state - drv->status_error = false; ret = -1; } - - if (ret == 0) + else { // Invalidate cache if (drv->job.base_addr_size != NULL) @@ -568,6 +603,7 @@ int ethosu_wait(struct ethosu_driver *drv, bool block) } LOG_DEBUG("Inference finished successfully..."); + ret = 0; } // Reset internal job (state resets to IDLE) @@ -634,7 +670,7 @@ int ethosu_invoke_async(struct ethosu_driver *drv, if (base_addr_size != NULL && base_addr_size[FAST_MEMORY_BASE_ADDR_INDEX] > drv->fast_memory_size) { - LOG_ERR("Fast memory area too small. fast_memory_size=%u, base_addr_size=%u", + LOG_ERR("Fast memory area too small. fast_memory_size=%zu, base_addr_size=%zu", drv->fast_memory_size, base_addr_size[FAST_MEMORY_BASE_ADDR_INDEX]); goto err; @@ -643,8 +679,6 @@ int ethosu_invoke_async(struct ethosu_driver *drv, base_addr[FAST_MEMORY_BASE_ADDR_INDEX] = drv->fast_memory; } - drv->status_error = false; - // Parse Custom Operator Payload data while (data_ptr < data_end) { @@ -712,7 +746,7 @@ struct ethosu_driver *ethosu_reserve_driver(void) struct ethosu_driver *drv = NULL; LOG_INFO("Acquiring NPU driver handle"); - ethosu_semaphore_take(ethosu_semaphore); // This is meant to block until available + ethosu_semaphore_take(ethosu_semaphore, ETHOSU_SEMAPHORE_WAIT_FOREVER); // This is meant to block until available ethosu_mutex_lock(ethosu_mutex); drv = registered_drivers; @@ -751,7 +785,6 @@ void ethosu_release_driver(struct ethosu_driver *drv) drv->power_request_counter = 0; ethosu_soft_reset(drv); ethosu_reset_job(drv); - drv->status_error = false; } } diff --git a/src/ethosu_pmu.c b/src/ethosu_pmu.c index b5925c4..0b58c6f 100644 --- a/src/ethosu_pmu.c +++ b/src/ethosu_pmu.c @@ -1,6 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2019-2023 Arm Limited and/or its affiliates <open-source-office@arm.com> - * + * SPDX-FileCopyrightText: Copyright 2019-2024 Arm Limited and/or its affiliates <open-source-office@arm.com> * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the License); you may @@ -75,10 +74,8 @@ static uint32_t pmu_event_value(enum ethosu_pmu_event_type event) { return eventbyid[event]; } - else - { - return (uint32_t)(-1); - } + + return UINT32_MAX; } /***************************************************************************** @@ -110,6 +107,12 @@ void ETHOSU_PMU_Set_EVTYPER(struct ethosu_driver *drv, uint32_t num, enum ethosu { assert(num < ETHOSU_PMU_NCOUNTERS); uint32_t val = pmu_event_value(type); + if (val == UINT32_MAX) + { + LOG_ERR("Invalid ethosu_pmu_event_type: %d", type); + return; + } + LOG_DEBUG("num=%" PRIu32 ", type=%d, val=%" PRIu32, num, type, val); drv->dev->reg->PMEVTYPER[num].word = val; } @@ -269,8 +272,14 @@ void ETHOSU_PMU_CNTR_Increment(struct ethosu_driver *drv, uint32_t mask) void ETHOSU_PMU_PMCCNTR_CFG_Set_Start_Event(struct ethosu_driver *drv, enum ethosu_pmu_event_type start_event) { LOG_DEBUG("start_event=%u", start_event); - uint32_t val = pmu_event_value(start_event); struct pmccntr_cfg_r cfg; + uint32_t val = pmu_event_value(start_event); + if (val == UINT32_MAX) + { + LOG_ERR("Invalid ethosu_pmu_event_type: %d", start_event); + return; + } + cfg.word = drv->dev->reg->PMCCNTR_CFG.word; cfg.CYCLE_CNT_CFG_START = val; drv->dev->reg->PMCCNTR_CFG.word = cfg.word; @@ -279,8 +288,14 @@ void ETHOSU_PMU_PMCCNTR_CFG_Set_Start_Event(struct ethosu_driver *drv, enum etho void ETHOSU_PMU_PMCCNTR_CFG_Set_Stop_Event(struct ethosu_driver *drv, enum ethosu_pmu_event_type stop_event) { LOG_DEBUG("stop_event=%u", stop_event); - uint32_t val = pmu_event_value(stop_event); struct pmccntr_cfg_r cfg; + uint32_t val = pmu_event_value(stop_event); + if (val == UINT32_MAX) + { + LOG_ERR("Invalid ethosu_pmu_event_type: %d", stop_event); + return; + } + cfg.word = drv->dev->reg->PMCCNTR_CFG.word; cfg.CYCLE_CNT_CFG_STOP = val; drv->dev->reg->PMCCNTR_CFG.word = cfg.word; |