1793453a962b5f3f787f00e5fe7274cac20d35e4
[fw/openocd] / src / server / tcl_server.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2
3 /***************************************************************************
4  *   Copyright (C) 2010 Ã˜yvind Harboe                                      *
5  *   oyvind.harboe@zylin.com                                               *
6  ***************************************************************************/
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11
12 #include "tcl_server.h"
13 #include <target/target.h>
14 #include <helper/binarybuffer.h>
15
16 #define TCL_SERVER_VERSION              "TCL Server 0.1"
17 #define TCL_LINE_INITIAL                (4*1024)
18 #define TCL_LINE_MAX                    (4*1024*1024)
19
20 struct tcl_connection {
21         int tc_linedrop;
22         int tc_lineoffset;
23         int tc_line_size;
24         char *tc_line;
25         int tc_outerror;/* flag an output error */
26         enum target_state tc_laststate;
27         bool tc_notify;
28         bool tc_trace;
29 };
30
31 static char *tcl_port;
32
33 /* handlers */
34 static int tcl_new_connection(struct connection *connection);
35 static int tcl_input(struct connection *connection);
36 static int tcl_output(struct connection *connection, const void *buf, ssize_t len);
37 static int tcl_closed(struct connection *connection);
38
39 static int tcl_target_callback_event_handler(struct target *target,
40                 enum target_event event, void *priv)
41 {
42         struct connection *connection = priv;
43         struct tcl_connection *tclc;
44         char buf[256];
45
46         tclc = connection->priv;
47
48         if (tclc->tc_notify) {
49                 snprintf(buf, sizeof(buf), "type target_event event %s\r\n\x1a", target_event_name(event));
50                 tcl_output(connection, buf, strlen(buf));
51         }
52
53         if (tclc->tc_laststate != target->state) {
54                 tclc->tc_laststate = target->state;
55                 if (tclc->tc_notify) {
56                         snprintf(buf, sizeof(buf), "type target_state state %s\r\n\x1a", target_state_name(target));
57                         tcl_output(connection, buf, strlen(buf));
58                 }
59         }
60
61         return ERROR_OK;
62 }
63
64 static int tcl_target_callback_reset_handler(struct target *target,
65                 enum target_reset_mode reset_mode, void *priv)
66 {
67         struct connection *connection = priv;
68         struct tcl_connection *tclc;
69         char buf[256];
70
71         tclc = connection->priv;
72
73         if (tclc->tc_notify) {
74                 snprintf(buf, sizeof(buf), "type target_reset mode %s\r\n\x1a", target_reset_mode_name(reset_mode));
75                 tcl_output(connection, buf, strlen(buf));
76         }
77
78         return ERROR_OK;
79 }
80
81 static int tcl_target_callback_trace_handler(struct target *target,
82                 size_t len, uint8_t *data, void *priv)
83 {
84         struct connection *connection = priv;
85         struct tcl_connection *tclc;
86         char *header = "type target_trace data ";
87         char *trailer = "\r\n\x1a";
88         size_t hex_len = len * 2 + 1;
89         size_t max_len = hex_len + strlen(header) + strlen(trailer);
90         char *buf, *hex;
91
92         tclc = connection->priv;
93
94         if (tclc->tc_trace) {
95                 hex = malloc(hex_len);
96                 buf = malloc(max_len);
97                 hexify(hex, data, len, hex_len);
98                 snprintf(buf, max_len, "%s%s%s", header, hex, trailer);
99                 tcl_output(connection, buf, strlen(buf));
100                 free(hex);
101                 free(buf);
102         }
103
104         return ERROR_OK;
105 }
106
107 /* write data out to a socket.
108  *
109  * this is a blocking write, so the return value must equal the length, if
110  * that is not the case then flag the connection with an output error.
111  */
112 int tcl_output(struct connection *connection, const void *data, ssize_t len)
113 {
114         ssize_t wlen;
115         struct tcl_connection *tclc;
116
117         tclc = connection->priv;
118         if (tclc->tc_outerror)
119                 return ERROR_SERVER_REMOTE_CLOSED;
120
121         wlen = connection_write(connection, data, len);
122
123         if (wlen == len)
124                 return ERROR_OK;
125
126         LOG_ERROR("error during write: %d != %d", (int)wlen, (int)len);
127         tclc->tc_outerror = 1;
128         return ERROR_SERVER_REMOTE_CLOSED;
129 }
130
131 /* connections */
132 static int tcl_new_connection(struct connection *connection)
133 {
134         struct tcl_connection *tclc;
135
136         tclc = calloc(1, sizeof(struct tcl_connection));
137         if (!tclc)
138                 return ERROR_CONNECTION_REJECTED;
139
140         tclc->tc_line_size = TCL_LINE_INITIAL;
141         tclc->tc_line = malloc(tclc->tc_line_size);
142         if (!tclc->tc_line) {
143                 free(tclc);
144                 return ERROR_CONNECTION_REJECTED;
145         }
146
147         connection->priv = tclc;
148
149         struct target *target = get_current_target_or_null(connection->cmd_ctx);
150         if (target)
151                 tclc->tc_laststate = target->state;
152
153         /* store the connection object on cmd_ctx so we can access it from command handlers */
154         connection->cmd_ctx->output_handler_priv = connection;
155
156         target_register_event_callback(tcl_target_callback_event_handler, connection);
157         target_register_reset_callback(tcl_target_callback_reset_handler, connection);
158         target_register_trace_callback(tcl_target_callback_trace_handler, connection);
159
160         return ERROR_OK;
161 }
162
163 static int tcl_input(struct connection *connection)
164 {
165         Jim_Interp *interp = (Jim_Interp *)connection->cmd_ctx->interp;
166         int retval;
167         int i;
168         ssize_t rlen;
169         const char *result;
170         int reslen;
171         struct tcl_connection *tclc;
172         unsigned char in[256];
173         char *tc_line_new;
174         int tc_line_size_new;
175
176         rlen = connection_read(connection, &in, sizeof(in));
177         if (rlen <= 0) {
178                 if (rlen < 0)
179                         LOG_ERROR("error during read: %s", strerror(errno));
180                 return ERROR_SERVER_REMOTE_CLOSED;
181         }
182
183         tclc = connection->priv;
184         if (!tclc)
185                 return ERROR_CONNECTION_REJECTED;
186
187         /* push as much data into the line as possible */
188         for (i = 0; i < rlen; i++) {
189                 /* buffer the data */
190                 tclc->tc_line[tclc->tc_lineoffset] = in[i];
191                 if (tclc->tc_lineoffset + 1 < tclc->tc_line_size) {
192                         tclc->tc_lineoffset++;
193                 } else if (tclc->tc_line_size >= TCL_LINE_MAX) {
194                         /* maximum line size reached, drop line */
195                         tclc->tc_linedrop = 1;
196                 } else {
197                         /* grow line buffer: exponential below 1 MB, linear above */
198                         if (tclc->tc_line_size <= 1*1024*1024)
199                                 tc_line_size_new = tclc->tc_line_size * 2;
200                         else
201                                 tc_line_size_new = tclc->tc_line_size + 1*1024*1024;
202
203                         if (tc_line_size_new > TCL_LINE_MAX)
204                                 tc_line_size_new = TCL_LINE_MAX;
205
206                         tc_line_new = realloc(tclc->tc_line, tc_line_size_new);
207                         if (!tc_line_new) {
208                                 tclc->tc_linedrop = 1;
209                         } else {
210                                 tclc->tc_line = tc_line_new;
211                                 tclc->tc_line_size = tc_line_size_new;
212                                 tclc->tc_lineoffset++;
213                         }
214
215                 }
216
217                 /* ctrl-z is end of command. When testing from telnet, just
218                  * press ctrl-z a couple of times first to put telnet into the
219                  * mode where it will send 0x1a in response to pressing ctrl-z
220                  */
221                 if (in[i] != '\x1a')
222                         continue;
223
224                 /* process the line */
225                 if (tclc->tc_linedrop) {
226 #define ESTR "line too long\n"
227                         retval = tcl_output(connection, ESTR, sizeof(ESTR));
228                         if (retval != ERROR_OK)
229                                 return retval;
230 #undef ESTR
231                 } else {
232                         tclc->tc_line[tclc->tc_lineoffset-1] = '\0';
233                         command_run_line(connection->cmd_ctx, tclc->tc_line);
234                         result = Jim_GetString(Jim_GetResult(interp), &reslen);
235                         retval = tcl_output(connection, result, reslen);
236                         if (retval != ERROR_OK)
237                                 return retval;
238                         /* Always output ctrl-z as end of line to allow multiline results */
239                         tcl_output(connection, "\x1a", 1);
240                 }
241
242                 tclc->tc_lineoffset = 0;
243                 tclc->tc_linedrop = 0;
244         }
245
246         return ERROR_OK;
247 }
248
249 static int tcl_closed(struct connection *connection)
250 {
251         struct tcl_connection *tclc;
252         tclc = connection->priv;
253
254         /* cleanup connection context */
255         if (tclc) {
256                 free(tclc->tc_line);
257                 free(tclc);
258                 connection->priv = NULL;
259         }
260
261         target_unregister_event_callback(tcl_target_callback_event_handler, connection);
262         target_unregister_reset_callback(tcl_target_callback_reset_handler, connection);
263         target_unregister_trace_callback(tcl_target_callback_trace_handler, connection);
264
265         return ERROR_OK;
266 }
267
268 static const struct service_driver tcl_service_driver = {
269         .name = "tcl",
270         .new_connection_during_keep_alive_handler = NULL,
271         .new_connection_handler = tcl_new_connection,
272         .input_handler = tcl_input,
273         .connection_closed_handler = tcl_closed,
274         .keep_client_alive_handler = NULL,
275 };
276
277 int tcl_init(void)
278 {
279         if (strcmp(tcl_port, "disabled") == 0) {
280                 LOG_INFO("tcl server disabled");
281                 return ERROR_OK;
282         }
283
284         return add_service(&tcl_service_driver, tcl_port, CONNECTION_LIMIT_UNLIMITED, NULL);
285 }
286
287 COMMAND_HANDLER(handle_tcl_port_command)
288 {
289         return CALL_COMMAND_HANDLER(server_pipe_command, &tcl_port);
290 }
291
292 COMMAND_HANDLER(handle_tcl_notifications_command)
293 {
294         struct connection *connection = NULL;
295         struct tcl_connection *tclc = NULL;
296
297         if (CMD_CTX->output_handler_priv)
298                 connection = CMD_CTX->output_handler_priv;
299
300         if (connection && !strcmp(connection->service->name, "tcl")) {
301                 tclc = connection->priv;
302                 return CALL_COMMAND_HANDLER(handle_command_parse_bool, &tclc->tc_notify, "Target Notification output ");
303         } else {
304                 LOG_ERROR("%s: can only be called from the tcl server", CMD_NAME);
305                 return ERROR_COMMAND_SYNTAX_ERROR;
306         }
307 }
308
309 COMMAND_HANDLER(handle_tcl_trace_command)
310 {
311         struct connection *connection = NULL;
312         struct tcl_connection *tclc = NULL;
313
314         if (CMD_CTX->output_handler_priv)
315                 connection = CMD_CTX->output_handler_priv;
316
317         if (connection && !strcmp(connection->service->name, "tcl")) {
318                 tclc = connection->priv;
319                 return CALL_COMMAND_HANDLER(handle_command_parse_bool, &tclc->tc_trace, "Target trace output ");
320         } else {
321                 LOG_ERROR("%s: can only be called from the tcl server", CMD_NAME);
322                 return ERROR_COMMAND_SYNTAX_ERROR;
323         }
324 }
325
326 static const struct command_registration tcl_command_handlers[] = {
327         {
328                 .name = "tcl_port",
329                 .handler = handle_tcl_port_command,
330                 .mode = COMMAND_CONFIG,
331                 .help = "Specify port on which to listen "
332                         "for incoming Tcl syntax.  "
333                         "Read help on 'gdb_port'.",
334                 .usage = "[port_num]",
335         },
336         {
337                 .name = "tcl_notifications",
338                 .handler = handle_tcl_notifications_command,
339                 .mode = COMMAND_EXEC,
340                 .help = "Target Notification output",
341                 .usage = "[on|off]",
342         },
343         {
344                 .name = "tcl_trace",
345                 .handler = handle_tcl_trace_command,
346                 .mode = COMMAND_EXEC,
347                 .help = "Target trace output",
348                 .usage = "[on|off]",
349         },
350         COMMAND_REGISTRATION_DONE
351 };
352
353 int tcl_register_commands(struct command_context *cmd_ctx)
354 {
355         tcl_port = strdup("6666");
356         return register_commands(cmd_ctx, NULL, tcl_command_handlers);
357 }
358
359 void tcl_service_free(void)
360 {
361         free(tcl_port);
362 }