mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
* cli_shell: separate into toolbox * fix: cmd flags * fix formatting * cli: increase default stack depth * cli_shell: fix loader lock logic * cli: fix command flags * fix f18 * speaker_debug: fix * cli_registry: fix docs * ufbt: rename cli target back * cli: rename app and record * cli: fix and simplify help command * cli_master_shell: fix ext commands * fix formatting * cli: rename master to main * fix formatting --------- Co-authored-by: hedger <hedger@users.noreply.github.com>
309 lines
9.2 KiB
C
309 lines
9.2 KiB
C
#include "cli_vcp.h"
|
|
#include <furi_hal_usb_cdc.h>
|
|
#include <furi_hal.h>
|
|
#include <furi.h>
|
|
#include <stdint.h>
|
|
#include <toolbox/pipe.h>
|
|
#include <toolbox/cli/shell/cli_shell.h>
|
|
#include "cli_main_shell.h"
|
|
#include "cli_main_commands.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;
|
|
PipeSide* shell_pipe;
|
|
bool is_currently_transmitting;
|
|
size_t previous_tx_length;
|
|
|
|
CliRegistry* main_registry;
|
|
CliShell* 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) {
|
|
cli_shell_join(cli_vcp->shell);
|
|
cli_shell_free(cli_vcp->shell);
|
|
pipe_free(cli_vcp->shell_pipe);
|
|
}
|
|
|
|
// start shell thread
|
|
PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1);
|
|
cli_vcp->own_pipe = bundle.alices_side;
|
|
cli_vcp->shell_pipe = bundle.bobs_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_alloc(
|
|
cli_main_motd, NULL, cli_vcp->shell_pipe, cli_vcp->main_registry, &cli_main_ext_config);
|
|
cli_shell_start(cli_vcp->shell);
|
|
}
|
|
|
|
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);
|
|
|
|
cli_vcp->main_registry = furi_record_open(RECORD_CLI);
|
|
|
|
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);
|
|
}
|