server: tcl_notifications command 36/2336/8
authorAustin Morton <austinpmorton@gmail.com>
Sat, 21 Feb 2015 09:20:33 +0000 (04:20 -0500)
committerPaul Fertser <fercerpav@gmail.com>
Mon, 9 Mar 2015 06:30:30 +0000 (06:30 +0000)
Implements async target notifications to the tcl server

Change-Id: I4d83e9fa209e95426c440030597f99e9f0c3b260
Signed-off-by: Austin Morton <austinpmorton@gmail.com>
Reviewed-on: http://openocd.zylin.com/2336
Reviewed-by: Paul Fertser <fercerpav@gmail.com>
Tested-by: jenkins
Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk>
doc/openocd.texi
src/server/tcl_server.c
src/target/target.c
src/target/target.h

index c708c20613b11d62e9a82c8cca04d62db4d471ec..9a697e1d4713a62e8cbebd922a0ff316fe1000f7 100644 (file)
@@ -8642,6 +8642,28 @@ Remember that most of the OpenOCD commands need to be prefixed with
 
 See @file{contrib/rpc_examples/} for specific client implementations.
 
+@section Tcl RPC server notifications
+@cindex RPC Notifications
+
+Notifications are sent asynchronously to other commands being executed over
+the RPC server, so the port must be polled continuously.
+
+Target event, state and reset notifications are emitted as Tcl associative arrays
+in the following format.
+
+@verbatim
+type target_event event [event-name]
+type target_state state [state-name]
+type target_reset mode [reset-mode]
+@end verbatim
+
+@deffn {Command} tcl_notifications [on/off]
+Toggle output of target notifications to the current Tcl RPC server.
+Only available from the Tcl RPC server.
+Defaults to off.
+
+@end deffn
+
 @node FAQ
 @chapter FAQ
 @cindex faq
index b62f1a37e2c99e584e61020f9a13dd0d53fc9206..d6828f12aab7da54076666ffb38d43cfad1d6d6f 100644 (file)
@@ -23,6 +23,7 @@
 #endif
 
 #include "tcl_server.h"
+#include <target/target.h>
 
 #define TCL_SERVER_VERSION             "TCL Server 0.1"
 #define TCL_MAX_LINE                   (4096)
@@ -32,6 +33,8 @@ struct tcl_connection {
        int tc_lineoffset;
        char tc_line[TCL_MAX_LINE];
        int tc_outerror;/* flag an output error */
+       enum target_state tc_laststate;
+       bool tc_notify;
 };
 
 static char *tcl_port;
@@ -42,6 +45,48 @@ static int tcl_input(struct connection *connection);
 static int tcl_output(struct connection *connection, const void *buf, ssize_t len);
 static int tcl_closed(struct connection *connection);
 
+static int tcl_target_callback_event_handler(struct target *target,
+               enum target_event event, void *priv)
+{
+       struct connection *connection = priv;
+       struct tcl_connection *tclc;
+       char buf[256];
+
+       tclc = connection->priv;
+
+       if (tclc->tc_notify) {
+               snprintf(buf, sizeof(buf), "type target_event event %s\r\n\x1a", target_event_name(event));
+               tcl_output(connection, buf, strlen(buf));
+       }
+
+       if (tclc->tc_laststate != target->state) {
+               tclc->tc_laststate = target->state;
+               if (tclc->tc_notify) {
+                       snprintf(buf, sizeof(buf), "type target_state state %s\r\n\x1a", target_state_name(target));
+                       tcl_output(connection, buf, strlen(buf));
+               }
+       }
+
+       return ERROR_OK;
+}
+
+static int tcl_target_callback_reset_handler(struct target *target,
+               enum target_reset_mode reset_mode, void *priv)
+{
+       struct connection *connection = priv;
+       struct tcl_connection *tclc;
+       char buf[256];
+
+       tclc = connection->priv;
+
+       if (tclc->tc_notify) {
+               snprintf(buf, sizeof(buf), "type target_reset mode %s\r\n\x1a", target_reset_mode_name(reset_mode));
+               tcl_output(connection, buf, strlen(buf));
+       }
+
+       return ERROR_OK;
+}
+
 /* write data out to a socket.
  *
  * this is a blocking write, so the return value must equal the length, if
@@ -77,6 +122,17 @@ static int tcl_new_connection(struct connection *connection)
 
        memset(tclc, 0, sizeof(struct tcl_connection));
        connection->priv = tclc;
+
+       struct target *target = get_current_target(connection->cmd_ctx);
+       if (target != NULL)
+               tclc->tc_laststate = target->state;
+
+       /* store the connection object on cmd_ctx so we can access it from command handlers */
+       connection->cmd_ctx->output_handler_priv = connection;
+
+       target_register_event_callback(tcl_target_callback_event_handler, connection);
+       target_register_reset_callback(tcl_target_callback_reset_handler, connection);
+
        return ERROR_OK;
 }
 
