mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-30 08:20:21 +00:00
6836a7b7c5
* Separate expansion control and worker threads * Add edge case checks * Reduce expansion control thread stack size, add comments * Fix crash when disabling expansion modules * Show a different RPC icon for expansion modules * Restore expansion interrupt on changing logging settings * Improve responsiveness in heavy games at the expense of dropped frames * Improve furi_hal_serial API * Fix a typo * Remove too optimistic furi_check, replace with condition * Fix premature RX interrupt during serial configuration * Disable expansion interrupt if the handle was acquired * Do not use a timer callback Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
395 lines
14 KiB
C
395 lines
14 KiB
C
#include "furi_hal_serial_control.h"
|
|
#include "furi_hal_serial_types_i.h"
|
|
#include "furi_hal_serial.h"
|
|
|
|
#include <furi.h>
|
|
#include <toolbox/api_lock.h>
|
|
|
|
#define TAG "FuriHalSerialControl"
|
|
|
|
typedef enum {
|
|
FuriHalSerialControlMessageTypeStop,
|
|
FuriHalSerialControlMessageTypeAcquire,
|
|
FuriHalSerialControlMessageTypeRelease,
|
|
FuriHalSerialControlMessageTypeIsBusy,
|
|
FuriHalSerialControlMessageTypeLogging,
|
|
FuriHalSerialControlMessageTypeExpansionSetCallback,
|
|
FuriHalSerialControlMessageTypeExpansionIrq,
|
|
} FuriHalSerialControlMessageType;
|
|
|
|
typedef struct {
|
|
FuriHalSerialControlMessageType type;
|
|
FuriApiLock api_lock;
|
|
void* input;
|
|
void* output;
|
|
} FuriHalSerialControlMessage;
|
|
|
|
typedef struct {
|
|
const FuriHalSerialId id;
|
|
const uint32_t baud_rate;
|
|
} FuriHalSerialControlMessageInputLogging;
|
|
|
|
typedef struct {
|
|
const FuriHalSerialId id;
|
|
const FuriHalSerialControlExpansionCallback callback;
|
|
void* context;
|
|
} FuriHalSerialControlMessageExpCallback;
|
|
|
|
typedef struct {
|
|
FuriHalSerialHandle handles[FuriHalSerialIdMax];
|
|
FuriMessageQueue* queue;
|
|
FuriThread* thread;
|
|
|
|
// Logging
|
|
FuriHalSerialId log_config_serial_id;
|
|
uint32_t log_config_serial_baud_rate;
|
|
FuriLogHandler log_handler;
|
|
FuriHalSerialHandle* log_serial;
|
|
|
|
// Expansion detection
|
|
FuriHalSerialHandle* expansion_serial;
|
|
FuriHalSerialControlExpansionCallback expansion_cb;
|
|
void* expansion_ctx;
|
|
} FuriHalSerialControl;
|
|
|
|
FuriHalSerialControl* furi_hal_serial_control = NULL;
|
|
|
|
static void furi_hal_serial_control_log_callback(const uint8_t* data, size_t size, void* context) {
|
|
FuriHalSerialHandle* handle = context;
|
|
furi_hal_serial_tx(handle, data, size);
|
|
}
|
|
|
|
static void furi_hal_serial_control_expansion_irq_callback(void* context) {
|
|
UNUSED(context);
|
|
|
|
FuriHalSerialControlMessage message;
|
|
message.type = FuriHalSerialControlMessageTypeExpansionIrq;
|
|
message.api_lock = NULL;
|
|
furi_message_queue_put(furi_hal_serial_control->queue, &message, 0);
|
|
}
|
|
|
|
static void
|
|
furi_hal_serial_control_enable_expansion_irq(FuriHalSerialHandle* handle, bool enable) {
|
|
const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx);
|
|
|
|
if(enable) {
|
|
furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx);
|
|
furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL);
|
|
furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow);
|
|
} else {
|
|
furi_hal_gpio_remove_int_callback(gpio);
|
|
furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx);
|
|
}
|
|
}
|
|
|
|
static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) {
|
|
// Disable expansion module detection before reconfiguring UARTs
|
|
if(furi_hal_serial_control->expansion_serial) {
|
|
furi_hal_serial_control_enable_expansion_irq(
|
|
furi_hal_serial_control->expansion_serial, false);
|
|
}
|
|
|
|
if(furi_hal_serial_control->log_serial) {
|
|
furi_log_remove_handler(furi_hal_serial_control->log_handler);
|
|
furi_hal_serial_deinit(furi_hal_serial_control->log_serial);
|
|
furi_hal_serial_control->log_serial = NULL;
|
|
}
|
|
|
|
if(handle) {
|
|
furi_hal_serial_control->log_serial = handle;
|
|
furi_hal_serial_init(
|
|
furi_hal_serial_control->log_serial,
|
|
furi_hal_serial_control->log_config_serial_baud_rate);
|
|
furi_hal_serial_control->log_handler.callback = furi_hal_serial_control_log_callback;
|
|
furi_hal_serial_control->log_handler.context = furi_hal_serial_control->log_serial;
|
|
furi_log_add_handler(furi_hal_serial_control->log_handler);
|
|
}
|
|
|
|
// Re-enable expansion module detection (if applicable)
|
|
if(furi_hal_serial_control->expansion_serial) {
|
|
furi_hal_serial_control_enable_expansion_irq(
|
|
furi_hal_serial_control->expansion_serial, true);
|
|
}
|
|
}
|
|
|
|
static bool furi_hal_serial_control_handler_stop(void* input, void* output) {
|
|
UNUSED(input);
|
|
UNUSED(output);
|
|
return false;
|
|
}
|
|
|
|
static bool furi_hal_serial_control_handler_acquire(void* input, void* output) {
|
|
FuriHalSerialId serial_id = *(FuriHalSerialId*)input;
|
|
FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[serial_id];
|
|
|
|
if(handle->in_use) {
|
|
*(FuriHalSerialHandle**)output = NULL;
|
|
} else {
|
|
// Logging
|
|
if(furi_hal_serial_control->log_config_serial_id == serial_id) {
|
|
furi_hal_serial_control_log_set_handle(NULL);
|
|
// Expansion
|
|
} else if(furi_hal_serial_control->expansion_serial == handle) {
|
|
furi_hal_serial_control_enable_expansion_irq(handle, false);
|
|
}
|
|
// Return handle
|
|
handle->in_use = true;
|
|
*(FuriHalSerialHandle**)output = handle;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool furi_hal_serial_control_handler_release(void* input, void* output) {
|
|
UNUSED(output);
|
|
|
|
FuriHalSerialHandle* handle = *(FuriHalSerialHandle**)input;
|
|
furi_assert(handle->in_use);
|
|
furi_hal_serial_deinit(handle);
|
|
handle->in_use = false;
|
|
|
|
if(furi_hal_serial_control->log_config_serial_id == handle->id) {
|
|
// Return back logging
|
|
furi_hal_serial_control_log_set_handle(handle);
|
|
} else if(furi_hal_serial_control->expansion_serial == handle) {
|
|
// Re-enable expansion
|
|
furi_hal_serial_control_enable_expansion_irq(handle, true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool furi_hal_serial_control_handler_is_busy(void* input, void* output) {
|
|
FuriHalSerialId serial_id = *(FuriHalSerialId*)input;
|
|
*(bool*)output = furi_hal_serial_control->handles[serial_id].in_use;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool furi_hal_serial_control_handler_logging(void* input, void* output) {
|
|
UNUSED(output);
|
|
|
|
// Set new configuration
|
|
FuriHalSerialControlMessageInputLogging* message_input = input;
|
|
furi_hal_serial_control->log_config_serial_id = message_input->id;
|
|
furi_hal_serial_control->log_config_serial_baud_rate = message_input->baud_rate;
|
|
// Apply new configuration
|
|
FuriHalSerialHandle* handle = NULL;
|
|
if(furi_hal_serial_control->log_config_serial_id < FuriHalSerialIdMax) {
|
|
if(!furi_hal_serial_control->handles[furi_hal_serial_control->log_config_serial_id].in_use) {
|
|
handle =
|
|
&furi_hal_serial_control->handles[furi_hal_serial_control->log_config_serial_id];
|
|
}
|
|
}
|
|
|
|
furi_hal_serial_control_log_set_handle(handle);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool furi_hal_serial_control_handler_expansion_set_callback(void* input, void* output) {
|
|
UNUSED(output);
|
|
|
|
FuriHalSerialControlMessageExpCallback* message_input = input;
|
|
FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[message_input->id];
|
|
|
|
const bool enable_irq = message_input->callback != NULL;
|
|
|
|
if(enable_irq) {
|
|
furi_check(furi_hal_serial_control->expansion_serial == NULL);
|
|
furi_check(furi_hal_serial_control->expansion_cb == NULL);
|
|
furi_hal_serial_control->expansion_serial = handle;
|
|
} else {
|
|
furi_check(furi_hal_serial_control->expansion_serial == handle);
|
|
furi_check(furi_hal_serial_control->expansion_cb != NULL);
|
|
furi_hal_serial_control->expansion_serial = NULL;
|
|
}
|
|
|
|
furi_hal_serial_control->expansion_cb = message_input->callback;
|
|
furi_hal_serial_control->expansion_ctx = message_input->context;
|
|
|
|
furi_hal_serial_control_enable_expansion_irq(handle, enable_irq);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool furi_hal_serial_control_handler_expansion_irq(void* input, void* output) {
|
|
UNUSED(input);
|
|
UNUSED(output);
|
|
|
|
if(furi_hal_serial_control->expansion_cb) {
|
|
void* context = furi_hal_serial_control->expansion_ctx;
|
|
furi_hal_serial_control->expansion_cb(context);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef bool (*FuriHalSerialControlCommandHandler)(void* input, void* output);
|
|
|
|
static const FuriHalSerialControlCommandHandler furi_hal_serial_control_handlers[] = {
|
|
[FuriHalSerialControlMessageTypeStop] = furi_hal_serial_control_handler_stop,
|
|
[FuriHalSerialControlMessageTypeAcquire] = furi_hal_serial_control_handler_acquire,
|
|
[FuriHalSerialControlMessageTypeRelease] = furi_hal_serial_control_handler_release,
|
|
[FuriHalSerialControlMessageTypeIsBusy] = furi_hal_serial_control_handler_is_busy,
|
|
[FuriHalSerialControlMessageTypeLogging] = furi_hal_serial_control_handler_logging,
|
|
[FuriHalSerialControlMessageTypeExpansionSetCallback] =
|
|
furi_hal_serial_control_handler_expansion_set_callback,
|
|
[FuriHalSerialControlMessageTypeExpansionIrq] = furi_hal_serial_control_handler_expansion_irq,
|
|
};
|
|
|
|
static int32_t furi_hal_serial_control_thread(void* args) {
|
|
UNUSED(args);
|
|
|
|
bool should_continue = true;
|
|
while(should_continue || furi_message_queue_get_count(furi_hal_serial_control->queue) > 0) {
|
|
FuriHalSerialControlMessage message = {0};
|
|
FuriStatus status =
|
|
furi_message_queue_get(furi_hal_serial_control->queue, &message, FuriWaitForever);
|
|
furi_check(status == FuriStatusOk);
|
|
furi_check(message.type < COUNT_OF(furi_hal_serial_control_handlers));
|
|
|
|
should_continue =
|
|
furi_hal_serial_control_handlers[message.type](message.input, message.output);
|
|
|
|
if(message.api_lock != NULL) {
|
|
api_lock_unlock(message.api_lock);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void furi_hal_serial_control_init(void) {
|
|
furi_check(furi_hal_serial_control == NULL);
|
|
// Allocate resources
|
|
furi_hal_serial_control = malloc(sizeof(FuriHalSerialControl));
|
|
furi_hal_serial_control->handles[FuriHalSerialIdUsart].id = FuriHalSerialIdUsart;
|
|
furi_hal_serial_control->handles[FuriHalSerialIdLpuart].id = FuriHalSerialIdLpuart;
|
|
furi_hal_serial_control->queue =
|
|
furi_message_queue_alloc(8, sizeof(FuriHalSerialControlMessage));
|
|
furi_hal_serial_control->thread =
|
|
furi_thread_alloc_ex("SerialControlDriver", 512, furi_hal_serial_control_thread, NULL);
|
|
furi_thread_mark_as_service(furi_hal_serial_control->thread);
|
|
furi_thread_set_priority(furi_hal_serial_control->thread, FuriThreadPriorityHighest);
|
|
furi_hal_serial_control->log_config_serial_id = FuriHalSerialIdMax;
|
|
// Start control plane thread
|
|
furi_thread_start(furi_hal_serial_control->thread);
|
|
}
|
|
|
|
void furi_hal_serial_control_deinit(void) {
|
|
furi_check(furi_hal_serial_control);
|
|
// Stop control plane thread
|
|
FuriHalSerialControlMessage message;
|
|
message.type = FuriHalSerialControlMessageTypeStop;
|
|
message.api_lock = NULL;
|
|
furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever);
|
|
furi_thread_join(furi_hal_serial_control->thread);
|
|
// Release resources
|
|
furi_thread_free(furi_hal_serial_control->thread);
|
|
furi_message_queue_free(furi_hal_serial_control->queue);
|
|
free(furi_hal_serial_control);
|
|
}
|
|
|
|
void furi_hal_serial_control_suspend(void) {
|
|
furi_check(furi_hal_serial_control);
|
|
|
|
for(size_t i = 0; i < FuriHalSerialIdMax; i++) {
|
|
furi_hal_serial_tx_wait_complete(&furi_hal_serial_control->handles[i]);
|
|
furi_hal_serial_suspend(&furi_hal_serial_control->handles[i]);
|
|
}
|
|
}
|
|
|
|
void furi_hal_serial_control_resume(void) {
|
|
furi_check(furi_hal_serial_control);
|
|
|
|
for(size_t i = 0; i < FuriHalSerialIdMax; i++) {
|
|
furi_hal_serial_resume(&furi_hal_serial_control->handles[i]);
|
|
}
|
|
}
|
|
|
|
FuriHalSerialHandle* furi_hal_serial_control_acquire(FuriHalSerialId serial_id) {
|
|
furi_check(furi_hal_serial_control);
|
|
|
|
FuriHalSerialHandle* output = NULL;
|
|
|
|
FuriHalSerialControlMessage message;
|
|
message.type = FuriHalSerialControlMessageTypeAcquire;
|
|
message.api_lock = api_lock_alloc_locked();
|
|
message.input = &serial_id;
|
|
message.output = &output;
|
|
furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever);
|
|
api_lock_wait_unlock_and_free(message.api_lock);
|
|
|
|
return output;
|
|
}
|
|
|
|
void furi_hal_serial_control_release(FuriHalSerialHandle* handle) {
|
|
furi_check(furi_hal_serial_control);
|
|
furi_check(handle);
|
|
|
|
FuriHalSerialControlMessage message;
|
|
message.type = FuriHalSerialControlMessageTypeRelease;
|
|
message.api_lock = api_lock_alloc_locked();
|
|
message.input = &handle;
|
|
furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever);
|
|
api_lock_wait_unlock_and_free(message.api_lock);
|
|
}
|
|
|
|
bool furi_hal_serial_control_is_busy(FuriHalSerialId serial_id) {
|
|
furi_check(furi_hal_serial_control);
|
|
|
|
bool result = false;
|
|
|
|
FuriHalSerialControlMessage message;
|
|
message.type = FuriHalSerialControlMessageTypeIsBusy;
|
|
message.api_lock = api_lock_alloc_locked();
|
|
message.input = &serial_id;
|
|
message.output = &result;
|
|
furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever);
|
|
api_lock_wait_unlock_and_free(message.api_lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
void furi_hal_serial_control_set_logging_config(FuriHalSerialId serial_id, uint32_t baud_rate) {
|
|
furi_check(serial_id <= FuriHalSerialIdMax);
|
|
furi_check(baud_rate >= 9600 && baud_rate <= 4000000);
|
|
|
|
// Very special case of updater, where RTC initialized before kernel start
|
|
if(!furi_hal_serial_control) return;
|
|
|
|
furi_check(furi_hal_serial_is_baud_rate_supported(
|
|
&furi_hal_serial_control->handles[serial_id], baud_rate));
|
|
|
|
FuriHalSerialControlMessageInputLogging message_input = {
|
|
.id = serial_id,
|
|
.baud_rate = baud_rate,
|
|
};
|
|
FuriHalSerialControlMessage message;
|
|
message.type = FuriHalSerialControlMessageTypeLogging;
|
|
message.api_lock = api_lock_alloc_locked();
|
|
message.input = &message_input;
|
|
furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever);
|
|
api_lock_wait_unlock_and_free(message.api_lock);
|
|
}
|
|
|
|
void furi_hal_serial_control_set_expansion_callback(
|
|
FuriHalSerialId serial_id,
|
|
FuriHalSerialControlExpansionCallback callback,
|
|
void* context) {
|
|
furi_check(serial_id <= FuriHalSerialIdMax);
|
|
furi_check(furi_hal_serial_control);
|
|
|
|
FuriHalSerialControlMessageExpCallback message_input = {
|
|
.id = serial_id,
|
|
.callback = callback,
|
|
.context = context,
|
|
};
|
|
FuriHalSerialControlMessage message;
|
|
message.type = FuriHalSerialControlMessageTypeExpansionSetCallback;
|
|
message.api_lock = api_lock_alloc_locked();
|
|
message.input = &message_input;
|
|
furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever);
|
|
api_lock_wait_unlock_and_free(message.api_lock);
|
|
}
|