server: tcl_trace command
authorAustin Morton <austinpmorton@gmail.com>
Mon, 9 Mar 2015 09:34:52 +0000 (05:34 -0400)
committerPaul Fertser <fercerpav@gmail.com>
Sat, 5 Sep 2015 08:19:26 +0000 (09:19 +0100)
Implements async target trace output to the tcl server

Change-Id: I0178f6404447337d523782a1d2c317457030da40
Signed-off-by: Austin Morton <austinpmorton@gmail.com>
Reviewed-on: http://openocd.zylin.com/2588
Tested-by: jenkins
Reviewed-by: Paul Fertser <fercerpav@gmail.com>
doc/openocd.texi
src/server/tcl_server.c
src/target/armv7m_trace.c
src/target/target.c
src/target/target.h

index 63ab5deb5dc29bc00ea612adcdc1e97387891295..21141caae6b25723b4cfd90c6ae84925fd8315ba 100644 (file)
@@ -7570,7 +7570,7 @@ Defaulting to 0.
 @cindex ITM
 @cindex ETM
 
-@deffn Command {tpiu config} (@option{disable} | ((@option{external} | @option{internal @var{filename}}) @
+@deffn Command {tpiu config} (@option{disable} | ((@option{external} | @option{internal (@var{filename} | -)}) @
                (@option{sync @var{port_width}} | ((@option{manchester} | @option{uart}) @var{formatter_enable})) @
                @var{TRACECLKIN_freq} [@var{trace_freq}]))
 
@@ -7594,6 +7594,8 @@ output externally (with an additional UART or logic analyzer hardware);
 @item @option{internal @var{filename}} configure TPIU and debug adapter to
 gather trace data and append it to @var{filename} (which can be
 either a regular file or a named pipe);
+@item @option{internal -} configure TPIU and debug adapter to
+gather trace data, but not write to any file. Useful in conjunction with the @command{tcl_trace} command;
 @item @option{sync @var{port_width}} use synchronous parallel trace output
 mode, and set port width to @var{port_width};
 @item @option{manchester} use asynchronous SWO mode with Manchester
@@ -8699,6 +8701,28 @@ Defaults to off.
 
 @end deffn
 
+@section Tcl RPC server trace output
+@cindex RPC trace output
+
+Trace data is sent asynchronously to other commands being executed over
+the RPC server, so the port must be polled continuously.
+
+Target trace data is emitted as a Tcl associative array in the following format.
+
+@verbatim
+type target_trace data [trace-data-hex-encoded]
+@end verbatim
+
+@deffn {Command} tcl_trace [on/off]
+Toggle output of target trace data to the current Tcl RPC server.
+Only available from the Tcl RPC server.
+Defaults to off.
+
+See an example application here:
+@url{https://github.com/apmorton/OpenOcdTraceUtil} [OpenOcdTraceUtil]
+
+@end deffn
+
 @node FAQ
 @chapter FAQ
 @cindex faq
index 65f71cc988f19f64c652b227bf0bb8c9b41e461d..409567c9d90e9e515cee078dea3b3048409daf4e 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "tcl_server.h"
 #include <target/target.h>
+#include <helper/binarybuffer.h>
 
 #define TCL_SERVER_VERSION             "TCL Server 0.1"
 #define TCL_MAX_LINE                   (4096)
@@ -35,6 +36,7 @@ struct tcl_connection {
        int tc_outerror;/* flag an output error */
        enum target_state tc_laststate;
        bool tc_notify;
+       bool tc_trace;
 };
 
 static char *tcl_port;
@@ -87,6 +89,32 @@ static int tcl_target_callback_reset_handler(struct target *target,
        return ERROR_OK;
 }
 
+static int tcl_target_callback_trace_handler(struct target *target,
+               size_t len, uint8_t *data, void *priv)
+{
+       struct connection *connection = priv;
+       struct tcl_connection *tclc;
+       char *header = "type target_trace data ";
+       char *trailer = "\r\n\x1a";
+       size_t hex_len = len * 2 + 1;
+       size_t max_len = hex_len + strlen(header) + strlen(trailer);
+       char *buf, *hex;
+
+       tclc = connection->priv;
+
+       if (tclc->tc_trace) {
+               hex = malloc(hex_len);
+               buf = malloc(max_len);
+               hexify(hex, (const char *)data, len, hex_len);
+               snprintf(buf, max_len, "%s%s%s", header, hex, trailer);
+               tcl_output(connection, buf, strlen(buf));
+               free(hex);
+               free(buf);
+       }
+
+       return ERROR_OK;
+}
+
 /* write data out to a socket.
  *
  * this is a blocking write, so the return value must equal the length, if
@@ -132,6 +160,7 @@ static int tcl_new_connection(struct connection *connection)
 
        target_register_event_callback(tcl_target_callback_event_handler, connection);
        target_register_reset_callback(tcl_target_callback_reset_handler, connection);
+       target_register_trace_callback(tcl_target_callback_trace_handler, connection);
 
        return ERROR_OK;
 }
@@ -183,7 +212,7 @@ static int tcl_input(struct connection *connection)
 #undef ESTR
                } else {
                        tclc->tc_line[tclc->tc_lineoffset-1] = '\0';
-                       retval = command_run_line(connection->cmd_ctx, tclc->tc_line);
+                       command_run_line(connection->cmd_ctx, tclc->tc_line);
                        result = Jim_GetString(Jim_GetResult(interp), &reslen);
                        retval = tcl_output(connection, result, reslen);
                        if (retval != ERROR_OK)
@@ -209,6 +238,7 @@ static int tcl_closed(struct connection *connection)
 
        target_unregister_event_callback(tcl_target_callback_event_handler, connection);
        target_unregister_reset_callback(tcl_target_callback_reset_handler, connection);
+       target_unregister_trace_callback(tcl_target_callback_trace_handler, connection);
 
        return ERROR_OK;
 }
@@ -240,7 +270,24 @@ COMMAND_HANDLER(handle_tcl_notifications_command)
 
        if (connection != NULL && !strcmp(connection->service->name, "tcl")) {
                tclc = connection->priv;
-               return CALL_COMMAND_HANDLER(handle_command_parse_bool, &tclc->tc_notify, "Target Notification output is");
+               return CALL_COMMAND_HANDLER(handle_command_parse_bool, &tclc->tc_notify, "Target Notification output ");
+       } else {
+               LOG_ERROR("%s: can only be called from the tcl server", CMD_NAME);
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+}
+
+COMMAND_HANDLER(handle_tcl_trace_command)
+{
+       struct connection *connection = NULL;
+       struct tcl_connection *tclc = NULL;
+
+       if (CMD_CTX->output_handler_priv != NULL)
+               connection = CMD_CTX->output_handler_priv;
+
+       if (connection != NULL && !strcmp(connection->service->name, "tcl")) {
+               tclc = connection->priv;
+               return CALL_COMMAND_HANDLER(handle_command_parse_bool, &tclc->tc_trace, "Target trace output ");
        } else {
                LOG_ERROR("%s: can only be called from the tcl server", CMD_NAME);
                return ERROR_COMMAND_SYNTAX_ERROR;
@@ -264,6 +311,13 @@ static const struct command_registration tcl_command_handlers[] = {
                .help = "Target Notification output",
                .usage = "[on|off]",
        },
+       {
+               .name = "tcl_trace",
+               .handler = handle_tcl_trace_command,
+               .mode = COMMAND_EXEC,
+               .help = "Target trace output",
+               .usage = "[on|off]",
+       },
        COMMAND_REGISTRATION_DONE
 };
 
index eb07a6e64056f9db57907b0b28c22496159c5069..3592bad0f64c2f59f8120b2e5787f80676130b12 100644 (file)
@@ -35,11 +35,15 @@ static int armv7m_poll_trace(void *target)
        if (retval != ERROR_OK || !size)
                return retval;
 
-       if (fwrite(buf, 1, size, armv7m->trace_config.trace_file) == size)
-               fflush(armv7m->trace_config.trace_file);
-       else {
-               LOG_ERROR("Error writing to the trace destination file");
-               return ERROR_FAIL;
+       target_call_trace_callbacks(target, size, buf);
+
+       if (armv7m->trace_config.trace_file != NULL) {
+               if (fwrite(buf, 1, size, armv7m->trace_config.trace_file) == size)
+                       fflush(armv7m->trace_config.trace_file);
+               else {
+                       LOG_ERROR("Error writing to the trace destination file");
+                       return ERROR_FAIL;
+               }
        }
 
        return ERROR_OK;
@@ -183,10 +187,13 @@ COMMAND_HANDLER(handle_tpiu_config_command)
                                return ERROR_COMMAND_SYNTAX_ERROR;
 
                        armv7m->trace_config.config_type = INTERNAL;
-                       armv7m->trace_config.trace_file = fopen(CMD_ARGV[cmd_idx], "ab");
-                       if (!armv7m->trace_config.trace_file) {
-                               LOG_ERROR("Can't open trace destination file");
-                               return ERROR_FAIL;
+
+                       if (strcmp(CMD_ARGV[cmd_idx], "-") != 0) {
+                               armv7m->trace_config.trace_file = fopen(CMD_ARGV[cmd_idx], "ab");
+                               if (!armv7m->trace_config.trace_file) {
+                                       LOG_ERROR("Can't open trace destination file");
+                                       return ERROR_FAIL;
+                               }
                        }
                }
                cmd_idx++;
index 89fee4aaa5fa7899fed3dfd96ee448bd734ab997..19e5d6515e9ba4f88a2ed06e77a975b12639d0b9 100644 (file)
@@ -140,6 +140,7 @@ struct target *all_targets;
 static struct target_event_callback *target_event_callbacks;
 static struct target_timer_callback *target_timer_callbacks;
 LIST_HEAD(target_reset_callback_list);
+LIST_HEAD(target_trace_callback_list);
 static const int polling_interval = 100;
 
 static const Jim_Nvp nvp_assert[] = {
@@ -1350,6 +1351,28 @@ int target_register_reset_callback(int (*callback)(struct target *target,
        return ERROR_OK;
 }
 
+int target_register_trace_callback(int (*callback)(struct target *target,
+               size_t len, uint8_t *data, void *priv), void *priv)
+{
+       struct target_trace_callback *entry;
+
+       if (callback == NULL)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       entry = malloc(sizeof(struct target_trace_callback));
+       if (entry == NULL) {
+               LOG_ERROR("error allocating buffer for trace callback entry");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       entry->callback = callback;
+       entry->priv = priv;
+       list_add(&entry->list, &target_trace_callback_list);
+
+
+       return ERROR_OK;
+}
+
 int target_register_timer_callback(int (*callback)(void *priv), int time_ms, int periodic, void *priv)
 {
        struct target_timer_callback **callbacks_p = &target_timer_callbacks;
@@ -1427,6 +1450,25 @@ int target_unregister_reset_callback(int (*callback)(struct target *target,
        return ERROR_OK;
 }
 
+int target_unregister_trace_callback(int (*callback)(struct target *target,
+               size_t len, uint8_t *data, void *priv), void *priv)
+{
+       struct target_trace_callback *entry;
+
+       if (callback == NULL)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       list_for_each_entry(entry, &target_trace_callback_list, list) {
+               if (entry->callback == callback && entry->priv == priv) {
+                       list_del(&entry->list);
+                       free(entry);
+                       break;
+               }
+       }
+
+       return ERROR_OK;
+}
+
 int target_unregister_timer_callback(int (*callback)(void *priv), void *priv)
 {
        if (callback == NULL)
@@ -1480,6 +1522,16 @@ int target_call_reset_callbacks(struct target *target, enum target_reset_mode re
        return ERROR_OK;
 }
 
+int target_call_trace_callbacks(struct target *target, size_t len, uint8_t *data)
+{
+       struct target_trace_callback *callback;
+
+       list_for_each_entry(callback, &target_trace_callback_list, list)
+               callback->callback(target, len, data, callback->priv);
+
+       return ERROR_OK;
+}
+
 static int target_timer_callback_periodic_restart(
                struct target_timer_callback *cb, struct timeval *now)
 {
index 7471c1b330657b27e6e7313a9277f0ef344a8274..4faf3119f6a02efb9fb864eabbb8e8e7674743d6 100644 (file)
@@ -292,6 +292,12 @@ struct target_reset_callback {
        int (*callback)(struct target *target, enum target_reset_mode reset_mode, void *priv);
 };
 
+struct target_trace_callback {
+       struct list_head list;
+       void *priv;
+       int (*callback)(struct target *target, size_t len, uint8_t *data, void *priv);
+};
+
 struct target_timer_callback {
        int (*callback)(void *priv);
        int time_ms;
@@ -323,6 +329,15 @@ int target_unregister_reset_callback(
                enum target_reset_mode reset_mode, void *priv),
                void *priv);
 
+int target_register_trace_callback(
+               int (*callback)(struct target *target,
+               size_t len, uint8_t *data, void *priv),
+               void *priv);
+int target_unregister_trace_callback(
+               int (*callback)(struct target *target,
+               size_t len, uint8_t *data, void *priv),
+               void *priv);
+
 /* Poll the status of the target, detect any error conditions and report them.
  *
  * Also note that this fn will clear such error conditions, so a subsequent
@@ -341,6 +356,7 @@ int target_resume(struct target *target, int current, uint32_t address,
 int target_halt(struct target *target);
 int target_call_event_callbacks(struct target *target, enum target_event event);
 int target_call_reset_callbacks(struct target *target, enum target_reset_mode reset_mode);
+int target_call_trace_callbacks(struct target *target, size_t len, uint8_t *data);
 
 /**
  * The period is very approximate, the callback can happen much more often