@@ -127,10 +183,8 @@ static int tcl_input(struct connection *connection)
 #undef ESTR
                } else {
                        tclc->tc_line[tclc->tc_lineoffset-1] = '\0';
-                       LOG_DEBUG("Executing script:\n %s", tclc->tc_line);
-                       retval = Jim_Eval_Named(interp, tclc->tc_line, "remote:connection", 1);
+                       retval = command_run_line(connection->cmd_ctx, tclc->tc_line);
                        result = Jim_GetString(Jim_GetResult(interp), &reslen);
-                       LOG_DEBUG("Result: %d\n %s", retval, result);
                        retval = tcl_output(connection, result, reslen);
                        if (retval != ERROR_OK)
                                return retval;
@@ -152,6 +206,10 @@ static int tcl_closed(struct connection *connection)
                free(connection->priv);
                connection->priv = NULL;
        }
+
+       target_unregister_event_callback(tcl_target_callback_event_handler, connection);
+       target_unregister_reset_callback(tcl_target_callback_reset_handler, connection);
+
        return ERROR_OK;
 }
 
@@ -172,6 +230,23 @@ COMMAND_HANDLER(handle_tcl_port_command)
        return CALL_COMMAND_HANDLER(server_pipe_command, &tcl_port);
 }
 
+COMMAND_HANDLER(handle_tcl_notifications_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_notify, "Target Notification output is");
+       } else {
+               LOG_ERROR("%s: can only be called from the tcl server", CMD_NAME);
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+}
+
 static const struct command_registration tcl_command_handlers[] = {
        {
                .name = "tcl_port",
@@ -182,6 +257,13 @@ static const struct command_registration tcl_command_handlers[] = {
                        "Read help on 'gdb_port'.",
                .usage = "[port_num]",
        },
+       {
+               .name = "tcl_notifications",
+               .handler = handle_tcl_notifications_command,
+               .mode = COMMAND_EXEC,
+               .help = "Target Notification output",
+               .usage = "[on|off]",
+       },
        COMMAND_REGISTRATION_DONE
 };
 
index c87c88986c9f22678666af6d5611d1f81904a540..fb141789e74e65812dba4059523b9a378f978973 100644 (file)
@@ -139,6 +139,7 @@ static struct target_type *target_types[] = {
 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);
 static const int polling_interval = 100;
 
 static const Jim_Nvp nvp_assert[] = {
@@ -280,6 +281,28 @@ const char *target_state_name(struct target *t)
        return cp;
 }
 
+const char *target_event_name(enum target_event event)
+{
+       const char *cp;
+       cp = Jim_Nvp_value2name_simple(nvp_target_event, event)->name;
+       if (!cp) {
+               LOG_ERROR("Invalid target event: %d", (int)(event));
+               cp = "(*BUG*unknown*BUG*)";
+       }
+       return cp;
+}
+
+const char *target_reset_mode_name(enum target_reset_mode reset_mode)
+{
+       const char *cp;
+       cp = Jim_Nvp_value2name_simple(nvp_reset_modes, reset_mode)->name;
+       if (!cp) {
+               LOG_ERROR("Invalid target reset mode: %d", (int)(reset_mode));
+               cp = "(*BUG*unknown*BUG*)";
+       }
+       return cp;
+}
+
 /* determine the number of the new target */
 static int new_target_number(void)
 {
@@ -601,6 +624,10 @@ static int target_process_reset(struct command_context *cmd_ctx, enum target_res
                return ERROR_FAIL;
        }
 
+       struct target *target;
+       for (target = all_targets; target; target = target->next)
+               target_call_reset_callbacks(target, reset_mode);
+
        /* disable polling during reset to make reset event scripts
         * more predictable, i.e. dr/irscan & pathmove in events will
         * not have JTAG operations injected into the middle of a sequence.
@@ -623,7 +650,6 @@ static int target_process_reset(struct command_context *cmd_ctx, enum target_res
        /* We want any events to be processed before the prompt */
        retval = target_call_timer_callbacks_now();
 
-       struct target *target;
        for (target = all_targets; target; target = target->next) {
                target->type->check_reset(target);
                target->running_alg = false;
@@ -1312,6 +1338,28 @@ int target_register_event_callback(int (*callback)(struct target *target,
        return ERROR_OK;
 }
 
+int target_register_reset_callback(int (*callback)(struct target *target,
+               enum target_reset_mode reset_mode, void *priv), void *priv)
+{
+       struct target_reset_callback *entry;
+
+       if (callback == NULL)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       entry = malloc(sizeof(struct target_reset_callback));
+       if (entry == NULL) {
+               LOG_ERROR("error allocating buffer for reset callback entry");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       entry->callback = callback;
+       entry->priv = priv;
+       list_add(&entry->list, &target_reset_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;
@@ -1369,6 +1417,25 @@ int target_unregister_event_callback(int (*callback)(struct target *target,
        return ERROR_OK;
 }
 
+int target_unregister_reset_callback(int (*callback)(struct target *target,
+               enum target_reset_mode reset_mode, void *priv), void *priv)
+{
+       struct target_reset_callback *entry;
+
+       if (callback == NULL)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       list_for_each_entry(entry, &target_reset_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)
 {
        struct target_timer_callback **p = &target_timer_callbacks;
@@ -1415,6 +1482,19 @@ int target_call_event_callbacks(struct target *target, enum target_event event)
        return ERROR_OK;
 }
 
+int target_call_reset_callbacks(struct target *target, enum target_reset_mode reset_mode)
+{
+       struct target_reset_callback *callback;
+
+       LOG_DEBUG("target reset %i (%s)", reset_mode,
+                       Jim_Nvp_value2name_simple(nvp_reset_modes, reset_mode)->name);
+
+       list_for_each_entry(callback, &target_reset_callback_list, list)
+               callback->callback(target, reset_mode, callback->priv);
+
+       return ERROR_OK;
+}
+
 static int target_timer_callback_periodic_restart(
                struct target_timer_callback *cb, struct timeval *now)
 {
index 0552b8f9b2c51cb66f68282c9531db5b1177602f..d46571e7b7f96193991766166a11e469fc461cb2 100644 (file)
@@ -33,6 +33,8 @@
 #ifndef TARGET_H
 #define TARGET_H
 
+#include <helper/list.h>
+
 struct reg;
 struct trace;
 struct command_context;
@@ -282,6 +284,12 @@ struct target_event_callback {
        struct target_event_callback *next;
 };
 
+struct target_reset_callback {
+       struct list_head list;
+       void *priv;
+       int (*callback)(struct target *target, enum target_reset_mode reset_mode, void *priv);
+};
+
 struct target_timer_callback {
        int (*callback)(void *priv);
        int time_ms;
@@ -303,6 +311,15 @@ int target_unregister_event_callback(
                enum target_event event, void *priv),
                void *priv);
 
+int target_register_reset_callback(
+               int (*callback)(struct target *target,
+               enum target_reset_mode reset_mode, void *priv),
+               void *priv);
+int target_unregister_reset_callback(
+               int (*callback)(struct target *target,
+               enum target_reset_mode reset_mode, 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
@@ -320,6 +337,7 @@ int target_resume(struct target *target, int current, uint32_t address,
                int handle_breakpoints, int debug_execution);
 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);
 
 /**
  * The period is very approximate, the callback can happen much more often
@@ -565,6 +583,12 @@ int target_gdb_fileio_end(struct target *target, int retcode, int fileio_errno,
 /** Return the *name* of this targets current state */
 const char *target_state_name(struct target *target);
 
+/** Return the *name* of a target event enumeration value */
+const char *target_event_name(enum target_event event);
+
+/** Return the *name* of a target reset reason enumeration value */
+const char *target_reset_mode_name(enum target_reset_mode reset_mode);
+
 /* DANGER!!!!!
  *
  * if "area" passed in to target_alloc_working_area() points to a memory

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)