mirror of
https://github.com/flipperdevices/flipperzero-firmware.git
synced 2025-12-12 04:41:26 +04:00
* feat: FuriThread stdin * ci: fix f18 * feat: stdio callback context * feat: FuriPipe * POTENTIALLY EXPLOSIVE pipe welding * fix: non-explosive welding * Revert welding * docs: furi_pipe * feat: pipe event loop integration * update f18 sdk * f18 * docs: make doxygen happy * fix: event loop not triggering when pipe attached to stdio * fix: partial stdout in pipe * allow simultaneous in and out subscription in event loop * feat: vcp i/o * feat: cli ansi stuffs and history * feat: more line editing * working but slow cli rewrite * restore previous speed after 4 days of debugging 🥲 * fix: cli_app_should_stop * fix: cli and event_loop memory leaks * style: remove commented out code * ci: fix pvs warnings * fix: unit tests, event_loop crash * ci: fix build * ci: silence pvs warning * feat: cli gpio * ci: fix formatting * Fix memory leak during event loop unsubscription * Event better memory leak fix * feat: cli completions * Merge remote-tracking branch 'origin/dev' into portasynthinca3/3928-cli-threads * merge fixups * temporarily exclude speaker_debug app * pvs and unit tests fixups * feat: commands in fals * move commands out of flash, code cleanup * ci: fix errors * fix: run commands in buffer when stopping session * speedup cli file transfer * fix f18 * separate cli_shell into modules * fix pvs warning * fix qflipper refusing to connect * remove temp debug logs * remove erroneous conclusion * Fix memory leak during event loop unsubscription * Event better memory leak fix * unit test for the fix * improve thread stdio callback signatures * pipe stdout timeout * update api symbols * fix f18, formatting * fix pvs warnings * increase stack size, hope to fix unit tests * cli: revert flag changes * cli: fix formatting * cli, fbt: loopback perf benchmark * thread, event_loop: subscribing to thread flags * cli: signal internal events using thread flags, improve performance * fix f18, formatting * event_loop: fix crash * storage_cli: increase write_chunk buffer size again * cli: explanation for order=0 * thread, event_loop: thread flags callback refactor * cli: increase stack size * cli: rename cli_app_should_stop -> cli_is_pipe_broken_or_is_etx_next_char * cli: use plain array instead of mlib for history * cli: prepend file name to static fns * cli: fix formatting * cli_shell: increase stack size * cli: fix rpc lockup * cli: better lockup fix * cli: fix f18 * fix merge --------- Co-authored-by: Georgii Surkov <georgii.surkov@outlook.com> Co-authored-by: あく <alleteam@gmail.com>
299 lines
8.8 KiB
C
299 lines
8.8 KiB
C
#include "cli_vcp.h"
|
|
#include "shell/cli_shell.h"
|
|
#include <furi_hal_usb_cdc.h>
|
|
#include <furi_hal.h>
|
|
#include <furi.h>
|
|
#include <stdint.h>
|
|
#include <toolbox/pipe.h>
|
|
|
|
#define TAG "CliVcp"
|
|
|
|
#define USB_CDC_PKT_LEN CDC_DATA_SZ
|
|
#define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3)
|
|
#define VCP_IF_NUM 0
|
|
#define VCP_MESSAGE_Q_LEN 8
|
|
|
|
#ifdef CLI_VCP_TRACE
|
|
#define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__)
|
|
#else
|
|
#define VCP_TRACE(...)
|
|
#endif
|
|
|
|
typedef struct {
|
|
enum {
|
|
CliVcpMessageTypeEnable,
|
|
CliVcpMessageTypeDisable,
|
|
} type;
|
|
union {};
|
|
} CliVcpMessage;
|
|
|
|
typedef enum {
|
|
CliVcpInternalEventConnected = (1 << 0),
|
|
CliVcpInternalEventDisconnected = (1 << 1),
|
|
CliVcpInternalEventTxDone = (1 << 2),
|
|
CliVcpInternalEventRx = (1 << 3),
|
|
} CliVcpInternalEvent;
|
|
|
|
#define CliVcpInternalEventAll \
|
|
(CliVcpInternalEventConnected | CliVcpInternalEventDisconnected | CliVcpInternalEventTxDone | \
|
|
CliVcpInternalEventRx)
|
|
|
|
struct CliVcp {
|
|
FuriEventLoop* event_loop;
|
|
FuriMessageQueue* message_queue; // <! external messages
|
|
FuriThreadId thread_id;
|
|
|
|
bool is_enabled, is_connected;
|
|
FuriHalUsbInterface* previous_interface;
|
|
|
|
PipeSide* own_pipe;
|
|
bool is_currently_transmitting;
|
|
size_t previous_tx_length;
|
|
|
|
FuriThread* shell;
|
|
};
|
|
|
|
// ============
|
|
// Data copying
|
|
// ============
|
|
|
|
/**
|
|
* Called in the following cases:
|
|
* - previous transfer has finished;
|
|
* - new data became available to send.
|
|
*/
|
|
static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) {
|
|
if(cli_vcp->is_currently_transmitting) return;
|
|
if(!cli_vcp->own_pipe) return;
|
|
|
|
uint8_t buf[USB_CDC_PKT_LEN];
|
|
size_t to_receive_from_pipe = MIN(sizeof(buf), pipe_bytes_available(cli_vcp->own_pipe));
|
|
size_t length = pipe_receive(cli_vcp->own_pipe, buf, to_receive_from_pipe);
|
|
if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) {
|
|
VCP_TRACE(TAG, "cdc_send length=%zu", length);
|
|
cli_vcp->is_currently_transmitting = true;
|
|
furi_hal_cdc_send(VCP_IF_NUM, buf, length);
|
|
}
|
|
cli_vcp->previous_tx_length = length;
|
|
}
|
|
|
|
/**
|
|
* Called in the following cases:
|
|
* - new data arrived at the endpoint;
|
|
* - data was read out of the pipe.
|
|
*/
|
|
static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) {
|
|
if(!cli_vcp->own_pipe) return;
|
|
if(pipe_spaces_available(cli_vcp->own_pipe) < USB_CDC_PKT_LEN) return;
|
|
|
|
uint8_t buf[USB_CDC_PKT_LEN];
|
|
size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf));
|
|
VCP_TRACE(TAG, "cdc_receive length=%zu", length);
|
|
furi_check(pipe_send(cli_vcp->own_pipe, buf, length) == length);
|
|
}
|
|
|
|
// =============
|
|
// CDC callbacks
|
|
// =============
|
|
|
|
static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) {
|
|
furi_thread_flags_set(cli_vcp->thread_id, event);
|
|
}
|
|
|
|
static void cli_vcp_cdc_tx_done(void* context) {
|
|
CliVcp* cli_vcp = context;
|
|
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone);
|
|
}
|
|
|
|
static void cli_vcp_cdc_rx(void* context) {
|
|
CliVcp* cli_vcp = context;
|
|
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventRx);
|
|
}
|
|
|
|
static void cli_vcp_cdc_state_callback(void* context, CdcState state) {
|
|
CliVcp* cli_vcp = context;
|
|
if(state == CdcStateDisconnected) {
|
|
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected);
|
|
}
|
|
// `Connected` events are generated by DTR going active
|
|
}
|
|
|
|
static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) {
|
|
CliVcp* cli_vcp = context;
|
|
if(ctrl_lines & CdcCtrlLineDTR) {
|
|
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventConnected);
|
|
} else {
|
|
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected);
|
|
}
|
|
}
|
|
|
|
static CdcCallbacks cdc_callbacks = {
|
|
.tx_ep_callback = cli_vcp_cdc_tx_done,
|
|
.rx_ep_callback = cli_vcp_cdc_rx,
|
|
.state_callback = cli_vcp_cdc_state_callback,
|
|
.ctrl_line_callback = cli_vcp_cdc_ctrl_line_callback,
|
|
.config_callback = NULL,
|
|
};
|
|
|
|
// ======================
|
|
// Pipe callback handlers
|
|
// ======================
|
|
|
|
static void cli_vcp_data_from_shell(PipeSide* pipe, void* context) {
|
|
UNUSED(pipe);
|
|
CliVcp* cli_vcp = context;
|
|
cli_vcp_maybe_send_data(cli_vcp);
|
|
}
|
|
|
|
static void cli_vcp_shell_ready(PipeSide* pipe, void* context) {
|
|
UNUSED(pipe);
|
|
CliVcp* cli_vcp = context;
|
|
cli_vcp_maybe_receive_data(cli_vcp);
|
|
}
|
|
|
|
/**
|
|
* Processes messages arriving from other threads
|
|
*/
|
|
static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) {
|
|
CliVcp* cli_vcp = context;
|
|
CliVcpMessage message;
|
|
furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk);
|
|
|
|
switch(message.type) {
|
|
case CliVcpMessageTypeEnable:
|
|
if(cli_vcp->is_enabled) return;
|
|
FURI_LOG_D(TAG, "Enabling");
|
|
cli_vcp->is_enabled = true;
|
|
|
|
// switch usb mode
|
|
cli_vcp->previous_interface = furi_hal_usb_get_config();
|
|
furi_hal_usb_set_config(&usb_cdc_single, NULL);
|
|
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_callbacks, cli_vcp);
|
|
break;
|
|
|
|
case CliVcpMessageTypeDisable:
|
|
if(!cli_vcp->is_enabled) return;
|
|
FURI_LOG_D(TAG, "Disabling");
|
|
cli_vcp->is_enabled = false;
|
|
|
|
// restore usb mode
|
|
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
|
|
furi_hal_usb_set_config(cli_vcp->previous_interface, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes messages arriving from CDC event callbacks
|
|
*/
|
|
static void cli_vcp_internal_event_happened(void* context) {
|
|
CliVcp* cli_vcp = context;
|
|
CliVcpInternalEvent event = furi_thread_flags_wait(CliVcpInternalEventAll, FuriFlagWaitAny, 0);
|
|
furi_check(!(event & FuriFlagError));
|
|
|
|
if(event & CliVcpInternalEventDisconnected) {
|
|
if(!cli_vcp->is_connected) return;
|
|
FURI_LOG_D(TAG, "Disconnected");
|
|
cli_vcp->is_connected = false;
|
|
|
|
// disconnect our side of the pipe
|
|
pipe_detach_from_event_loop(cli_vcp->own_pipe);
|
|
pipe_free(cli_vcp->own_pipe);
|
|
cli_vcp->own_pipe = NULL;
|
|
}
|
|
|
|
if(event & CliVcpInternalEventConnected) {
|
|
if(cli_vcp->is_connected) return;
|
|
FURI_LOG_D(TAG, "Connected");
|
|
cli_vcp->is_connected = true;
|
|
|
|
// wait for previous shell to stop
|
|
furi_check(!cli_vcp->own_pipe);
|
|
if(cli_vcp->shell) {
|
|
furi_thread_join(cli_vcp->shell);
|
|
furi_thread_free(cli_vcp->shell);
|
|
}
|
|
|
|
// start shell thread
|
|
PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1);
|
|
cli_vcp->own_pipe = bundle.alices_side;
|
|
pipe_attach_to_event_loop(cli_vcp->own_pipe, cli_vcp->event_loop);
|
|
pipe_set_callback_context(cli_vcp->own_pipe, cli_vcp);
|
|
pipe_set_data_arrived_callback(
|
|
cli_vcp->own_pipe, cli_vcp_data_from_shell, FuriEventLoopEventFlagEdge);
|
|
pipe_set_space_freed_callback(
|
|
cli_vcp->own_pipe, cli_vcp_shell_ready, FuriEventLoopEventFlagEdge);
|
|
furi_delay_ms(33); // we are too fast, minicom isn't ready yet
|
|
cli_vcp->shell = cli_shell_start(bundle.bobs_side);
|
|
}
|
|
|
|
if(event & CliVcpInternalEventRx) {
|
|
VCP_TRACE(TAG, "Rx");
|
|
cli_vcp_maybe_receive_data(cli_vcp);
|
|
}
|
|
|
|
if(event & CliVcpInternalEventTxDone) {
|
|
VCP_TRACE(TAG, "TxDone");
|
|
cli_vcp->is_currently_transmitting = false;
|
|
cli_vcp_maybe_send_data(cli_vcp);
|
|
}
|
|
}
|
|
|
|
// ============
|
|
// Thread stuff
|
|
// ============
|
|
|
|
static CliVcp* cli_vcp_alloc(void) {
|
|
CliVcp* cli_vcp = malloc(sizeof(CliVcp));
|
|
cli_vcp->thread_id = furi_thread_get_current_id();
|
|
|
|
cli_vcp->event_loop = furi_event_loop_alloc();
|
|
|
|
cli_vcp->message_queue = furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpMessage));
|
|
furi_event_loop_subscribe_message_queue(
|
|
cli_vcp->event_loop,
|
|
cli_vcp->message_queue,
|
|
FuriEventLoopEventIn,
|
|
cli_vcp_message_received,
|
|
cli_vcp);
|
|
|
|
furi_event_loop_subscribe_thread_flags(
|
|
cli_vcp->event_loop, cli_vcp_internal_event_happened, cli_vcp);
|
|
|
|
return cli_vcp;
|
|
}
|
|
|
|
int32_t cli_vcp_srv(void* p) {
|
|
UNUSED(p);
|
|
|
|
if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
|
|
FURI_LOG_W(TAG, "Skipping start in special boot mode");
|
|
furi_thread_suspend(furi_thread_get_current_id());
|
|
return 0;
|
|
}
|
|
|
|
CliVcp* cli_vcp = cli_vcp_alloc();
|
|
furi_record_create(RECORD_CLI_VCP, cli_vcp);
|
|
furi_event_loop_run(cli_vcp->event_loop);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ==========
|
|
// Public API
|
|
// ==========
|
|
|
|
void cli_vcp_enable(CliVcp* cli_vcp) {
|
|
CliVcpMessage message = {
|
|
.type = CliVcpMessageTypeEnable,
|
|
};
|
|
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
|
|
}
|
|
|
|
void cli_vcp_disable(CliVcp* cli_vcp) {
|
|
CliVcpMessage message = {
|
|
.type = CliVcpMessageTypeDisable,
|
|
};
|
|
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
|
|
}
|