cli_control.c 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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, void* context) {
  15. UNUSED(context);
  16. tx_handler((const uint8_t*)buffer, size);
  17. }
  18. static size_t real_rx_handler(uint8_t* buffer, size_t size, uint32_t timeout) {
  19. size_t rx_cnt = 0;
  20. while(size > 0) {
  21. size_t batch_size = size;
  22. if(batch_size > 128) batch_size = 128;
  23. size_t len = furi_stream_buffer_receive(cli_rx_stream, buffer, batch_size, timeout);
  24. if(len == 0) break;
  25. size -= len;
  26. buffer += len;
  27. rx_cnt += len;
  28. }
  29. if(restore_tx_stdout) {
  30. furi_thread_set_stdout_callback(cli_vcp.tx_stdout, NULL);
  31. } else {
  32. furi_thread_set_stdout_callback(tx_handler_stdout, NULL);
  33. }
  34. return rx_cnt;
  35. }
  36. static CliSession* session;
  37. static void session_init(void) {
  38. }
  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); !CliCommandTree_end_p(cmd_iterator);
  62. CliCommandTree_next(cmd_iterator)) {
  63. CliCommand* t = CliCommandTree_cref(cmd_iterator)->value_ptr;
  64. // Move CliCommandFlagParallelSafe to another bit
  65. t->flags ^=
  66. ((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;
  73. void* prev_stdout_ctx;
  74. furi_thread_get_stdout_callback(&prev_stdout, &prev_stdout_ctx);
  75. cli_session_close(global_cli);
  76. restore_tx_stdout = false;
  77. cli_session_open(global_cli, session);
  78. furi_thread_set_stdout_callback(prev_stdout, prev_stdout_ctx);
  79. furi_record_close(RECORD_CLI);
  80. }
  81. void clicontrol_unhijack(bool persist) {
  82. if(cli_rx_stream == NULL && cli_tx_stream == NULL) {
  83. return;
  84. }
  85. // Consume remaining tx data
  86. if(furi_stream_buffer_bytes_available(cli_tx_stream) > 0) {
  87. char sink = 0;
  88. while(!furi_stream_buffer_is_empty(cli_tx_stream)) {
  89. furi_stream_buffer_receive(cli_tx_stream, &sink, 1, FuriWaitForever);
  90. }
  91. }
  92. Cli* global_cli = furi_record_open(RECORD_CLI);
  93. if(persist) {
  94. // Don't trigger a terminal reset as the session switches
  95. // cli_vcp.is_connected = &furi_hal_version_do_i_belong_here;
  96. } else {
  97. // Send CTRL-C a few times
  98. char eot = 0x03;
  99. furi_stream_buffer_send(cli_rx_stream, &eot, 1, FuriWaitForever);
  100. furi_stream_buffer_send(cli_rx_stream, &eot, 1, FuriWaitForever);
  101. furi_stream_buffer_send(cli_rx_stream, &eot, 1, FuriWaitForever);
  102. }
  103. // Restore command flags
  104. CliCommandTree_it_t cmd_iterator;
  105. for(CliCommandTree_it(cmd_iterator, global_cli->commands); !CliCommandTree_end_p(cmd_iterator);
  106. CliCommandTree_next(cmd_iterator)) {
  107. CliCommand* t = CliCommandTree_cref(cmd_iterator)->value_ptr;
  108. t->flags ^=
  109. (((t->flags & CliCommandFlagParallelSafe) >> 8) ^
  110. ((t->flags & (CliCommandFlagParallelSafe << 8)) >> 8));
  111. }
  112. restore_tx_stdout = true; // Ready for next rx call
  113. // Session switcharooney again
  114. FuriThreadStdoutWriteCallback prev_stdout;
  115. void* prev_stdout_ctx;
  116. furi_thread_get_stdout_callback(&prev_stdout, &prev_stdout_ctx);
  117. cli_session_close(global_cli);
  118. cli_session_open(global_cli, &cli_vcp);
  119. furi_thread_set_stdout_callback(prev_stdout, prev_stdout_ctx);
  120. furi_record_close(RECORD_CLI);
  121. // Unblock waiting rx handler, restore old cli_vcp.tx_stdout
  122. furi_stream_buffer_send(cli_rx_stream, "_", 1, FuriWaitForever);
  123. // At this point, all cli_vcp functions should be restored.
  124. furi_stream_buffer_free(cli_rx_stream);
  125. furi_stream_buffer_free(cli_tx_stream);
  126. cli_rx_stream = NULL;
  127. cli_tx_stream = NULL;
  128. }