diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 17d1f371a..1f7ac440e 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -280,6 +280,7 @@ SOURCE_MICROPYTHON_SPEED = [ SOURCE_STMHAL = [ 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c', 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c', @@ -309,6 +310,7 @@ SOURCE_FIRMWARE = [ SOURCE_TREZORHAL = [ 'embed/trezorhal/common.c', + 'embed/trezorhal/dma.c', 'embed/trezorhal/flash.c', 'embed/trezorhal/mini_printf.c', 'embed/trezorhal/mpu.c', diff --git a/core/embed/firmware/mpconfigport.h b/core/embed/firmware/mpconfigport.h index f53734734..9e0befdbf 100644 --- a/core/embed/firmware/mpconfigport.h +++ b/core/embed/firmware/mpconfigport.h @@ -211,6 +211,7 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_HW_BOARD_NAME "TREZORv2" #define MICROPY_HW_MCU_NAME "STM32F427xx" #define MICROPY_PY_SYS_PLATFORM "trezor" +#define MICROPY_HW_HAS_SDCARD 1 // There is no classical C heap in bare-metal ports, only Python // garbage-collected heap. For completeness, emulate C heap via diff --git a/core/embed/trezorhal/dma.c b/core/embed/trezorhal/dma.c new file mode 100644 index 000000000..897773ffd --- /dev/null +++ b/core/embed/trezorhal/dma.c @@ -0,0 +1,305 @@ +// clang-format off + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/obj.h" +#include "dma.h" +#include "irq.h" +#include "systick.h" + +#define DMA_IDLE_ENABLED() (dma_idle.enabled != 0) +#define DMA_SYSTICK_LOG2 (3) +#define DMA_SYSTICK_MASK ((1 << DMA_SYSTICK_LOG2) - 1) +#define DMA_IDLE_TICK_MAX (8) // 8*8 = 64 msec +#define DMA_IDLE_TICK(tick) (((tick) & ~(SYSTICK_DISPATCH_NUM_SLOTS - 1) & DMA_SYSTICK_MASK) == 0) + +typedef enum { + dma_id_not_defined=-1, + dma_id_0, + dma_id_1, + dma_id_2, + dma_id_3, + dma_id_4, + dma_id_5, + dma_id_6, + dma_id_7, + dma_id_8, + dma_id_9, + dma_id_10, + dma_id_11, + dma_id_12, + dma_id_13, + dma_id_14, + dma_id_15, +} dma_id_t; + +typedef union { + uint16_t enabled; // Used to test if both counters are == 0 + uint8_t counter[2]; +} dma_idle_count_t; + +struct _dma_descr_t { + DMA_Stream_TypeDef *instance; + uint32_t sub_instance; + dma_id_t id; + const DMA_InitTypeDef *init; +}; + +// Parameters to dma_init() for SDIO tx and rx. +static const DMA_InitTypeDef dma_init_struct_sdio = { + .Channel = 0, + .Direction = 0, + .PeriphInc = DMA_PINC_DISABLE, + .MemInc = DMA_MINC_ENABLE, + .PeriphDataAlignment = DMA_PDATAALIGN_WORD, + .MemDataAlignment = DMA_MDATAALIGN_WORD, + .Mode = DMA_PFCTRL, + .Priority = DMA_PRIORITY_VERY_HIGH, + .FIFOMode = DMA_FIFOMODE_ENABLE, + .FIFOThreshold = DMA_FIFO_THRESHOLD_FULL, + .MemBurst = DMA_MBURST_INC4, + .PeriphBurst = DMA_PBURST_INC4, +}; + +#define NCONTROLLERS (2) +#define NSTREAMS_PER_CONTROLLER (8) +#define NSTREAM (NCONTROLLERS * NSTREAMS_PER_CONTROLLER) + +#define DMA_SUB_INSTANCE_AS_UINT8(dma_channel) (((dma_channel) & DMA_SxCR_CHSEL) >> 25) + +#define DMA1_ENABLE_MASK (0x00ff) // Bits in dma_enable_mask corresponding to DMA1 +#define DMA2_ENABLE_MASK (0xff00) // Bits in dma_enable_mask corresponding to DMA2 + +const dma_descr_t dma_SDIO_0 = { DMA2_Stream3, DMA_CHANNEL_4, dma_id_11, &dma_init_struct_sdio }; + +static const uint8_t dma_irqn[NSTREAM] = { + DMA1_Stream0_IRQn, + DMA1_Stream1_IRQn, + DMA1_Stream2_IRQn, + DMA1_Stream3_IRQn, + DMA1_Stream4_IRQn, + DMA1_Stream5_IRQn, + DMA1_Stream6_IRQn, + DMA1_Stream7_IRQn, + DMA2_Stream0_IRQn, + DMA2_Stream1_IRQn, + DMA2_Stream2_IRQn, + DMA2_Stream3_IRQn, + DMA2_Stream4_IRQn, + DMA2_Stream5_IRQn, + DMA2_Stream6_IRQn, + DMA2_Stream7_IRQn, +}; + +static DMA_HandleTypeDef *dma_handle[NSTREAM] = {NULL}; +static uint8_t dma_last_sub_instance[NSTREAM]; +static volatile uint32_t dma_enable_mask = 0; +volatile dma_idle_count_t dma_idle; + +#define DMA_INVALID_CHANNEL 0xff // Value stored in dma_last_channel which means invalid + +#define DMA1_IS_CLK_ENABLED() ((RCC->AHB1ENR & RCC_AHB1ENR_DMA1EN) != 0) +#define DMA2_IS_CLK_ENABLED() ((RCC->AHB1ENR & RCC_AHB1ENR_DMA2EN) != 0) + +void DMA1_Stream0_IRQHandler(void) { IRQ_ENTER(DMA1_Stream0_IRQn); if (dma_handle[dma_id_0] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_0]); } IRQ_EXIT(DMA1_Stream0_IRQn); } +void DMA1_Stream1_IRQHandler(void) { IRQ_ENTER(DMA1_Stream1_IRQn); if (dma_handle[dma_id_1] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_1]); } IRQ_EXIT(DMA1_Stream1_IRQn); } +void DMA1_Stream2_IRQHandler(void) { IRQ_ENTER(DMA1_Stream2_IRQn); if (dma_handle[dma_id_2] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_2]); } IRQ_EXIT(DMA1_Stream2_IRQn); } +void DMA1_Stream3_IRQHandler(void) { IRQ_ENTER(DMA1_Stream3_IRQn); if (dma_handle[dma_id_3] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_3]); } IRQ_EXIT(DMA1_Stream3_IRQn); } +void DMA1_Stream4_IRQHandler(void) { IRQ_ENTER(DMA1_Stream4_IRQn); if (dma_handle[dma_id_4] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_4]); } IRQ_EXIT(DMA1_Stream4_IRQn); } +void DMA1_Stream5_IRQHandler(void) { IRQ_ENTER(DMA1_Stream5_IRQn); if (dma_handle[dma_id_5] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_5]); } IRQ_EXIT(DMA1_Stream5_IRQn); } +void DMA1_Stream6_IRQHandler(void) { IRQ_ENTER(DMA1_Stream6_IRQn); if (dma_handle[dma_id_6] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_6]); } IRQ_EXIT(DMA1_Stream6_IRQn); } +void DMA1_Stream7_IRQHandler(void) { IRQ_ENTER(DMA1_Stream7_IRQn); if (dma_handle[dma_id_7] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_7]); } IRQ_EXIT(DMA1_Stream7_IRQn); } +void DMA2_Stream0_IRQHandler(void) { IRQ_ENTER(DMA2_Stream0_IRQn); if (dma_handle[dma_id_8] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_8]); } IRQ_EXIT(DMA2_Stream0_IRQn); } +void DMA2_Stream1_IRQHandler(void) { IRQ_ENTER(DMA2_Stream1_IRQn); if (dma_handle[dma_id_9] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_9]); } IRQ_EXIT(DMA2_Stream1_IRQn); } +void DMA2_Stream2_IRQHandler(void) { IRQ_ENTER(DMA2_Stream2_IRQn); if (dma_handle[dma_id_10] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_10]); } IRQ_EXIT(DMA2_Stream2_IRQn); } +void DMA2_Stream3_IRQHandler(void) { IRQ_ENTER(DMA2_Stream3_IRQn); if (dma_handle[dma_id_11] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_11]); } IRQ_EXIT(DMA2_Stream3_IRQn); } +void DMA2_Stream4_IRQHandler(void) { IRQ_ENTER(DMA2_Stream4_IRQn); if (dma_handle[dma_id_12] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_12]); } IRQ_EXIT(DMA2_Stream4_IRQn); } +void DMA2_Stream5_IRQHandler(void) { IRQ_ENTER(DMA2_Stream5_IRQn); if (dma_handle[dma_id_13] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_13]); } IRQ_EXIT(DMA2_Stream5_IRQn); } +void DMA2_Stream6_IRQHandler(void) { IRQ_ENTER(DMA2_Stream6_IRQn); if (dma_handle[dma_id_14] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_14]); } IRQ_EXIT(DMA2_Stream6_IRQn); } +void DMA2_Stream7_IRQHandler(void) { IRQ_ENTER(DMA2_Stream7_IRQn); if (dma_handle[dma_id_15] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_15]); } IRQ_EXIT(DMA2_Stream7_IRQn); } + +static void dma_idle_handler(uint32_t tick); + +// Resets the idle counter for the DMA controller associated with dma_id. +static void dma_tickle(dma_id_t dma_id) { + dma_idle.counter[(dma_id < NSTREAMS_PER_CONTROLLER) ? 0 : 1] = 1; + systick_enable_dispatch(SYSTICK_DISPATCH_DMA, dma_idle_handler); +} + +static void dma_enable_clock(dma_id_t dma_id) { + // We don't want dma_tick_handler() to turn off the clock right after we + // enable it, so we need to mark the channel in use in an atomic fashion. + mp_uint_t irq_state = MICROPY_BEGIN_ATOMIC_SECTION(); + uint32_t old_enable_mask = dma_enable_mask; + dma_enable_mask |= (1 << dma_id); + MICROPY_END_ATOMIC_SECTION(irq_state); + + if (dma_id < NSTREAMS_PER_CONTROLLER) { + if (((old_enable_mask & DMA1_ENABLE_MASK) == 0) && !DMA1_IS_CLK_ENABLED()) { + __HAL_RCC_DMA1_CLK_ENABLE(); + + // We just turned on the clock. This means that anything stored + // in dma_last_channel (for DMA1) needs to be invalidated. + + for (int channel = 0; channel < NSTREAMS_PER_CONTROLLER; channel++) { + dma_last_sub_instance[channel] = DMA_INVALID_CHANNEL; + } + } + } + #if defined(DMA2) + else { + if (((old_enable_mask & DMA2_ENABLE_MASK) == 0) && !DMA2_IS_CLK_ENABLED()) { + __HAL_RCC_DMA2_CLK_ENABLE(); + + // We just turned on the clock. This means that anything stored + // in dma_last_channel (for DMA2) needs to be invalidated. + + for (int channel = NSTREAMS_PER_CONTROLLER; channel < NSTREAM; channel++) { + dma_last_sub_instance[channel] = DMA_INVALID_CHANNEL; + } + } + } + #endif +} + +static void dma_disable_clock(dma_id_t dma_id) { + // We just mark the clock as disabled here, but we don't actually disable it. + // We wait for the timer to expire first, which means that back-to-back + // transfers don't have to initialize as much. + dma_tickle(dma_id); + dma_enable_mask &= ~(1 << dma_id); +} + +void dma_init_handle(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir, void *data) { + // initialise parameters + dma->Instance = dma_descr->instance; + dma->Init = *dma_descr->init; + dma->Init.Direction = dir; + dma->Init.Channel = dma_descr->sub_instance; + // half of __HAL_LINKDMA(data, xxx, *dma) + // caller must implement other half by doing: data->xxx = dma + dma->Parent = data; +} + +void dma_init(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir, void *data){ + // Some drivers allocate the DMA_HandleTypeDef from the stack + // (i.e. dac, i2c, spi) and for those cases we need to clear the + // structure so we don't get random values from the stack) + memset(dma, 0, sizeof(*dma)); + + if (dma_descr != NULL) { + dma_id_t dma_id = dma_descr->id; + + dma_init_handle(dma, dma_descr, dir, data); + // set global pointer for IRQ handler + dma_handle[dma_id] = dma; + + dma_enable_clock(dma_id); + + // if this stream was previously configured for this channel/request and direction then we + // can skip most of the initialisation + uint8_t sub_inst = DMA_SUB_INSTANCE_AS_UINT8(dma_descr->sub_instance) | (dir == DMA_PERIPH_TO_MEMORY) << 7; + if (dma_last_sub_instance[dma_id] != sub_inst) { + dma_last_sub_instance[dma_id] = sub_inst; + + // reset and configure DMA peripheral + // (dma->State is set to HAL_DMA_STATE_RESET by memset above) + HAL_DMA_DeInit(dma); + HAL_DMA_Init(dma); + NVIC_SetPriority(IRQn_NONNEG(dma_irqn[dma_id]), IRQ_PRI_DMA); + } else { + // only necessary initialization + dma->State = HAL_DMA_STATE_READY; + // calculate DMA base address and bitshift to be used in IRQ handler + extern uint32_t DMA_CalcBaseAndBitshift(DMA_HandleTypeDef *hdma); + DMA_CalcBaseAndBitshift(dma); + } + + HAL_NVIC_EnableIRQ(dma_irqn[dma_id]); + } +} + +void dma_deinit(const dma_descr_t *dma_descr) { + if (dma_descr != NULL) { + HAL_NVIC_DisableIRQ(dma_irqn[dma_descr->id]); + dma_handle[dma_descr->id] = NULL; + + dma_disable_clock(dma_descr->id); + } +} + +void dma_invalidate_channel(const dma_descr_t *dma_descr) { + if (dma_descr != NULL) { + dma_id_t dma_id = dma_descr->id; + // Only compare the sub-instance, not the direction bit (MSB) + if ((dma_last_sub_instance[dma_id] & 0x7f) == DMA_SUB_INSTANCE_AS_UINT8(dma_descr->sub_instance) ) { + dma_last_sub_instance[dma_id] = DMA_INVALID_CHANNEL; + } + } +} +// Called from the SysTick handler +// We use LSB of tick to select which controller to process +static void dma_idle_handler(uint32_t tick) { + if (!DMA_IDLE_ENABLED() || !DMA_IDLE_TICK(tick)) { + return; + } + + static const uint32_t controller_mask[] = { + DMA1_ENABLE_MASK, + #if defined(DMA2) + DMA2_ENABLE_MASK, + #endif + }; + { + int controller = (tick >> DMA_SYSTICK_LOG2) & 1; + if (dma_idle.counter[controller] == 0) { + return; + } + if (++dma_idle.counter[controller] > DMA_IDLE_TICK_MAX) { + if ((dma_enable_mask & controller_mask[controller]) == 0) { + // Nothing is active and we've reached our idle timeout, + // Now we'll really disable the clock. + dma_idle.counter[controller] = 0; + if (controller == 0) { + __HAL_RCC_DMA1_CLK_DISABLE(); + } + #if defined(DMA2) + else { + __HAL_RCC_DMA2_CLK_DISABLE(); + } + #endif + } else { + // Something is still active, but the counter never got + // reset, so we'll reset the counter here. + dma_idle.counter[controller] = 1; + } + } + } +} diff --git a/core/embed/trezorhal/dma.h b/core/embed/trezorhal/dma.h new file mode 100644 index 000000000..a1a5883c4 --- /dev/null +++ b/core/embed/trezorhal/dma.h @@ -0,0 +1,46 @@ +// clang-format off + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_DMA_H +#define MICROPY_INCLUDED_STM32_DMA_H + +#include STM32_HAL_H + +typedef struct _dma_descr_t dma_descr_t; + +extern const dma_descr_t dma_SDIO_0; + +void dma_init(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir, void *data); +void dma_init_handle(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir, void *data); +void dma_deinit(const dma_descr_t *dma_descr); +void dma_invalidate_channel(const dma_descr_t *dma_descr); + +void dma_nohal_init(const dma_descr_t *descr, uint32_t config); +void dma_nohal_deinit(const dma_descr_t *descr); +void dma_nohal_start(const dma_descr_t *descr, uint32_t src_addr, uint32_t dst_addr, uint16_t len); + +#endif // MICROPY_INCLUDED_STM32_DMA_H diff --git a/core/embed/trezorhal/irq.h b/core/embed/trezorhal/irq.h new file mode 100644 index 000000000..52d90c09a --- /dev/null +++ b/core/embed/trezorhal/irq.h @@ -0,0 +1,150 @@ +// clang-format off + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_IRQ_H +#define MICROPY_INCLUDED_STM32_IRQ_H + +// Use this macro together with NVIC_SetPriority to indicate that an IRQn is non-negative, +// which helps the compiler optimise the resulting inline function. +#define IRQn_NONNEG(pri) ((pri) & 0x7f) + +// these states correspond to values from query_irq, enable_irq and disable_irq +#define IRQ_STATE_DISABLED (0x00000001) +#define IRQ_STATE_ENABLED (0x00000000) + +// Enable this to get a count for the number of times each irq handler is called, +// accessible via pyb.irq_stats(). +#define IRQ_ENABLE_STATS (0) + +#if IRQ_ENABLE_STATS +#define IRQ_STATS_MAX (128) +extern uint32_t irq_stats[IRQ_STATS_MAX]; +#define IRQ_ENTER(irq) ++irq_stats[irq] +#define IRQ_EXIT(irq) +#else +#define IRQ_ENTER(irq) +#define IRQ_EXIT(irq) +#endif + +static inline uint32_t query_irq(void) { + return __get_PRIMASK(); +} + +// enable_irq and disable_irq are defined inline in mpconfigport.h + +#if __CORTEX_M >= 0x03 + +// irqs with a priority value greater or equal to "pri" will be disabled +// "pri" should be between 1 and 15 inclusive +static inline uint32_t raise_irq_pri(uint32_t pri) { + uint32_t basepri = __get_BASEPRI(); + // If non-zero, the processor does not process any exception with a + // priority value greater than or equal to BASEPRI. + // When writing to BASEPRI_MAX the write goes to BASEPRI only if either: + // - Rn is non-zero and the current BASEPRI value is 0 + // - Rn is non-zero and less than the current BASEPRI value + pri <<= (8 - __NVIC_PRIO_BITS); + __ASM volatile ("msr basepri_max, %0" : : "r" (pri) : "memory"); + return basepri; +} + +// "basepri" should be the value returned from raise_irq_pri +static inline void restore_irq_pri(uint32_t basepri) { + __set_BASEPRI(basepri); +} + +#else + +static inline uint32_t raise_irq_pri(uint32_t pri) { + return disable_irq(); +} + +// "state" should be the value returned from raise_irq_pri +static inline void restore_irq_pri(uint32_t state) { + enable_irq(state); +} + +#endif + +// IRQ priority definitions. +// +// Lower number implies higher interrupt priority. +// +// The default priority grouping is set to NVIC_PRIORITYGROUP_4 in the +// HAL_Init function. This corresponds to 4 bits for the priority field +// and 0 bits for the sub-priority field (which means that for all intensive +// purposes that the sub-priorities below are ignored). +// +// While a given interrupt is being processed, only higher priority (lower number) +// interrupts will preempt a given interrupt. If sub-priorities are active +// then the sub-priority determines the order that pending interrupts of +// a given priority are executed. This is only meaningful if 2 or more +// interrupts of the same priority are pending at the same time. +// +// The priority of the SysTick timer is determined from the TICK_INT_PRIORITY +// value which is normally set to 0 in the stm32f4xx_hal_conf.h file. +// +// The following interrupts are arranged from highest priority to lowest +// priority to make it a bit easier to figure out. + +//#def IRQ_PRI_SYSTICK NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 0, 0) + +// The UARTs have no FIFOs, so if they don't get serviced quickly then characters +// get dropped. The handling for each character only consumes about 0.5 usec +#define IRQ_PRI_UART NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 1, 0) + +// SDIO must be higher priority than DMA for SDIO DMA transfers to work. +#define IRQ_PRI_SDIO NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 4, 0) + +// DMA should be higher priority than USB, since USB Mass Storage calls +// into the sdcard driver which waits for the DMA to complete. +#define IRQ_PRI_DMA NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 5, 0) + +// Flash IRQ (used for flushing storage cache) must be at the same priority as +// the USB IRQs, so that the IRQ priority can be raised to this level to disable +// both the USB and cache flushing, when storage transfers are in progress. +#define IRQ_PRI_FLASH NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 6, 0) + +#define IRQ_PRI_OTG_FS NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 6, 0) +#define IRQ_PRI_OTG_HS NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 6, 0) +#define IRQ_PRI_TIM5 NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 6, 0) + +#define IRQ_PRI_CAN NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 7, 0) + +#define IRQ_PRI_SPI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0) + +// Interrupt priority for non-special timers. +#define IRQ_PRI_TIMX NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 13, 0) + +#define IRQ_PRI_EXTINT NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 14, 0) + +// PENDSV should be at the lowst priority so that other interrupts complete +// before exception is raised. +#define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 15, 0) +#define IRQ_PRI_RTC_WKUP NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 15, 0) + +#endif // MICROPY_INCLUDED_STM32_IRQ_H diff --git a/core/embed/trezorhal/sdcard.c b/core/embed/trezorhal/sdcard.c index 22652adf8..15170774d 100644 --- a/core/embed/trezorhal/sdcard.c +++ b/core/embed/trezorhal/sdcard.c @@ -46,10 +46,18 @@ #include STM32_HAL_H #include +#include "mpconfigport.h" +#include "dma.h" +#include "irq.h" #include "sdcard-set_clr_card_detect.h" #include "sdcard.h" +#define SDMMC_CLK_ENABLE() __HAL_RCC_SDMMC1_CLK_ENABLE() +#define SDMMC_CLK_DISABLE() __HAL_RCC_SDMMC1_CLK_DISABLE() +#define SDMMC_IRQn SDMMC1_IRQn +#define SDMMC_DMA dma_SDIO_0 + static SD_HandleTypeDef sd_handle; static inline void sdcard_default_pin_state(void) { @@ -109,12 +117,24 @@ static inline void sdcard_active_pin_state(void) { void sdcard_init(void) { sdcard_default_pin_state(); } void HAL_SD_MspInit(SD_HandleTypeDef *hsd) { - // enable SDIO clock - __HAL_RCC_SDIO_CLK_ENABLE(); + if (hsd->Instance == sd_handle.Instance) { + // enable SDIO clock + SDMMC_CLK_ENABLE(); + + // NVIC configuration for SDIO interrupts + NVIC_SetPriority(SDMMC_IRQn, IRQ_PRI_SDIO); + HAL_NVIC_EnableIRQ(SDMMC_IRQn); + } + // GPIO have already been initialised by sdcard_init } -void HAL_SD_MspDeInit(SD_HandleTypeDef *hsd) { __HAL_RCC_SDIO_CLK_DISABLE(); } +void HAL_SD_MspDeInit(SD_HandleTypeDef *hsd) { + if (hsd->Instance == sd_handle.Instance) { + HAL_NVIC_DisableIRQ(SDMMC_IRQn); + SDMMC_CLK_DISABLE(); + } +} secbool sdcard_power_on(void) { if (sectrue != sdcard_is_present()) { @@ -192,15 +212,40 @@ uint64_t sdcard_get_capacity_in_bytes(void) { return (uint64_t)cardinfo.LogBlockNbr * (uint64_t)cardinfo.LogBlockSize; } +void SDIO_IRQHandler(void) { + IRQ_ENTER(SDIO_IRQn); + HAL_SD_IRQHandler(&sd_handle); + IRQ_EXIT(SDIO_IRQn); +} + +static void sdcard_reset_periph(void) { + // Fully reset the SDMMC peripheral before calling HAL SD DMA functions. + // (There could be an outstanding DTIMEOUT event from a previous call and the + // HAL function enables IRQs before fully configuring the SDMMC peripheral.) + SDIO->DTIMER = 0; + SDIO->DLEN = 0; + SDIO->DCTRL = 0; + SDIO->ICR = SDMMC_STATIC_FLAGS; +} + static HAL_StatusTypeDef sdcard_wait_finished(SD_HandleTypeDef *sd, uint32_t timeout) { // Wait for HAL driver to be ready (eg for DMA to finish) uint32_t start = HAL_GetTick(); - while (sd->State == HAL_SD_STATE_BUSY) { + for (;;) { + // Do an atomic check of the state; WFI will exit even if IRQs are disabled + uint32_t irq_state = disable_irq(); + if (sd->State != HAL_SD_STATE_BUSY) { + enable_irq(irq_state); + break; + } + __WFI(); + enable_irq(irq_state); if (HAL_GetTick() - start >= timeout) { return HAL_TIMEOUT; } } + // Wait for SD card to complete the operation for (;;) { HAL_SD_CardStateTypeDef state = HAL_SD_GetCardState(sd); @@ -214,6 +259,7 @@ static HAL_StatusTypeDef sdcard_wait_finished(SD_HandleTypeDef *sd, if (HAL_GetTick() - start >= timeout) { return HAL_TIMEOUT; } + __WFI(); } return HAL_OK; } @@ -232,12 +278,33 @@ secbool sdcard_read_blocks(uint32_t *dest, uint32_t block_num, HAL_StatusTypeDef err = HAL_OK; - err = HAL_SD_ReadBlocks(&sd_handle, (uint8_t *)dest, block_num, num_blocks, - 60000); + // we must disable USB irqs to prevent MSC contention with SD card + uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + + DMA_HandleTypeDef sd_dma; + dma_init(&sd_dma, &SDMMC_DMA, DMA_PERIPH_TO_MEMORY, &sd_handle); + sd_handle.hdmarx = &sd_dma; + + // we need to assign hdmatx even though it's unused + // because STMHAL tries to access its error code in SD_DMAError() + // even though it shouldn't :-/ + // this will get removed eventually when we update to new STMHAL + DMA_HandleTypeDef dummy_dma; + memset(&dummy_dma, 0, sizeof(dummy_dma)); + sd_handle.hdmatx = &dummy_dma; + + sdcard_reset_periph(); + err = + HAL_SD_ReadBlocks_DMA(&sd_handle, (uint8_t *)dest, block_num, num_blocks); if (err == HAL_OK) { err = sdcard_wait_finished(&sd_handle, 60000); } + dma_deinit(&SDMMC_DMA); + sd_handle.hdmarx = NULL; + + restore_irq_pri(basepri); + return sectrue * (err == HAL_OK); } @@ -255,11 +322,32 @@ secbool sdcard_write_blocks(const uint32_t *src, uint32_t block_num, HAL_StatusTypeDef err = HAL_OK; - err = HAL_SD_WriteBlocks(&sd_handle, (uint8_t *)src, block_num, num_blocks, - 60000); + // we must disable USB irqs to prevent MSC contention with SD card + uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + + DMA_HandleTypeDef sd_dma; + dma_init(&sd_dma, &SDMMC_DMA, DMA_MEMORY_TO_PERIPH, &sd_handle); + sd_handle.hdmatx = &sd_dma; + + // we need to assign hdmarx even though it's unused + // because HAL tries to access its error code in SD_DMAError() + // even though it shouldn't :-/ + // this will get removed eventually when we update to new STMHAL + DMA_HandleTypeDef dummy_dma; + memset(&dummy_dma, 0, sizeof(dummy_dma)); + sd_handle.hdmarx = &dummy_dma; + + sdcard_reset_periph(); + err = + HAL_SD_WriteBlocks_DMA(&sd_handle, (uint8_t *)src, block_num, num_blocks); if (err == HAL_OK) { err = sdcard_wait_finished(&sd_handle, 60000); } + dma_deinit(&SDMMC_DMA); + sd_handle.hdmatx = NULL; + + restore_irq_pri(basepri); + return sectrue * (err == HAL_OK); } diff --git a/core/embed/trezorhal/stm32.c b/core/embed/trezorhal/stm32.c index 6a416131d..200f64167 100644 --- a/core/embed/trezorhal/stm32.c +++ b/core/embed/trezorhal/stm32.c @@ -86,14 +86,6 @@ void SystemInit(void) { SCB->CPACR |= ((3U << 22) | (3U << 20)); } -extern volatile uint32_t uwTick; - -void SysTick_Handler(void) { - // this is a millisecond tick counter that wraps after approximately - // 49.71 days = (0xffffffff / (24 * 60 * 60 * 1000)) - uwTick++; -} - // from util.s extern void shutdown(void); diff --git a/core/embed/trezorhal/systick.c b/core/embed/trezorhal/systick.c index 94e7a61cd..4d69cf9b6 100644 --- a/core/embed/trezorhal/systick.c +++ b/core/embed/trezorhal/systick.c @@ -47,12 +47,24 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "ports/stm32/irq.h" -#include "ports/stm32/systick.h" -#include "ports/stm32/pybthread.h" +#include "irq.h" +#include "systick.h" extern __IO uint32_t uwTick; +systick_dispatch_t systick_dispatch_table[SYSTICK_DISPATCH_NUM_SLOTS]; + +void SysTick_Handler(void) { + // this is a millisecond tick counter that wraps after approximately + // 49.71 days = (0xffffffff / (24 * 60 * 60 * 1000)) + uint32_t uw_tick = uwTick + 1; + uwTick = uw_tick; + systick_dispatch_t f = systick_dispatch_table[uw_tick & (SYSTICK_DISPATCH_NUM_SLOTS - 1)]; + if (f != NULL) { + f(uw_tick); + } +} + // We provide our own version of HAL_Delay that calls __WFI while waiting, // and works when interrupts are disabled. This function is intended to be // used only by the ST HAL functions. diff --git a/core/embed/trezorhal/systick.h b/core/embed/trezorhal/systick.h new file mode 100644 index 000000000..99421ff21 --- /dev/null +++ b/core/embed/trezorhal/systick.h @@ -0,0 +1,56 @@ +// clang-format off + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_SYSTICK_H +#define MICROPY_INCLUDED_STM32_SYSTICK_H + +#include +#include + +// Works for x between 0 and 16 inclusive +#define POW2_CEIL(x) ((((x) - 1) | ((x) - 1) >> 1 | ((x) - 1) >> 2 | ((x) - 1) >> 3) + 1) + +enum { + SYSTICK_DISPATCH_DMA = 0, + SYSTICK_DISPATCH_MAX +}; + +#define SYSTICK_DISPATCH_NUM_SLOTS POW2_CEIL(SYSTICK_DISPATCH_MAX) + +typedef void (*systick_dispatch_t)(uint32_t); + +extern systick_dispatch_t systick_dispatch_table[SYSTICK_DISPATCH_NUM_SLOTS]; + +static inline void systick_enable_dispatch(size_t slot, systick_dispatch_t f) { + systick_dispatch_table[slot] = f; +} + +static inline void systick_disable_dispatch(size_t slot) { + systick_dispatch_table[slot] = NULL; +} + +#endif // MICROPY_INCLUDED_STM32_SYSTICK_H