quic/qbox
Loading...
Searching...
No Matches
char_backend_stdio.h
1/*
2 * Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All Rights Reserved.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7#ifndef _GS_UART_BACKEND_STDIO_H_
8#define _GS_UART_BACKEND_STDIO_H_
9
10#include <systemc>
11#include <tlm.h>
12#include <tlm_utils/simple_target_socket.h>
13
14#include <unistd.h>
15
16#include <async_event.h>
17#include <uutils.h>
18#include <ports/biflow-socket.h>
19#include <module_factory_registery.h>
20#include <queue>
21#include <signal.h>
22#include <regex>
23#include <atomic>
24#include <cstdlib>
25
26// TODO convert scp warn to scp fatal
27
28#ifdef _WIN32
29#include <windows.h>
30typedef HANDLE descriptor_t;
31typedef DWORD console_mode_t;
32#else
33#include <termios.h>
34#include <poll.h>
35typedef int descriptor_t;
36typedef struct termios console_mode_t;
37#endif // _WIN32
38
39#define RCV_POLL_TIMEOUT_MS 300
40
41class char_backend_stdio : public sc_core::sc_module
42{
43protected:
44 cci::cci_param<bool> p_read_write;
45 cci::cci_param<std::string> p_expect;
46 cci::cci_param<std::string> p_highlight;
47
48private:
49 std::atomic_bool m_running;
50 std::thread rcv_thread_id;
51 std::atomic<bool> c_flag;
52 SCP_LOGGER();
53 std::string line;
54 std::string ecmd;
55 sc_core::sc_event ecmdev;
56 bool processing = false;
57 static descriptor_t stdin_fd;
58
59#ifdef _WIN32
60 const static size_t input_buffer_size = 128;
62#endif
63
64public:
66 static console_mode_t old_console_mode;
67 static bool old_console_mode_valid;
68
69private:
70#ifdef _WIN32
71 static void restore_old_mode()
72 {
73 BOOL res = SetConsoleMode(stdin_fd, old_console_mode);
74 if (!res) {
75 set_new_mode();
76 }
77 }
78
79 static void set_new_mode(bool save_current_mode = false)
80 {
81 if (save_current_mode && GetConsoleMode(stdin_fd, &old_console_mode)) {
82 old_console_mode_valid = true;
83 }
84
86 }
87
88 descriptor_t get_stdin_descriptor()
89 {
92 SCP_WARN(())("char_backend_stdio: Failed to get standard input descriptor. Error: {}", GetLastError());
93 }
94
96 }
97
99 {
100 for (DWORD i = 0; i < cNumRead; ++i) {
101 const INPUT_RECORD& record = irInBuf[i];
102 if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) {
103 enqueue(record.Event.KeyEvent.uChar.AsciiChar);
104 }
105 }
106 }
107
108 void rcv_thread()
109 {
110 DWORD cNumRead = 0;
111
112 while (m_running) {
113 DWORD waitResult = WaitForSingleObject(stdin_fd, RCV_POLL_TIMEOUT_MS);
114
115 if (waitResult == WAIT_TIMEOUT) {
116 if (c_flag) {
117 c_flag = false;
118 enqueue('\x03');
119 }
120 continue;
121 } else if (waitResult == WAIT_OBJECT_0) {
124 } else {
125 SCP_WARN(())("char_backend_stdio: Failed to read console input. Error: {}", GetLastError());
126 }
127 } else {
128 break;
129 }
130 }
131 }
132#else
133 descriptor_t get_stdin_descriptor() { return STDIN_FILENO; } /* stdin */
134
135 static void restore_old_mode()
136 {
137 int res = tcsetattr(stdin_fd, TCSANOW, &old_console_mode);
138 if (errno != 0) {
139 set_new_mode();
140 }
141 }
142
143 static void set_new_mode(bool save_current_mode = false)
144 {
145 console_mode_t tty;
146 tcgetattr(stdin_fd, &tty);
147 if (save_current_mode) {
148 old_console_mode = tty;
149 old_console_mode_valid = true;
150 }
151 tty.c_lflag |= ECHO | ECHONL | ICANON | IEXTEN;
152 tcsetattr(stdin_fd, TCSANOW, &tty);
153 }
154
155 void rcv_thread()
156 {
157 int fd = STDIN_FILENO;
158 struct pollfd rcv_monitor;
159 rcv_monitor.fd = fd;
160 rcv_monitor.events = POLLIN;
161
162 while (m_running) {
163 int ret = poll(&rcv_monitor, 1, RCV_POLL_TIMEOUT_MS);
164 if ((ret == -1 && errno == EINTR) || (ret == 0) /*timeout*/) {
165 if (c_flag) {
166 c_flag = false;
167 enqueue('\x03');
168 }
169 continue;
170 } else if (rcv_monitor.revents & POLLIN) {
171 char c;
172 int r = read(fd, &c, 1);
173 if (r == 1) {
174 enqueue(c);
175 }
176 if (r == 0) {
177 break;
178 }
179 } else {
180 break;
181 }
182 }
183 }
184#endif
185
186public:
187 void catch_fn(int signo) {}
188
189 static void tty_reset()
190 {
191 if (old_console_mode_valid) {
192 restore_old_mode();
193 } else {
194 set_new_mode();
195 }
196 }
197
198 // trim from end of string (right)
199 std::string& rtrim(std::string& s)
200 {
201 const char* t = "\n\r\f\v";
202 s.erase(s.find_last_not_of(t) + 1);
203 return s;
204 }
205 std::string getecmdstr(const std::string& cmd)
206 {
207 return ecmd.substr(cmd.length(), ecmd.find_first_of("\n") - cmd.length());
208 }
209 void process()
210 {
211 processing = true;
212 expect_process();
213 }
214 void expect_process()
215 {
216 if (!processing) return;
217 const std::string expectstr = "expect ";
218 const std::string sendstr = "send ";
219 const std::string waitstr = "wait ";
220 const std::string exitstr = "exit";
221 do {
222 if (ecmd.substr(0, expectstr.length()) == expectstr) {
223 std::string str = getecmdstr(expectstr);
224 std::regex restr(str);
225 if (std::regex_search(line, restr)) {
226 SCP_WARN(())("Found expect string {}", rtrim(line));
227 ecmd.erase(0, ecmd.find_first_of("\n"));
228 if (ecmd != "") ecmd.erase(0, 1);
229 if (ecmd.substr(0, expectstr.length()) == expectstr) break;
230 continue;
231 }
232 }
233 if (ecmd.substr(0, sendstr.length()) == sendstr) {
234 SCP_WARN(())("Sending expect string {}", getecmdstr(sendstr));
235 for (char c : getecmdstr(sendstr)) {
236 enqueue(c);
237 }
238 enqueue('\n');
239 ecmd.erase(0, ecmd.find_first_of("\n"));
240 if (ecmd != "") ecmd.erase(0, 1);
241 continue;
242 }
243 if (ecmd.substr(0, waitstr.length()) == waitstr) {
244 SCP_WARN(())("Expect waiting {}s", getecmdstr(waitstr));
245 processing = false;
246 ecmdev.notify(stod(getecmdstr(waitstr)), sc_core::SC_SEC);
247 ecmd.erase(0, ecmd.find_first_of("\n"));
248 if (ecmd != "") ecmd.erase(0, 1);
249 break;
250 }
251 if (ecmd.substr(0, exitstr.length()) == exitstr) {
252 SCP_WARN(())("Expect caused exit");
253 sc_core::sc_stop();
254 }
255 break;
256 } while (true);
257 }
258
259 /*
260 * char_backend_stdio constructor
261 * Parameters:
262 * \param read_write bool : when set to true, accept input from STDIO
263 * \param expect String : An 'expect' like string of commands, each command should be separated by \n
264 * The acceptable commands are:
265 * expect [string] : dont process any more commands until the "string" is seen on the output (STDIO)
266 * send [string] : send "string" to the input buffer (NB this will happen whether of not read_write is
267 * set) wait [float] : Wait for "float" (simulated) seconds, until processing continues. exit :
268 * Cause the simulation to terminate normally.
269 */
270 char_backend_stdio(sc_core::sc_module_name name)
271 : sc_core::sc_module(name)
272 , p_read_write("read_write", true, "read_write if true start rcv_thread")
273 , p_expect("expect", "", "string of expect commands")
274 , p_highlight("ansi_highlight", "", "ANSI highlight code to use for output, default bold")
275 , socket("biflow_socket")
276 , m_running(true)
277 , c_flag(false)
278 {
279 SCP_TRACE(()) << "CharBackendStdio constructor";
280
281 stdin_fd = get_stdin_descriptor();
282
283 ecmd = p_expect;
284 SC_METHOD(process);
285 sensitive << ecmdev;
286 if (p_expect.get_value() != "") {
287 ecmd = p_expect;
288 processing = true;
289 SCP_WARN(())("Processing expect string {}", ecmd);
290 }
291
292 // gs::SigHandler::get().register_on_exit_cb(std::string(this->name()) + ".char_backend_stdio::tty_reset",
293 // tty_reset);
294 gs::SigHandler::get().add_sigint_handler(gs::Handler_CB::PASS);
295 gs::SigHandler::get().register_handler(std::string(this->name()) + ".char_backend_stdio::SIGINT_handler",
296 [&](int signo) {
297 if (signo == SIGINT) {
298 c_flag = true;
299 }
300 });
301 if (p_read_write) rcv_thread_id = std::thread(&char_backend_stdio::rcv_thread, this);
302
303 socket.register_b_transport(this, &char_backend_stdio::writefn);
304 std::atexit(tty_reset);
305 }
306
307 void end_of_elaboration() { socket.can_receive_any(); }
308
309 void enqueue(char c) { socket.enqueue(c); }
310
311 void writefn(tlm::tlm_generic_payload& txn, sc_core::sc_time& t)
312 {
313 uint8_t* data = txn.get_data_ptr();
314 if (!p_highlight.get_value().empty()) std::cout << p_highlight.get_value();
315 for (int i = 0; i < txn.get_streaming_width(); i++) {
316 putchar(data[i]);
317 if ((char)data[i] == '\n') {
318 expect_process();
319 line = "";
320 } else {
321 line = line + (char)data[i];
322 }
323 }
324 if (!p_highlight.get_value().empty()) std::cout << "\x1B[0m"; // ANSI color reset.
325 fflush(stdout);
326 }
327
328 void start_of_simulation()
329 {
330 if (!old_console_mode_valid) {
331 set_new_mode(true);
332 }
333 }
334
335 void end_of_simulation() { tty_reset(); }
336
338 {
339 gs::SigHandler::get().deregister_on_exit_cb(std::string(name()) + ".char_backend_stdio::tty_reset");
340 gs::SigHandler::get().deregister_handler(std::string(name()) + ".char_backend_stdio::SIGINT_handler");
341 m_running = false;
342 if (rcv_thread_id.joinable()) rcv_thread_id.join();
343 tty_reset();
344 }
345};
346extern "C" void module_register();
347#endif
Definition target.h:160
Definition char_backend_stdio.h:42
Definition biflow-socket.h:73
void can_receive_any()
can_receive_any Allow unlimited items to arrive.
Definition biflow-socket.h:263
void enqueue(T data)
enqueue Enqueue data to be sent (unlimited queue size) NOTE: Thread safe.
Definition biflow-socket.h:276
void register_b_transport(MODULE *mod, void(MODULE::*cb)(tlm::tlm_generic_payload &, sc_core::sc_time &))
Register b_transport to be called whenever data is received from the socket.
Definition biflow-socket.h:226