Skip to content
30 changes: 15 additions & 15 deletions furi/core/check.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ FURI_NORETURN void __furi_halt_implementation(void);
#define furi_halt(...) M_APPLY(__furi_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))

/** Check condition and crash if check failed */
#define __furi_check(__e, __m) \
do { \
if(!(__e)) { \
__furi_crash(__m); \
} \
#define __furi_check(__e, __m) \
do { \
if(__builtin_expect(!(__e), 0)) { \
__furi_crash(__m); \
} \
} while(0)

/** Check condition and crash if failed
Expand All @@ -75,11 +75,11 @@ FURI_NORETURN void __furi_halt_implementation(void);

/** Only in debug build: Assert condition and crash if assert failed */
#ifdef FURI_DEBUG
#define __furi_assert(__e, __m) \
do { \
if(!(__e)) { \
__furi_crash(__m); \
} \
#define __furi_assert(__e, __m) \
do { \
if(__builtin_expect(!(__e), 0)) { \
__furi_crash(__m); \
} \
} while(0)
#else
#define __furi_assert(__e, __m) \
Expand All @@ -98,11 +98,11 @@ FURI_NORETURN void __furi_halt_implementation(void);
#define furi_assert(...) \
M_APPLY(__furi_assert, M_DEFAULT_ARGS(2, (__FURI_ASSERT_MESSAGE_FLAG), __VA_ARGS__))

#define furi_break(__e) \
do { \
if(!(__e)) { \
asm volatile("bkpt 0"); \
} \
#define furi_break(__e) \
do { \
if(__builtin_expect(!(__e), 0)) { \
asm volatile("bkpt 0"); \
} \
} while(0)

#ifdef __cplusplus
Expand Down
2 changes: 1 addition & 1 deletion furi/core/kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ FuriStatus furi_delay_until_tick(uint32_t tick) {
return stat;
}

uint32_t furi_get_tick(void) {
__attribute__((flatten)) uint32_t furi_get_tick(void) {
TickType_t ticks;

if(furi_kernel_is_irq_or_masked() != 0U) {
Expand Down
5 changes: 4 additions & 1 deletion furi/core/memmgr.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "memmgr.h"
#include "memmgr_heap.h"
#include <string.h>
#include <furi_hal_memory.h>
#include <FreeRTOS.h>
Expand All @@ -25,7 +26,9 @@ void* realloc(void* ptr, size_t size) {

void* p = pvPortMalloc(size);
if(ptr != NULL) {
memcpy(p, ptr, size);
size_t old_size = memmgr_heap_get_block_size(ptr);
size_t copy_size = old_size < size ? old_size : size;
memcpy(p, ptr, copy_size);
vPortFree(ptr);
}

Expand Down
18 changes: 18 additions & 0 deletions furi/core/memmgr_heap.c
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,24 @@ size_t xPortGetMinimumEverFreeHeapSize(void) {
}
/*-----------------------------------------------------------*/

size_t memmgr_heap_get_block_size(const void* pv) {
const uint8_t* puc = (const uint8_t*)pv;
BlockLink_t* pxLink;

if(pv == NULL) {
return 0;
}

puc -= xHeapStructSize;
pxLink = (BlockLink_t*)(uintptr_t)puc;

Comment thread
WonderMr marked this conversation as resolved.
heapVALIDATE_BLOCK_POINTER(pxLink);
configASSERT(heapBLOCK_IS_ALLOCATED(pxLink) != 0);

return (pxLink->xBlockSize & ~heapBLOCK_ALLOCATED_BITMASK) - xHeapStructSize;
}
/*-----------------------------------------------------------*/

void xPortResetHeapMinimumEverFreeHeapSize(void) {
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
Expand Down
7 changes: 7 additions & 0 deletions furi/core/memmgr_heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ size_t memmgr_heap_get_max_free_block(void);
*/
void memmgr_heap_printf_free_blocks(void);

/** Get usable size of an allocated heap block
*
* @param ptr pointer to allocated memory
* @return usable size in bytes
*/
size_t memmgr_heap_get_block_size(const void* ptr);

#ifdef __cplusplus
}
#endif
33 changes: 28 additions & 5 deletions furi/core/string.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "string.h"
#include <m-string.h>
#include <stdio.h>

struct FuriString {
string_t string;
Expand Down Expand Up @@ -175,11 +176,33 @@ int furi_string_cat_printf(FuriString* v, const char format[], ...) {
}

int furi_string_cat_vprintf(FuriString* v, const char format[], va_list args) {
FuriString* string = furi_string_alloc();
int ret = furi_string_vprintf(string, format, args);
furi_string_cat(v, string);
furi_string_free(string);
return ret;
// In-place append: format directly into destination buffer at current offset
// Eliminates temporary string allocation (malloc + free) per call
va_list args_copy;
va_copy(args_copy, args);

size_t old_size = string_size(v->string);
char* ptr = m_str1ng_get_cstr(v->string);
size_t alloc = string_capacity(v->string);

int size = vsnprintf(&ptr[old_size], alloc - old_size, format, args);

if(size > 0 && (old_size + (size_t)size + 1 > alloc)) {
// Buffer too small — grow and retry
ptr = m_str1ng_fit2size(v->string, old_size + (size_t)size + 1);
size = vsnprintf(
&ptr[old_size], string_capacity(v->string) - old_size, format, args_copy);
}
Comment thread
WonderMr marked this conversation as resolved.
va_end(args_copy);

if(size >= 0) {
m_str1ng_set_size(v->string, old_size + (size_t)size);
} else {
// vsnprintf error — restore original string
ptr[old_size] = 0;
}

return size;
Comment thread
WonderMr marked this conversation as resolved.
}

bool furi_string_empty(const FuriString* v) {
Expand Down
6 changes: 3 additions & 3 deletions furi/core/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -458,11 +458,11 @@ int32_t furi_thread_get_return_code(FuriThread* thread) {
return thread->ret;
}

FuriThreadId furi_thread_get_current_id(void) {
__attribute__((flatten)) FuriThreadId furi_thread_get_current_id(void) {
return (FuriThreadId)xTaskGetCurrentTaskHandle();
}

FuriThread* furi_thread_get_current(void) {
__attribute__((flatten)) FuriThread* furi_thread_get_current(void) {
FuriThread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0);
return thread;
}
Expand Down Expand Up @@ -538,7 +538,7 @@ uint32_t furi_thread_flags_clear(uint32_t flags) {
return rflags;
}

uint32_t furi_thread_flags_get(void) {
__attribute__((flatten)) uint32_t furi_thread_flags_get(void) {
TaskHandle_t hTask;
uint32_t rflags;

Expand Down
2 changes: 1 addition & 1 deletion site_scons/firmwareopts.scons
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ else:
"NDEBUG",
],
CCFLAGS=[
"-Og",
"-Os",
],
)

Expand Down
5 changes: 3 additions & 2 deletions targets/f7/api_symbols.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,87.1,,
Version,+,87.2,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Expand Down Expand Up @@ -1703,7 +1703,7 @@ Function,+,furi_hal_spi_bus_handle_init,void,const FuriHalSpiBusHandle*
Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus*
Function,+,furi_hal_spi_bus_rx,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t"
Function,+,furi_hal_spi_bus_trx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t"
Function,+,furi_hal_spi_bus_trx_dma,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t"
Function,+,furi_hal_spi_bus_trx_dma,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t"
Function,+,furi_hal_spi_bus_tx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t"
Function,-,furi_hal_spi_config_deinit_early,void,
Function,-,furi_hal_spi_config_init,void,
Expand Down Expand Up @@ -2607,6 +2607,7 @@ Function,+,memmgr_get_minimum_free_heap,size_t,
Function,+,memmgr_get_total_heap,size_t,
Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId
Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId
Function,+,memmgr_heap_get_block_size,size_t,const void*
Function,+,memmgr_heap_get_max_free_block,size_t,
Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId
Function,+,memmgr_heap_printf_free_blocks,void,
Expand Down
82 changes: 70 additions & 12 deletions targets/f7/furi_hal/furi_hal_spi.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ bool furi_hal_spi_bus_tx(
furi_check(buffer);
furi_check(size > 0);

if(furi_kernel_is_running()) {
// Use DMA for TX when scheduler is running, freeing the CPU during transfer
bool ret = furi_hal_spi_bus_trx_dma(handle, buffer, NULL, size, timeout);
LL_SPI_ClearFlag_OVR(handle->bus->spi);
return ret;
}

// Polling fallback for pre-scheduler context
bool ret = true;

while(size > 0) {
Expand Down Expand Up @@ -193,7 +201,7 @@ static void spi_dma_isr(void* context) {

bool furi_hal_spi_bus_trx_dma(
const FuriHalSpiBusHandle* handle,
uint8_t* tx_buffer,
const uint8_t* tx_buffer,
uint8_t* rx_buffer,
size_t size,
uint32_t timeout_ms) {
Expand Down Expand Up @@ -227,9 +235,26 @@ bool furi_hal_spi_bus_trx_dma(
}

if(rx_buffer == NULL) {
// Only TX mode, do not use RX channel
// TX-only mode: set up RX DMA to drain incoming bytes (prevents OVR)
uint8_t dma_rx_dummy;

LL_DMA_InitTypeDef dma_config = {0};

// RX DMA channel: drain SPI RX FIFO into dummy byte (no increment)
dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR);
dma_config.MemoryOrM2MDstAddress = (uint32_t)&dma_rx_dummy;
dma_config.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
dma_config.Mode = LL_DMA_MODE_NORMAL;
dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_NOINCREMENT;
dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
dma_config.NbData = size;
dma_config.PeriphRequest = dma_rx_req;
dma_config.Priority = LL_DMA_PRIORITY_MEDIUM;
LL_DMA_Init(SPI_DMA_RX_DEF, &dma_config);

// TX DMA channel
dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR);
dma_config.MemoryOrM2MDstAddress = (uint32_t)tx_buffer;
dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
Expand All @@ -243,48 +268,70 @@ bool furi_hal_spi_bus_trx_dma(
dma_config.Priority = LL_DMA_PRIORITY_MEDIUM;
LL_DMA_Init(SPI_DMA_TX_DEF, &dma_config);

#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7
#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 && SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7
LL_DMA_ClearFlag_TC6(SPI_DMA);
LL_DMA_ClearFlag_TC7(SPI_DMA);
#else
#error Update this code. Would you kindly?
#endif

furi_hal_interrupt_set_isr(SPI_DMA_TX_IRQ, spi_dma_isr, NULL);
furi_hal_interrupt_set_isr(SPI_DMA_RX_IRQ, spi_dma_isr, NULL);

bool dma_tx_was_enabled = LL_SPI_IsEnabledDMAReq_TX(spi);
bool dma_rx_was_enabled = LL_SPI_IsEnabledDMAReq_RX(spi);
if(!dma_tx_was_enabled) {
LL_SPI_EnableDMAReq_TX(spi);
}
if(!dma_rx_was_enabled) {
LL_SPI_EnableDMAReq_RX(spi);
}

// acquire semaphore before enabling DMA
furi_check(furi_semaphore_acquire(spi_dma_completed, timeout_ms) == FuriStatusOk);

LL_DMA_EnableIT_TC(SPI_DMA_TX_DEF);
LL_DMA_EnableIT_TC(SPI_DMA_RX_DEF);
LL_DMA_EnableChannel(SPI_DMA_RX_DEF);
LL_DMA_EnableChannel(SPI_DMA_TX_DEF);

// and wait for it to be released (DMA transfer complete)
// wait for RX DMA complete (all bytes transmitted and drained)
if(furi_semaphore_acquire(spi_dma_completed, timeout_ms) != FuriStatusOk) {
ret = false;
FURI_LOG_E(TAG, "DMA timeout\r\n");
}

// Disable TC IRQ and clear pending TC flag BEFORE releasing the
// semaphore. On timeout the ISR may still fire and would try to
// re-release the binary semaphore, crashing furi_check.
LL_DMA_DisableIT_TC(SPI_DMA_RX_DEF);
#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 && SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7
LL_DMA_ClearFlag_TC6(SPI_DMA);
LL_DMA_ClearFlag_TC7(SPI_DMA);
#else
#error Update this code. Would you kindly?
#endif

// release semaphore, because we are using it as a flag
furi_semaphore_release(spi_dma_completed);
Comment thread
WonderMr marked this conversation as resolved.

LL_DMA_DisableIT_TC(SPI_DMA_TX_DEF);
LL_DMA_DisableChannel(SPI_DMA_TX_DEF);
LL_DMA_DisableChannel(SPI_DMA_RX_DEF);
if(!dma_tx_was_enabled) {
LL_SPI_DisableDMAReq_TX(spi);
}
furi_hal_interrupt_set_isr(SPI_DMA_TX_IRQ, NULL, NULL);
if(!dma_rx_was_enabled) {
LL_SPI_DisableDMAReq_RX(spi);
}
furi_hal_interrupt_set_isr(SPI_DMA_RX_IRQ, NULL, NULL);

LL_DMA_DeInit(SPI_DMA_TX_DEF);
LL_DMA_DeInit(SPI_DMA_RX_DEF);
} else {
// TRX or RX mode, use both channels
uint32_t tx_mem_increase_mode;

if(tx_buffer == NULL) {
// RX mode, use dummy data instead of TX buffer
tx_buffer = (uint8_t*)&dma_dummy_u32;
tx_buffer = (const uint8_t*)&dma_dummy_u32;
tx_mem_increase_mode = LL_DMA_MEMORY_NOINCREMENT;
} else {
tx_mem_increase_mode = LL_DMA_MEMORY_INCREMENT;
Expand Down Expand Up @@ -317,8 +364,9 @@ bool furi_hal_spi_bus_trx_dma(
dma_config.Priority = LL_DMA_PRIORITY_MEDIUM;
LL_DMA_Init(SPI_DMA_RX_DEF, &dma_config);

#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6
#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 && SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7
LL_DMA_ClearFlag_TC6(SPI_DMA);
LL_DMA_ClearFlag_TC7(SPI_DMA);
#else
#error Update this code. Would you kindly?
#endif
Expand Down Expand Up @@ -348,10 +396,20 @@ bool furi_hal_spi_bus_trx_dma(
ret = false;
FURI_LOG_E(TAG, "DMA timeout\r\n");
}
// release semaphore, because we are using it as a flag
furi_semaphore_release(spi_dma_completed);

// Disable TC IRQ and clear pending TC flag BEFORE releasing the
// semaphore. On timeout the ISR may still fire and would try to
// re-release the binary semaphore, crashing furi_check.
LL_DMA_DisableIT_TC(SPI_DMA_RX_DEF);
#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 && SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7
LL_DMA_ClearFlag_TC6(SPI_DMA);
LL_DMA_ClearFlag_TC7(SPI_DMA);
#else
Comment thread
WonderMr marked this conversation as resolved.
#error Update this code. Would you kindly?
#endif

// release semaphore, because we are using it as a flag
furi_semaphore_release(spi_dma_completed);

LL_DMA_DisableChannel(SPI_DMA_TX_DEF);
LL_DMA_DisableChannel(SPI_DMA_RX_DEF);
Expand Down
Loading
Loading