server: tcl_notifications command

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>
This commit is contained in:
Austin Morton 2015-02-21 04:20:33 -05:00 committed by Paul Fertser
parent c50047bb41
commit 1d0cf0df37
4 changed files with 212 additions and 4 deletions

View 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

View 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
};

View 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)
{

View 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