cli_control.c 4.7 KB

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