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_default_mode();
76 }
77 }
78
79 static void set_raw_mode()
80 {
81 if (!old_console_mode_valid && GetConsoleMode(stdin_fd, &old_console_mode)) {
82 old_console_mode_valid = true;
83 }
84
86 }
87
88 static void set_default_mode()
89 {
91 }
92
93 descriptor_t get_stdin_descriptor()
94 {
97 SCP_WARN(())("char_backend_stdio: Failed to get standard input descriptor. Error: {}", GetLastError());
98 }
99
101 }
102
104 {
105 for (DWORD i = 0; i < cNumRead; ++i) {
106 const INPUT_RECORD& record = irInBuf[i];
107 if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) {
108 enqueue(record.Event.KeyEvent.uChar.AsciiChar);
109 }
110 }
111 }
112
113 void rcv_thread()
114 {
115 DWORD cNumRead = 0;
116
117 while (m_running) {
118 DWORD waitResult = WaitForSingleObject(stdin_fd, RCV_POLL_TIMEOUT_MS);
119
120 if (waitResult == WAIT_TIMEOUT) {
121 if (c_flag) {
122 c_flag = false;
123 enqueue('\x03');
124 }
125 continue;
126 } else if (waitResult == WAIT_OBJECT_0) {
129 } else {
130 SCP_WARN(())("char_backend_stdio: Failed to read console input. Error: {}", GetLastError());
131 }
132 } else {
133 break;
134 }
135 }
136 }
137#else
138 descriptor_t get_stdin_descriptor() { return STDIN_FILENO; } /* stdin */
139
140 static void restore_old_mode()
141 {
142 if (tcsetattr(stdin_fd, TCSANOW, &old_console_mode) == -1) {
143 set_default_mode();
144 }
145 }
146
147 static void set_raw_mode()
148 {
149 console_mode_t tty;
150 tcgetattr(stdin_fd, &tty);
151 if (!old_console_mode_valid) {
152 old_console_mode = tty;
153 old_console_mode_valid = true;
154 }
155 tty.c_lflag &= ~(ECHO | ICANON | IEXTEN);
156 tcsetattr(stdin_fd, TCSANOW, &tty);
157 }
158
159 static void set_default_mode()
160 {
161 console_mode_t tty;
162 tcgetattr(stdin_fd, &tty);
163 tty.c_lflag |= ECHO | ICANON | IEXTEN;
164 tcsetattr(stdin_fd, TCSANOW, &tty);
165 }
166
167 void rcv_thread()
168 {
169 int fd = STDIN_FILENO;
170 struct pollfd rcv_monitor;
171 rcv_monitor.fd = fd;
172 rcv_monitor.events = POLLIN;
173
174 while (m_running) {
175 int ret = poll(&rcv_monitor, 1, RCV_POLL_TIMEOUT_MS);
176 if ((ret == -1 && errno == EINTR) || (ret == 0) /*timeout*/) {
177 if (c_flag) {
178 c_flag = false;
179 enqueue('\x03');
180 }
181 continue;
182 } else if (rcv_monitor.revents & POLLIN) {
183 char c;
184 int r = read(fd, &c, 1);
185 if (r == 1) {
186 enqueue(c);
187 }
188 if (r == 0) {
189 break;
190 }
191 } else {
192 break;
193 }
194 }
195 }
196#endif
197
198public:
199 void catch_fn(int signo) {}
200
201 static void tty_reset()
202 {
203 if (old_console_mode_valid) {
204 restore_old_mode();
205 } else {
206 set_default_mode();
207 }
208 }
209
210 // trim from end of string (right)
211 std::string& rtrim(std::string& s)
212 {
213 const char* t = "\n\r\f\v";
214 s.erase(s.find_last_not_of(t) + 1);
215 return s;
216 }
217 std::string getecmdstr(const std::string& cmd)
218 {
219 return ecmd.substr(cmd.length(), ecmd.find_first_of("\n") - cmd.length());
220 }
221 void process()
222 {
223 processing = true;
224 expect_process();
225 }
226 void expect_process()
227 {
228 if (!processing) return;
229 const std::string expectstr = "expect ";
230 const std::string sendstr = "send ";
231 const std::string waitstr = "wait ";
232 const std::string exitstr = "exit";
233 do {
234 if (ecmd.substr(0, expectstr.length()) == expectstr) {
235 std::string str = getecmdstr(expectstr);
236 std::regex restr(str);
237 if (std::regex_search(line, restr)) {
238 SCP_WARN(())("Found expect string {}", rtrim(line));
239 ecmd.erase(0, ecmd.find_first_of("\n"));
240 if (ecmd != "") ecmd.erase(0, 1);
241 if (ecmd.substr(0, expectstr.length()) == expectstr) break;
242 continue;
243 }
244 }
245 if (ecmd.substr(0, sendstr.length()) == sendstr) {
246 SCP_WARN(())("Sending expect string {}", getecmdstr(sendstr));
247 for (char c : getecmdstr(sendstr)) {
248 enqueue(c);
249 }
250 enqueue('\n');
251 ecmd.erase(0, ecmd.find_first_of("\n"));
252 if (ecmd != "") ecmd.erase(0, 1);
253 continue;
254 }
255 if (ecmd.substr(0, waitstr.length()) == waitstr) {
256 SCP_WARN(())("Expect waiting {}s", getecmdstr(waitstr));
257 processing = false;
258 ecmdev.notify(stod(getecmdstr(waitstr)), sc_core::SC_SEC);
259 ecmd.erase(0, ecmd.find_first_of("\n"));
260 if (ecmd != "") ecmd.erase(0, 1);
261 break;
262 }
263 if (ecmd.substr(0, exitstr.length()) == exitstr) {
264 SCP_WARN(())("Expect caused exit");
265 sc_core::sc_stop();
266 }
267 break;
268 } while (true);
269 }
270
271 /*
272 * char_backend_stdio constructor
273 * Parameters:
274 * \param read_write bool : when set to true, accept input from STDIO
275 * \param expect String : An 'expect' like string of commands, each command should be separated by \n
276 * The acceptable commands are:
277 * expect [string] : dont process any more commands until the "string" is seen on the output (STDIO)
278 * send [string] : send "string" to the input buffer (NB this will happen whether of not read_write is set
279 * wait [float] : Wait for "float" (simulated) seconds, until processing continues.
280 * exit : Cause the simulation to terminate normally.
281 */
282 char_backend_stdio(sc_core::sc_module_name name)
283 : sc_core::sc_module(name)
284 , p_read_write("read_write", true, "read_write if true start rcv_thread")
285 , p_expect("expect", "", "string of expect commands")
286 , p_highlight("ansi_highlight", "", "ANSI highlight code to use for output, default bold")
287 , socket("biflow_socket")
288 , m_running(true)
289 , c_flag(false)
290 {
291 SCP_TRACE(()) << "CharBackendStdio constructor";
292
293 stdin_fd = get_stdin_descriptor();
294
295 ecmd = p_expect;
296 SC_METHOD(process);
297 sensitive << ecmdev;
298 if (p_expect.get_value() != "") {
299 ecmd = p_expect;
300 processing = true;
301 SCP_WARN(())("Processing expect string {}", ecmd);
302 }
303
304 gs::SigHandler::get().register_on_exit_cb(std::string(this->name()) + ".char_backend_stdio::tty_reset",
305 tty_reset);
306 gs::SigHandler::get().add_sigint_handler(gs::Handler_CB::PASS);
307 gs::SigHandler::get().register_handler(std::string(this->name()) + ".char_backend_stdio::SIGINT_handler",
308 [&](int signo) {
309 if (signo == SIGINT) {
310 c_flag = true;
311 }
312 });
313 if (p_read_write) rcv_thread_id = std::thread(&char_backend_stdio::rcv_thread, this);
314
315 socket.register_b_transport(this, &char_backend_stdio::writefn);
316 std::atexit(tty_reset);
317 }
318
319 void end_of_elaboration() { socket.can_receive_any(); }
320
321 void enqueue(char c) { socket.enqueue(c); }
322
323 void writefn(tlm::tlm_generic_payload& txn, sc_core::sc_time& t)
324 {
325 uint8_t* data = txn.get_data_ptr();
326 if (!p_highlight.get_value().empty()) std::cout << p_highlight.get_value();
327 for (int i = 0; i < txn.get_streaming_width(); i++) {
328 putchar(data[i]);
329 if ((char)data[i] == '\n') {
330 expect_process();
331 line = "";
332 } else {
333 line = line + (char)data[i];
334 }
335 }
336 if (!p_highlight.get_value().empty()) std::cout << "\x1B[0m"; // ANSI color reset.
337 fflush(stdout);
338 }
339
340 void start_of_simulation()
341 {
342 if (!old_console_mode_valid) {
343 set_raw_mode();
344 }
345 }
346
347 void end_of_simulation() { tty_reset(); }
348
350 {
351 gs::SigHandler::get().deregister_on_exit_cb(std::string(name()) + ".char_backend_stdio::tty_reset");
352 gs::SigHandler::get().deregister_handler(std::string(name()) + ".char_backend_stdio::SIGINT_handler");
353 m_running = false;
354 if (rcv_thread_id.joinable()) rcv_thread_id.join();
355 tty_reset();
356 }
357};
358extern "C" void module_register();
359#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