cli_control.c 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #include "cli_control.h"
  2. #include <FreeRTOS.h>
  3. #include <cli/cli.h>
  4. #include <cli/cli_i.h>
  5. #include <cli/cli_vcp.h>
  6. #include <loader/loader.h>
  7. #include <loader/loader_i.h>
  8. FuriStreamBuffer* cli_tx_stream = NULL;
  9. FuriStreamBuffer* cli_rx_stream = NULL;
  10. static volatile bool restore_tx_stdout = false;
  11. static void tx_handler(const uint8_t* buffer, size_t size) {
  12. furi_stream_buffer_send(cli_tx_stream, buffer, size, FuriWaitForever);
  13. }
  14. static void tx_handler_stdout(const char* buffer, size_t size) {
  15. tx_handler((const uint8_t*)buffer, size);
  16. }
  17. static size_t real_rx_handler(uint8_t* buffer, size_t size, uint32_t timeout) {
  18. size_t rx_cnt = 0;
  19. while (size > 0) {
  20. size_t batch_size = size;
  21. if (batch_size > 128)
  22. batch_size = 128;
  23. size_t len = furi_stream_buffer_receive(cli_rx_stream, buffer, batch_size, timeout);
  24. if (len == 0)
  25. break;
  26. size -= len;
  27. buffer += len;
  28. rx_cnt += len;
  29. }
  30. if (restore_tx_stdout) {
  31. furi_thread_set_stdout_callback(cli_vcp.tx_stdout);
  32. } else {
  33. furi_thread_set_stdout_callback(tx_handler_stdout);
  34. }
  35. return rx_cnt;
  36. }
  37. static CliSession* session;
  38. static void session_init(void) {}
  39. static void session_deinit(void) {
  40. free(session);
  41. session = NULL;
  42. }
  43. static bool session_connected(void) {
  44. return true;
  45. }
  46. void clicontrol_hijack(size_t tx_size, size_t rx_size) {
  47. if (cli_rx_stream != NULL && cli_tx_stream != NULL) {
  48. return;
  49. }
  50. Cli* global_cli = furi_record_open(RECORD_CLI);
  51. cli_rx_stream = furi_stream_buffer_alloc(rx_size, 1);
  52. cli_tx_stream = furi_stream_buffer_alloc(tx_size, 1);
  53. session = (CliSession*)malloc(sizeof(CliSession));
  54. session->tx = &tx_handler;
  55. session->rx = &real_rx_handler;
  56. session->tx_stdout = &tx_handler_stdout;
  57. session->init = &session_init;
  58. session->deinit = &session_deinit;
  59. session->is_connected = &session_connected;
  60. CliCommandTree_it_t cmd_iterator;
  61. for (CliCommandTree_it(cmd_iterator, global_cli->commands);
  62. !CliCommandTree_end_p(cmd_iterator);
  63. CliCommandTree_next(cmd_iterator)) {
  64. CliCommand* t = CliCommandTree_cref(cmd_iterator)->value_ptr;
  65. // Move CliCommandFlagParallelSafe to another bit
  66. t->flags ^= ((t->flags & (CliCommandFlagParallelSafe << 8)) ^
  67. ((t->flags & CliCommandFlagParallelSafe) << 8));
  68. // Set parallel safe
  69. t->flags |= CliCommandFlagParallelSafe;
  70. }
  71. // Session switcharooney
  72. FuriThreadStdoutWriteCallback prev_stdout = furi_thread_get_stdout_callback();
  73. cli_session_close(global_cli);
  74. restore_tx_stdout = false;
  75. cli_session_open(global_cli, session);
  76. furi_thread_set_stdout_callback(prev_stdout);
  77. furi_record_close(RECORD_CLI);
  78. }
  79. void clicontrol_unhijack(bool persist) {
  80. if (cli_rx_stream == NULL && cli_tx_stream == NULL) {
  81. return;
  82. }
  83. // Consume remaining tx data
  84. if (furi_stream_buffer_bytes_available(cli_tx_stream) > 0) {
  85. char sink = 0;
  86. while (!furi_stream_buffer_is_empty(cli_tx_stream)) {
  87. furi_stream_buffer_receive(cli_tx_stream, &sink, 1, FuriWaitForever);
  88. }
  89. }
  90. Cli* global_cli = furi_record_open(RECORD_CLI);
  91. if (persist) {
  92. // Don't trigger a terminal reset as the session switches
  93. cli_vcp.is_connected = &furi_hal_version_do_i_belong_here;
  94. } else {
  95. // Send CTRL-C a few times
  96. char eot = 0x03;
  97. furi_stream_buffer_send(cli_rx_stream, &eot, 1, FuriWaitForever);
  98. furi_stream_buffer_send(cli_rx_stream, &eot, 1, FuriWaitForever);
  99. furi_stream_buffer_send(cli_rx_stream, &eot, 1, FuriWaitForever);
  100. }
  101. // Restore command flags
  102. CliCommandTree_it_t cmd_iterator;
  103. for (CliCommandTree_it(cmd_iterator, global_cli->commands);
  104. !CliCommandTree_end_p(cmd_iterator);
  105. CliCommandTree_next(cmd_iterator)) {
  106. CliCommand* t = CliCommandTree_cref(cmd_iterator)->value_ptr;
  107. t->flags ^= (((t->flags & CliCommandFlagParallelSafe) >> 8) ^
  108. ((t->flags & (CliCommandFlagParallelSafe << 8)) >> 8));
  109. }
  110. restore_tx_stdout = true; // Ready for next rx call
  111. // Session switcharooney again
  112. FuriThreadStdoutWriteCallback prev_stdout = furi_thread_get_stdout_callback();
  113. cli_session_close(global_cli);
  114. cli_session_open(global_cli, &cli_vcp);
  115. furi_thread_set_stdout_callback(prev_stdout);
  116. furi_record_close(RECORD_CLI);
  117. // Unblock waiting rx handler, restore old cli_vcp.tx_stdout
  118. furi_stream_buffer_send(cli_rx_stream, "_", 1, FuriWaitForever);
  119. // At this point, all cli_vcp functions should be restored.
  120. furi_stream_buffer_free(cli_rx_stream);
  121. furi_stream_buffer_free(cli_tx_stream);
  122. cli_rx_stream = NULL;
  123. cli_tx_stream = NULL;
  124. }