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, 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 = 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, NULL);
  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); !CliCommandTree_end_p(cmd_iterator);
  104. CliCommandTree_next(cmd_iterator)) {
  105. CliCommand* t = CliCommandTree_cref(cmd_iterator)->value_ptr;
  106. t->flags ^=
  107. (((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, NULL);
  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. }