/*******************************************************************************
* Copyright (c) 2007, 2013 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
/*
* TCF Memory - memory access service.
*/
#include <tcf/config.h>
#if SERVICE_Memory
#include <assert.h>
#include <tcf/framework/protocol.h>
#include <tcf/framework/context.h>
#include <tcf/framework/json.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/channel.h>
#include <tcf/framework/trace.h>
#include <tcf/framework/cache.h>
#include <tcf/services/memoryservice.h>
#include <tcf/services/runctrl.h>
static const char * MEMORY = "Memory";
static TCFBroadcastGroup * broadcast_group = NULL;
#define BYTE_VALID 0x00
#define BYTE_UNKNOWN 0x01
#define BYTE_INVALID 0x02
#define BYTE_CANNOT_READ 0x04
#define BYTE_CANNOT_WRITE 0x08
#define CMD_GET 1
#define CMD_SET 2
#define CMD_FILL 3
#define BUF_SIZE (128 * MEM_USAGE_FACTOR)
typedef struct MemoryCommandArgs {
char token[256];
char ctx_id[256];
MemoryAccessMode mode;
ContextAddress addr;
unsigned long size;
JsonReadBinaryState state;
char * buf;
size_t pos;
size_t max;
} MemoryCommandArgs;
static void write_context(OutputStream * out, Context * ctx) {
assert(!ctx->exited);
write_stream(out, '{');
json_write_string(out, "ID");
write_stream(out, ':');
json_write_string(out, ctx->id);
if (ctx->parent != NULL) {
write_stream(out, ',');
json_write_string(out, "ParentID");
write_stream(out, ':');
json_write_string(out, ctx->parent->id);
}
write_stream(out, ',');
json_write_string(out, "ProcessID");
write_stream(out, ':');
json_write_string(out, context_get_group(ctx, CONTEXT_GROUP_PROCESS)->id);
if (ctx->name != NULL) {
write_stream(out, ',');
json_write_string(out, "Name");
write_stream(out, ':');
json_write_string(out, ctx->name);
}
write_stream(out, ',');
json_write_string(out, "BigEndian");
write_stream(out, ':');
json_write_boolean(out, ctx->big_endian);
if (ctx->mem_access) {
int cnt = 0;
write_stream(out, ',');
json_write_string(out, "AddressSize");
write_stream(out, ':');
json_write_ulong(out, context_word_size(ctx));
write_stream(out, ',');
json_write_string(out, "AccessTypes");
write_stream(out, ':');
write_stream(out, '[');
if (ctx->mem_access & MEM_ACCESS_INSTRUCTION) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "instruction");
}
if (ctx->mem_access & MEM_ACCESS_DATA) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "data");
}
if (ctx->mem_access & MEM_ACCESS_IO) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "io");
}
if (ctx->mem_access & MEM_ACCESS_USER) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "user");
}
if (ctx->mem_access & MEM_ACCESS_SUPERVISOR) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "supervisor");
}
if (ctx->mem_access & MEM_ACCESS_HYPERVISOR) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "hypervisor");
}
if (ctx->mem_access & MEM_ACCESS_VIRTUAL) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "virtual");
}
if (ctx->mem_access & MEM_ACCESS_PHYSICAL) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "physical");
}
if (ctx->mem_access & MEM_ACCESS_CACHE) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "cache");
}
if (ctx->mem_access & MEM_ACCESS_TLB) {
if (cnt++) write_stream(out, ',');
json_write_string(out, "tlb");
}
write_stream(out, ']');
}
#if ENABLE_ContextMemoryProperties
{
/* Back-end context properties */
int cnt = 0;
const char ** names = NULL;
const char ** values = NULL;
if (context_get_memory_properties(ctx, &names, &values, &cnt) == 0) {
while (cnt > 0) {
if (*values != NULL) {
write_stream(out, ',');
json_write_string(out, *names);
write_stream(out, ':');
write_string(out, *values);
}
names++;
values++;
cnt--;
}
}
}
#endif
write_stream(out, '}');
}
static void write_ranges(OutputStream * out, ContextAddress addr, int offs, int status, MemoryErrorInfo * err_info) {
int cnt = 0;
size_t size_valid = 0;
size_t size_error = 0;
if (err_info->error) {
size_valid = err_info->size_valid + offs;
size_error = err_info->size_error;
}
else {
size_valid = offs;
}
write_stream(out, '[');
if (size_valid > 0) {
write_stream(out, '{');
json_write_string(out, "addr");
write_stream(out, ':');
json_write_uint64(out, addr);
write_stream(out, ',');
json_write_string(out, "size");
write_stream(out, ':');
json_write_ulong(out, size_valid);
write_stream(out, ',');
json_write_string(out, "stat");
write_stream(out, ':');
json_write_ulong(out, 0);
write_stream(out, '}');
cnt++;
}
if (size_error > 0) {
if (cnt > 0) write_stream(out, ',');
write_stream(out, '{');
json_write_string(out, "addr");
write_stream(out, ':');
json_write_uint64(out, addr + size_valid);
write_stream(out, ',');
json_write_string(out, "size");
write_stream(out, ':');
json_write_ulong(out, size_error);
write_stream(out, ',');
json_write_string(out, "stat");
write_stream(out, ':');
json_write_ulong(out, BYTE_INVALID | status);
write_stream(out, ',');
json_write_string(out, "msg");
write_stream(out, ':');
write_error_object(out, err_info->error);
write_stream(out, '}');
}
write_stream(out, ']');
write_stream(out, 0);
}
static void command_get_context(char * token, Channel * c) {
int err = 0;
char id[256];
Context * ctx = NULL;
json_read_string(&c->inp, id, sizeof(id));
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
ctx = id2ctx(id);
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
else if (ctx->mem_access == 0) err = ERR_INV_CONTEXT;
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, err);
if (err == 0) {
write_context(&c->out, ctx);
}
else {
write_string(&c->out, "null");
}
write_stream(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void command_get_children(char * token, Channel * c) {
char id[256];
json_read_string(&c->inp, id, sizeof(id));
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, 0);
write_stream(&c->out, '[');
if (id[0] == 0) {
LINK * qp;
int cnt = 0;
for (qp = context_root.next; qp != &context_root; qp = qp->next) {
Context * ctx = ctxl2ctxp(qp);
if (ctx->parent != NULL) continue;
if (ctx->exited) continue;
if (ctx->mem_access == 0 && list_is_empty(&ctx->children)) continue;
if (cnt > 0) write_stream(&c->out, ',');
json_write_string(&c->out, ctx->id);
cnt++;
}
}
else {
Context * parent = id2ctx(id);
if (parent != NULL) {
LINK * l;
int cnt = 0;
for (l = parent->children.next; l != &parent->children; l = l->next) {
Context * ctx = cldl2ctxp(l);
assert(ctx->parent == parent);
if (ctx->exited) continue;
if (ctx->mem_access == 0 && list_is_empty(&ctx->children)) continue;
if (cnt > 0) write_stream(&c->out, ',');
json_write_string(&c->out, ctx->id);
cnt++;
}
}
}
write_stream(&c->out, ']');
write_stream(&c->out, 0);
write_stream(&c->out, MARKER_EOM);
}
static void read_memory_fill_array_cb(InputStream * inp, void * args) {
MemoryCommandArgs * buf = (MemoryCommandArgs *)args;
if (buf->pos >= buf->max) {
buf->max = buf->max == 0 ? BUF_SIZE : buf->max * 2;
buf->buf = (char *)loc_realloc(buf->buf, buf->max);
}
buf->buf[buf->pos++] = (char)json_read_ulong(inp);
}
static MemoryCommandArgs * read_command_args(char * token, Channel * c, int cmd) {
int mode = 0;
static MemoryCommandArgs buf;
memset(&buf, 0, sizeof(buf));
json_read_string(&c->inp, buf.ctx_id, sizeof(buf.ctx_id));
json_test_char(&c->inp, MARKER_EOA);
buf.addr = (ContextAddress)json_read_uint64(&c->inp);
json_test_char(&c->inp, MARKER_EOA);
buf.mode.word_size = (int)json_read_long(&c->inp);
json_test_char(&c->inp, MARKER_EOA);
buf.size = (int)json_read_long(&c->inp);
json_test_char(&c->inp, MARKER_EOA);
mode = (int)json_read_long(&c->inp);
json_test_char(&c->inp, MARKER_EOA);
if (mode & 0x01) buf.mode.continue_on_error = 1;
if (mode & 0x02) buf.mode.verify = 1;
if (mode & 0x04) buf.mode.bypass_addr_check = 1;
switch (cmd) {
case CMD_SET:
json_read_binary_start(&buf.state, &c->inp);
buf.buf = (char *)loc_alloc(buf.max = BUF_SIZE);
buf.pos = json_read_binary_data(&buf.state, buf.buf, buf.max);
break;
case CMD_GET:
buf.buf = (char *)loc_alloc(buf.max = buf.size > BUF_SIZE ? BUF_SIZE : buf.size);
json_test_char(&c->inp, MARKER_EOM);
break;
case CMD_FILL:
json_read_array(&c->inp, read_memory_fill_array_cb, &buf);
json_test_char(&c->inp, MARKER_EOA);
json_test_char(&c->inp, MARKER_EOM);
break;
}
strlcpy(buf.token, token, sizeof(buf.token));
return &buf;
}
void send_event_memory_changed(Context * ctx, ContextAddress addr, unsigned long size) {
OutputStream * out = &broadcast_group->out;
if (!context_has_state(ctx) || is_intercepted(ctx)) {
write_stringz(out, "E");
write_stringz(out, MEMORY);
write_stringz(out, "memoryChanged");
json_write_string(out, ctx->id);
write_stream(out, 0);
/* <array of addres ranges> */
write_stream(out, '[');
write_stream(out, '{');
json_write_string(out, "addr");
write_stream(out, ':');
json_write_uint64(out, addr);
write_stream(out, ',');
json_write_string(out, "size");
write_stream(out, ':');
json_write_ulong(out, size);
write_stream(out, '}');
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
}
static void memory_set_cache_client(void * parm) {
MemoryCommandArgs * args = (MemoryCommandArgs *)parm;
Channel * c = cache_channel();
Context * ctx = NULL;
ContextAddress addr0 = args->addr;
ContextAddress addr = args->addr;
unsigned long size = 0;
MemoryErrorInfo err_info;
int err = 0;
memset(&err_info, 0, sizeof(err_info));
ctx = id2ctx(args->ctx_id);
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
/* First write needs to be done before cache_exit() */
if (err == 0) {
check_all_stopped(ctx);
#if ENABLE_MemoryAccessModes
if (context_write_mem_ext(ctx, &args->mode, addr, args->buf, args->pos) < 0) {
#else
if (context_write_mem(ctx, addr, args->buf, args->pos) < 0) {
#endif
err = errno;
#if ENABLE_ExtendedMemoryErrorReports
context_get_mem_error_info(&err_info);
#endif
}
else {
addr += args->pos;
}
}
size += args->pos;
cache_exit();
if (!is_channel_closed(c)) {
InputStream * inp = &c->inp;
OutputStream * out = &c->out;
char * token = args->token;
for (;;) {
size_t rd = json_read_binary_data(&args->state, args->buf, args->max);
if (rd == 0) break;
if (err == 0) {
#if ENABLE_MemoryAccessModes
if (context_write_mem_ext(ctx, &args->mode, addr, args->buf, rd) < 0) {
#else
if (context_write_mem(ctx, addr, args->buf, rd) < 0) {
#endif
err = errno;
#if ENABLE_ExtendedMemoryErrorReports
context_get_mem_error_info(&err_info);
#endif
}
else {
addr += rd;
}
}
size += rd;
}
json_read_binary_end(&args->state);
json_test_char(inp, MARKER_EOA);
json_test_char(inp, MARKER_EOM);
send_event_memory_changed(ctx, addr0, size);
write_stringz(out, "R");
write_stringz(out, token);
write_errno(out, err);
if (err == 0) {
write_stringz(out, "null");
}
else {
write_ranges(out, addr0, (int)(addr - addr0), BYTE_CANNOT_WRITE, &err_info);
}
write_stream(out, MARKER_EOM);
}
loc_free(args->buf);
}
static void command_set(char * token, Channel * c) {
MemoryCommandArgs * args = read_command_args(token, c, CMD_SET);
cache_enter(memory_set_cache_client, c, args, sizeof(MemoryCommandArgs));
}
static void memory_get_cache_client(void * parm) {
MemoryCommandArgs * args = (MemoryCommandArgs *)parm;
Context * ctx = NULL;
Channel * c = cache_channel();
ContextAddress addr0 = args->addr;
ContextAddress addr = args->addr;
unsigned long size = args->size;
unsigned long pos = 0;
int err = 0;
MemoryErrorInfo err_info;
JsonWriteBinaryState state;
memset(&err_info, 0, sizeof(err_info));
ctx = id2ctx(args->ctx_id);
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
/* First read needs to be done before cache_exit() */
if (size > 0) {
size_t rd = size;
if (rd > args->max) rd = args->max;
memset(args->buf, 0, rd);
if (err == 0) {
check_all_stopped(ctx);
#if ENABLE_MemoryAccessModes
if (context_read_mem_ext(ctx, &args->mode, addr, args->buf, rd) < 0) {
#else
if (context_read_mem(ctx, addr, args->buf, rd) < 0) {
#endif
err = errno;
#if ENABLE_ExtendedMemoryErrorReports
context_get_mem_error_info(&err_info);
#endif
}
else {
addr += rd;
}
}
pos += rd;
}
cache_exit();
if (!is_channel_closed(c)) {
OutputStream * out = &c->out;
write_stringz(out, "R");
write_stringz(out, args->token);
json_write_binary_start(&state, out, size);
json_write_binary_data(&state, args->buf, pos);
while (pos < size) {
size_t rd = size - pos;
if (rd > args->max) rd = args->max;
/* TODO: word size, mode */
memset(args->buf, 0, rd);
if (err == 0) {
#if ENABLE_MemoryAccessModes
if (context_read_mem_ext(ctx, &args->mode, addr, args->buf, rd) < 0) {
#else
if (context_read_mem(ctx, addr, args->buf, rd) < 0) {
#endif
err = errno;
#if ENABLE_ExtendedMemoryErrorReports
context_get_mem_error_info(&err_info);
#endif
}
else {
addr += rd;
}
}
json_write_binary_data(&state, args->buf, rd);
pos += rd;
}
json_write_binary_end(&state);
write_stream(out, 0);
write_errno(out, err);
if (err == 0) {
write_stringz(out, "null");
}
else {
write_ranges(out, addr0, (int)(addr - addr0), BYTE_CANNOT_READ, &err_info);
}
write_stream(out, MARKER_EOM);
}
loc_free(args->buf);
}
static void command_get(char * token, Channel * c) {
MemoryCommandArgs * args = read_command_args(token, c, CMD_GET);
cache_enter(memory_get_cache_client, c, args, sizeof(MemoryCommandArgs));
}
static void memory_fill_cache_client(void * parm) {
MemoryCommandArgs * args = (MemoryCommandArgs *)parm;
Channel * c = cache_channel();
Context * ctx = NULL;
char * tmp = (char *)tmp_alloc(args->pos);
ContextAddress addr0 = args->addr;
ContextAddress addr = args->addr;
unsigned long size = args->size;
MemoryErrorInfo err_info;
int err = 0;
memset(&err_info, 0, sizeof(err_info));
ctx = id2ctx(args->ctx_id);
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
if (err == 0) check_all_stopped(ctx);
while (err == 0 && addr < addr0 + size) {
/* Note: context_write_mem() modifies buffer contents */
unsigned wr = (unsigned)(addr0 + size - addr);
if (wr > args->pos) wr = args->pos;
memcpy(tmp, args->buf, wr);
#if ENABLE_MemoryAccessModes
if (context_write_mem_ext(ctx, &args->mode, addr, tmp, wr) < 0) {
#else
if (context_write_mem(ctx, addr, tmp, wr) < 0) {
#endif
err = errno;
#if ENABLE_ExtendedMemoryErrorReports
context_get_mem_error_info(&err_info);
#endif
}
else {
addr += wr;
}
}
cache_exit();
send_event_memory_changed(ctx, addr0, size);
if (!is_channel_closed(c)) {
OutputStream * out = &c->out;
char * token = args->token;
write_stringz(out, "R");
write_stringz(out, token);
write_errno(out, err);
if (err == 0) {
write_stringz(out, "null");
}
else {
write_ranges(out, addr0, (int)(addr - addr0), BYTE_CANNOT_WRITE, &err_info);
}
write_stream(out, MARKER_EOM);
}
loc_free(args->buf);
}
static void command_fill(char * token, Channel * c) {
MemoryCommandArgs * args = read_command_args(token, c, CMD_FILL);
/* Grow buffer for faster execution */
while (args->pos < args->size && args->pos <= args->max / 2) {
if (args->pos == 0) {
args->buf[args->pos++] = 0;
}
else {
memcpy(args->buf + args->pos, args->buf, args->pos);
args->pos *= 2;
}
}
cache_enter(memory_fill_cache_client, c, args, sizeof(MemoryCommandArgs));
}
static void send_event_context_added(Context * ctx) {
OutputStream * out = &broadcast_group->out;
write_stringz(out, "E");
write_stringz(out, MEMORY);
write_stringz(out, "contextAdded");
/* <array of context data> */
write_stream(out, '[');
write_context(out, ctx);
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
static void send_event_context_changed(Context * ctx) {
OutputStream * out = &broadcast_group->out;
write_stringz(out, "E");
write_stringz(out, MEMORY);
write_stringz(out, "contextChanged");
/* <array of context data> */
write_stream(out, '[');
write_context(out, ctx);
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
static void send_event_context_removed(Context * ctx) {
OutputStream * out = &broadcast_group->out;
write_stringz(out, "E");
write_stringz(out, MEMORY);
write_stringz(out, "contextRemoved");
/* <array of context IDs> */
write_stream(out, '[');
json_write_string(out, ctx->id);
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
static void event_context_created(Context * ctx, void * args) {
if (ctx->mem_access == 0) return;
send_event_context_added(ctx);
}
static void event_context_changed(Context * ctx, void * args) {
if (ctx->mem_access == 0) return;
send_event_context_changed(ctx);
}
static void event_context_exited(Context * ctx, void * args) {
if (ctx->mem_access == 0) return;
send_event_context_removed(ctx);
}
void ini_memory_service(Protocol * proto, TCFBroadcastGroup * bcg) {
static ContextEventListener listener = {
event_context_created,
event_context_exited,
NULL,
NULL,
event_context_changed
};
broadcast_group = bcg;
add_context_event_listener(&listener, NULL);
add_command_handler(proto, MEMORY, "getContext", command_get_context);
add_command_handler(proto, MEMORY, "getChildren", command_get_children);
add_command_handler(proto, MEMORY, "set", command_set);
add_command_handler(proto, MEMORY, "get", command_get);
add_command_handler(proto, MEMORY, "fill", command_fill);
}
#endif /* SERVICE_Memory */