Power history support.
This adds a "power" command which can be used to dump power history samples gathered by the device driver.
This commit is contained in:
parent
1973746d33
commit
cc5aed4c6d
2
Makefile
2
Makefile
|
@ -114,6 +114,7 @@ OBJ=\
|
||||||
util/gdb_proto.o \
|
util/gdb_proto.o \
|
||||||
util/dynload.o \
|
util/dynload.o \
|
||||||
util/demangle.o \
|
util/demangle.o \
|
||||||
|
util/powerbuf.o \
|
||||||
transport/cp210x.o \
|
transport/cp210x.o \
|
||||||
transport/cdc_acm.o \
|
transport/cdc_acm.o \
|
||||||
transport/ftdi.o \
|
transport/ftdi.o \
|
||||||
|
@ -157,6 +158,7 @@ OBJ=\
|
||||||
ui/cmddb.o \
|
ui/cmddb.o \
|
||||||
ui/stdcmd.o \
|
ui/stdcmd.o \
|
||||||
ui/aliasdb.o \
|
ui/aliasdb.o \
|
||||||
|
ui/power.o \
|
||||||
ui/main.o
|
ui/main.o
|
||||||
|
|
||||||
$(BINARY): $(OBJ)
|
$(BINARY): $(OBJ)
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "powerbuf.h"
|
||||||
|
|
||||||
struct device;
|
struct device;
|
||||||
typedef struct device *device_t;
|
typedef struct device *device_t;
|
||||||
|
@ -122,6 +123,11 @@ struct device {
|
||||||
*/
|
*/
|
||||||
int max_breakpoints;
|
int max_breakpoints;
|
||||||
struct device_breakpoint breakpoints[DEVICE_MAX_BREAKPOINTS];
|
struct device_breakpoint breakpoints[DEVICE_MAX_BREAKPOINTS];
|
||||||
|
|
||||||
|
/* Power sample buffer, if power profiling is supported by this
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
powerbuf_t power_buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Probe the device memory and extract ID bytes. This should be called
|
/* Probe the device memory and extract ID bytes. This should be called
|
||||||
|
|
|
@ -46,6 +46,8 @@ struct fet_device {
|
||||||
int version;
|
int version;
|
||||||
int fet_flags;
|
int fet_flags;
|
||||||
|
|
||||||
|
int poll_enable;
|
||||||
|
|
||||||
struct fet_proto proto;
|
struct fet_proto proto;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,6 +99,10 @@ struct fet_device {
|
||||||
#define C_IDENT2 0x29
|
#define C_IDENT2 0x29
|
||||||
#define C_IDENT3 0x2b
|
#define C_IDENT3 0x2b
|
||||||
|
|
||||||
|
#define C_CMM_PARAM 0x36
|
||||||
|
#define C_CMM_CTRL 0x37
|
||||||
|
#define C_CMM_READ 0x38
|
||||||
|
|
||||||
/* Constants for parameters of various FET commands */
|
/* Constants for parameters of various FET commands */
|
||||||
#define FET_CONFIG_VERIFICATION 0
|
#define FET_CONFIG_VERIFICATION 0
|
||||||
#define FET_CONFIG_EMULATION 1
|
#define FET_CONFIG_EMULATION 1
|
||||||
|
@ -380,6 +386,105 @@ static int do_identify(struct fet_device *dev, const char *force_id)
|
||||||
return identify_new(dev, force_id);
|
return identify_new(dev, force_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void power_init(struct fet_device *dev)
|
||||||
|
{
|
||||||
|
if (fet_proto_xfer(&dev->proto, C_CMM_PARAM, NULL, 0, 0) < 0) {
|
||||||
|
printc_err("warning: device does not support power "
|
||||||
|
"profiling\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->proto.argv[0] <= 0 || dev->proto.argv[0] <= 0) {
|
||||||
|
printc_err("Bad parameters returned by C_CMM_PARAM: "
|
||||||
|
"bufsize = %d bytes, %d us/sample\n",
|
||||||
|
dev->proto.argv[1], dev->proto.argv[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printc("Power profiling enabled: bufsize = %d bytes, %d us/sample\n",
|
||||||
|
dev->proto.argv[1], dev->proto.argv[0]);
|
||||||
|
|
||||||
|
dev->base.power_buf = powerbuf_new(POWERBUF_DEFAULT_SAMPLES,
|
||||||
|
dev->proto.argv[0]);
|
||||||
|
if (!dev->base.power_buf) {
|
||||||
|
printc_err("Failed to allocate memory for power profile\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int power_start(struct fet_device *dev)
|
||||||
|
{
|
||||||
|
if (!dev->base.power_buf)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fet_proto_xfer(&dev->proto, C_CMM_CTRL, NULL, 0, 1, 1) < 0) {
|
||||||
|
printc_err("fet: failed to start power profiling, "
|
||||||
|
"disabling\n");
|
||||||
|
powerbuf_free(dev->base.power_buf);
|
||||||
|
dev->base.power_buf = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
powerbuf_begin_session(dev->base.power_buf, time(NULL));
|
||||||
|
dev->poll_enable = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int power_end(struct fet_device *dev)
|
||||||
|
{
|
||||||
|
if (!dev->base.power_buf)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
powerbuf_end_session(dev->base.power_buf);
|
||||||
|
dev->poll_enable = 0;
|
||||||
|
|
||||||
|
if (fet_proto_xfer(&dev->proto, C_CMM_CTRL, NULL, 0, 1, 1) < 0) {
|
||||||
|
printc_err("fet: failed to end power profiling\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int power_poll(struct fet_device *dev)
|
||||||
|
{
|
||||||
|
address_t mab;
|
||||||
|
address_t mab_samples[1024];
|
||||||
|
unsigned int cur_samples[1024];
|
||||||
|
unsigned int count = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!dev->base.power_buf || !dev->poll_enable)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fet_proto_xfer(&dev->proto, C_CMM_READ, NULL, 0, 0) < 0) {
|
||||||
|
printc_err("fet: failed to fetch power data, disabling\n");
|
||||||
|
power_end(dev);
|
||||||
|
powerbuf_free(dev->base.power_buf);
|
||||||
|
dev->base.power_buf = NULL;
|
||||||
|
dev->poll_enable = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mab = powerbuf_last_mab(dev->base.power_buf);
|
||||||
|
for (i = 0; i + 3 < dev->proto.datalen; i += 4) {
|
||||||
|
uint32_t s = LE_LONG(dev->proto.data, i);
|
||||||
|
|
||||||
|
if (s & 0x80000000) {
|
||||||
|
mab = s & 0x7fffffff;
|
||||||
|
} else if (count + 1 < ARRAY_LEN(cur_samples)) {
|
||||||
|
cur_samples[count] = s;
|
||||||
|
mab_samples[count] = mab;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
powerbuf_add_samples(dev->base.power_buf, count,
|
||||||
|
cur_samples, mab_samples);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int do_run(struct fet_device *dev, int type)
|
static int do_run(struct fet_device *dev, int type)
|
||||||
{
|
{
|
||||||
if (fet_proto_xfer(&dev->proto, C_RUN, NULL, 0, 2, type, 0) < 0) {
|
if (fet_proto_xfer(&dev->proto, C_RUN, NULL, 0, 2, type, 0) < 0) {
|
||||||
|
@ -443,16 +548,25 @@ device_status_t fet_poll(device_t dev_base)
|
||||||
struct fet_device *dev = (struct fet_device *)dev_base;
|
struct fet_device *dev = (struct fet_device *)dev_base;
|
||||||
|
|
||||||
ctrlc_reset();
|
ctrlc_reset();
|
||||||
if ((delay_ms(50) < 0) || ctrlc_check())
|
|
||||||
return DEVICE_STATUS_INTR;
|
|
||||||
|
|
||||||
if (fet_proto_xfer(&dev->proto, C_STATE, NULL, 0, 1, 0) < 0) {
|
if (fet_proto_xfer(&dev->proto, C_STATE, NULL, 0, 1, 0) < 0) {
|
||||||
printc_err("fet: polling failed\n");
|
printc_err("fet: polling failed\n");
|
||||||
|
power_end(dev);
|
||||||
return DEVICE_STATUS_ERROR;
|
return DEVICE_STATUS_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(dev->proto.argv[0] & FET_POLL_RUNNING))
|
if (dev->base.power_buf)
|
||||||
|
power_poll(dev);
|
||||||
|
else
|
||||||
|
delay_ms(50);
|
||||||
|
|
||||||
|
if (!(dev->proto.argv[0] & FET_POLL_RUNNING)) {
|
||||||
|
power_end(dev);
|
||||||
return DEVICE_STATUS_HALTED;
|
return DEVICE_STATUS_HALTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrlc_check())
|
||||||
|
return DEVICE_STATUS_INTR;
|
||||||
|
|
||||||
return DEVICE_STATUS_RUNNING;
|
return DEVICE_STATUS_RUNNING;
|
||||||
}
|
}
|
||||||
|
@ -503,9 +617,17 @@ int fet_ctl(device_t dev_base, device_ctl_t action)
|
||||||
if (refresh_bps(dev) < 0)
|
if (refresh_bps(dev) < 0)
|
||||||
printc_err("warning: fet: failed to refresh "
|
printc_err("warning: fet: failed to refresh "
|
||||||
"breakpoints\n");
|
"breakpoints\n");
|
||||||
return do_run(dev, FET_RUN_BREAKPOINT);
|
|
||||||
|
power_start(dev);
|
||||||
|
if (do_run(dev, FET_RUN_BREAKPOINT) < 0) {
|
||||||
|
power_end(dev);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
case DEVICE_CTL_HALT:
|
case DEVICE_CTL_HALT:
|
||||||
|
power_end(dev);
|
||||||
if (fet_proto_xfer(&dev->proto, C_STATE, NULL, 0, 1, 1) < 0) {
|
if (fet_proto_xfer(&dev->proto, C_STATE, NULL, 0, 1, 1) < 0) {
|
||||||
printc_err("fet: failed to halt CPU\n");
|
printc_err("fet: failed to halt CPU\n");
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -550,6 +672,9 @@ void fet_destroy(device_t dev_base)
|
||||||
if (fet_proto_xfer(&dev->proto, C_CLOSE, NULL, 0, 1, 0) < 0)
|
if (fet_proto_xfer(&dev->proto, C_CLOSE, NULL, 0, 1, 0) < 0)
|
||||||
printc_err("fet: close command failed\n");
|
printc_err("fet: close command failed\n");
|
||||||
|
|
||||||
|
if (dev->base.power_buf)
|
||||||
|
powerbuf_free(dev->base.power_buf);
|
||||||
|
|
||||||
dev->proto.transport->ops->destroy(dev->proto.transport);
|
dev->proto.transport->ops->destroy(dev->proto.transport);
|
||||||
free(dev);
|
free(dev);
|
||||||
}
|
}
|
||||||
|
@ -853,6 +978,9 @@ device_t fet_open(const struct device_args *args,
|
||||||
for (i = 0; i < dev->base.max_breakpoints; i++)
|
for (i = 0; i < dev->base.max_breakpoints; i++)
|
||||||
dev->base.breakpoints[i].flags = DEVICE_BP_DIRTY;
|
dev->base.breakpoints[i].flags = DEVICE_BP_DIRTY;
|
||||||
|
|
||||||
|
/* Initialize power profiling */
|
||||||
|
power_init(dev);
|
||||||
|
|
||||||
return (device_t)dev;
|
return (device_t)dev;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
|
14
ui/cmddb.c
14
ui/cmddb.c
|
@ -29,6 +29,7 @@
|
||||||
#include "stdcmd.h"
|
#include "stdcmd.h"
|
||||||
#include "simio.h"
|
#include "simio.h"
|
||||||
#include "aliasdb.h"
|
#include "aliasdb.h"
|
||||||
|
#include "power.h"
|
||||||
|
|
||||||
const struct cmddb_record commands[] = {
|
const struct cmddb_record commands[] = {
|
||||||
{
|
{
|
||||||
|
@ -332,6 +333,19 @@ const struct cmddb_record commands[] = {
|
||||||
.help =
|
.help =
|
||||||
"fill <address> <length> <b0> [b1 b2 ...]\n"
|
"fill <address> <length> <b0> [b1 b2 ...]\n"
|
||||||
" Fill the given memory range with a repeated byte sequence.\n"
|
" Fill the given memory range with a repeated byte sequence.\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "power",
|
||||||
|
.func = cmd_power,
|
||||||
|
.help =
|
||||||
|
"power info\n"
|
||||||
|
" Show basic power statistics.\n"
|
||||||
|
"power clear\n"
|
||||||
|
" Clear power statistics.\n"
|
||||||
|
"power all [granularity]\n"
|
||||||
|
" Show all power data, optionally specifying a granularity in us.\n"
|
||||||
|
"power session <N> [granularity]\n"
|
||||||
|
" Show data only for the specified session.\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
/* MSPDebug - debugging tool MSP430 MCUs
|
||||||
|
* Copyright (C) 2009-2012 Daniel Beer
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "output.h"
|
||||||
|
#include "output_util.h"
|
||||||
|
#include "device.h"
|
||||||
|
#include "power.h"
|
||||||
|
#include "powerbuf.h"
|
||||||
|
|
||||||
|
static void print_header(powerbuf_t pb, unsigned int s)
|
||||||
|
{
|
||||||
|
unsigned int length;
|
||||||
|
const struct powerbuf_session *rec =
|
||||||
|
powerbuf_session_info(pb, s, &length);
|
||||||
|
|
||||||
|
printc("Session #%d: %s", s, ctime(&rec->wall_clock));
|
||||||
|
printc("%d samples (spanning %.03f ms)\n",
|
||||||
|
length, (double)(length * pb->interval_us) / 1000.0);
|
||||||
|
printc("%.01f uA average (%.01f uAs total charge)\n",
|
||||||
|
(double)rec->total_ua / (double)length,
|
||||||
|
(double)(rec->total_ua * pb->interval_us) / 1000000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump_session_data(powerbuf_t pb, unsigned int s,
|
||||||
|
unsigned int gran)
|
||||||
|
{
|
||||||
|
unsigned int length;
|
||||||
|
const struct powerbuf_session *rec =
|
||||||
|
powerbuf_session_info(pb, s, &length);
|
||||||
|
unsigned int i;
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
print_header(pb, s);
|
||||||
|
printc("\n");
|
||||||
|
|
||||||
|
printc("%15s %15s %-15s\n", "Time (us)", "Current (uA)", "MAB");
|
||||||
|
printc("------------------------------------------------\n");
|
||||||
|
|
||||||
|
idx = rec->start_index;
|
||||||
|
|
||||||
|
for (i = 0; i + gran <= length; i += gran) {
|
||||||
|
address_t mab = pb->mab[idx];
|
||||||
|
unsigned long ua_tot = 0;
|
||||||
|
char addr[128];
|
||||||
|
int j;
|
||||||
|
|
||||||
|
for (j = 0; j < gran; j++) {
|
||||||
|
ua_tot += pb->current_ua[idx];
|
||||||
|
idx = (idx + 1) % pb->max_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_address(mab, addr, sizeof(addr));
|
||||||
|
printc("%15d %15.01f %s\n", i * pb->interval_us,
|
||||||
|
((double)ua_tot) / (double)gran, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
printc("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_info(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
int sess_num = powerbuf_num_sessions(pb);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
printc("Sample granularity is %d us\n", pb->interval_us);
|
||||||
|
printc("%d sessions:\n", sess_num);
|
||||||
|
|
||||||
|
for (i = sess_num - 1; i >= 0; i--) {
|
||||||
|
printc("\n");
|
||||||
|
print_header(pb, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_clear(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
powerbuf_clear(pb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_granularity(powerbuf_t pb, char **arg, int *gran_out)
|
||||||
|
{
|
||||||
|
const char *text = get_arg(arg);
|
||||||
|
int request = 10000;
|
||||||
|
int gran;
|
||||||
|
|
||||||
|
if (text)
|
||||||
|
request = atoi(text);
|
||||||
|
|
||||||
|
if (request <= 0) {
|
||||||
|
printc_err("power: invalid granularity: %d us\n", request);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
gran = (request + (pb->interval_us / 2)) / pb->interval_us;
|
||||||
|
|
||||||
|
if (gran <= 0)
|
||||||
|
gran = 1;
|
||||||
|
|
||||||
|
*gran_out = gran;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_all(powerbuf_t pb, char **arg)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int gran;
|
||||||
|
|
||||||
|
if (parse_granularity(pb, arg, &gran) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for (i = powerbuf_num_sessions(pb) - 1; i >= 0; i--)
|
||||||
|
dump_session_data(pb, i, gran);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_session(powerbuf_t pb, char **arg)
|
||||||
|
{
|
||||||
|
const char *sess_text = get_arg(arg);
|
||||||
|
int sess;
|
||||||
|
int gran;
|
||||||
|
|
||||||
|
if (!sess_text) {
|
||||||
|
printc_err("power: you must specify a session number\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sess = atoi(sess_text);
|
||||||
|
if (sess < 0 || sess >= powerbuf_num_sessions(pb)) {
|
||||||
|
printc_err("power: invalid session: %d\n", sess);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parse_granularity(pb, arg, &gran) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
dump_session_data(pb, sess, gran);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_power(char **arg)
|
||||||
|
{
|
||||||
|
powerbuf_t pb = device_default->power_buf;
|
||||||
|
char *subcmd = get_arg(arg);
|
||||||
|
|
||||||
|
if (!pb) {
|
||||||
|
printc_err("power: power profiling is not supported "
|
||||||
|
"by this device.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subcmd) {
|
||||||
|
printc_err("power: need to specify a subcommand "
|
||||||
|
"(try \"help power\")\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcasecmp(subcmd, "info"))
|
||||||
|
return sc_info(pb);
|
||||||
|
if (!strcasecmp(subcmd, "clear"))
|
||||||
|
return sc_clear(pb);
|
||||||
|
if (!strcasecmp(subcmd, "all"))
|
||||||
|
return sc_all(pb, arg);
|
||||||
|
if (!strcasecmp(subcmd, "session"))
|
||||||
|
return sc_session(pb, arg);
|
||||||
|
|
||||||
|
printc_err("power: unknown subcommand: %s (try \"help power\")\n",
|
||||||
|
subcmd);
|
||||||
|
return -1;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* MSPDebug - debugging tool MSP430 MCUs
|
||||||
|
* Copyright (C) 2009-2012 Daniel Beer
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef POWER_H_
|
||||||
|
#define POWER_H_
|
||||||
|
|
||||||
|
int cmd_power(char **arg);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,279 @@
|
||||||
|
/* MSPDebug - debugging tool for MSP430 MCUs
|
||||||
|
* Copyright (C) 2009-2012 Daniel Beer
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "powerbuf.h"
|
||||||
|
|
||||||
|
powerbuf_t powerbuf_new(unsigned int max_samples, unsigned int interval_us)
|
||||||
|
{
|
||||||
|
powerbuf_t pb = malloc(sizeof(*pb));
|
||||||
|
|
||||||
|
if (!pb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (pb->max_samples <= 0) {
|
||||||
|
free(pb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(pb, 0, sizeof(*pb));
|
||||||
|
|
||||||
|
pb->current_ua = malloc(sizeof(pb->current_ua[0]) * max_samples);
|
||||||
|
if (!pb->current_ua) {
|
||||||
|
free(pb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pb->mab = malloc(sizeof(pb->mab[0]) * max_samples);
|
||||||
|
if (!pb->mab) {
|
||||||
|
free(pb->current_ua);
|
||||||
|
free(pb->mab);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pb->interval_us = interval_us;
|
||||||
|
pb->max_samples = max_samples;
|
||||||
|
|
||||||
|
pb->session_head = pb->session_tail = 0;
|
||||||
|
pb->current_head = pb->current_tail = 0;
|
||||||
|
|
||||||
|
return pb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerbuf_free(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
free(pb->current_ua);
|
||||||
|
free(pb->mab);
|
||||||
|
free(pb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerbuf_clear(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
pb->session_head = pb->session_tail = 0;
|
||||||
|
pb->current_head = pb->current_tail = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int session_length(powerbuf_t pb, unsigned int idx)
|
||||||
|
{
|
||||||
|
const unsigned int next_idx = (idx + 1) % POWERBUF_MAX_SESSIONS;
|
||||||
|
unsigned int end_index = pb->current_head;
|
||||||
|
|
||||||
|
/* If a session follows this one, the end index is the start
|
||||||
|
* index of the following session. Otherwise, it's the end index
|
||||||
|
* of the entire current/MAB buffer.
|
||||||
|
*/
|
||||||
|
if (next_idx != pb->session_head)
|
||||||
|
end_index = pb->sessions[next_idx].start_index;
|
||||||
|
|
||||||
|
/* Return (end-start) modulo max_samples */
|
||||||
|
return (end_index + pb->max_samples - pb->sessions[idx].start_index) %
|
||||||
|
pb->max_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pop_oldest_session(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
unsigned int length = session_length(pb, pb->session_tail);
|
||||||
|
|
||||||
|
/* Remove corresponding samples from the tail of the current/MAB
|
||||||
|
* buffers.
|
||||||
|
*/
|
||||||
|
pb->current_tail = (pb->current_tail + length) % pb->max_samples;
|
||||||
|
|
||||||
|
/* Remove the session from the session buffer. */
|
||||||
|
pb->session_tail = (pb->session_tail + 1) % POWERBUF_MAX_SESSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerbuf_begin_session(powerbuf_t pb, time_t when)
|
||||||
|
{
|
||||||
|
struct powerbuf_session *s;
|
||||||
|
unsigned int next_head;
|
||||||
|
|
||||||
|
/* If the most recent session is empty, remove it */
|
||||||
|
powerbuf_end_session(pb);
|
||||||
|
|
||||||
|
/* If the session buffer is full, drop the oldest */
|
||||||
|
next_head = (pb->session_head + 1) % POWERBUF_MAX_SESSIONS;
|
||||||
|
if (next_head == pb->session_tail)
|
||||||
|
pop_oldest_session(pb);
|
||||||
|
|
||||||
|
/* Copy data to the head */
|
||||||
|
s = &pb->sessions[pb->session_head];
|
||||||
|
s->wall_clock = when;
|
||||||
|
s->start_index = pb->current_head;
|
||||||
|
s->total_ua = 0;
|
||||||
|
|
||||||
|
/* Advance the head pointer */
|
||||||
|
pb->session_head = next_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the index of the nth most recent session */
|
||||||
|
static unsigned int rev_index(powerbuf_t pb, unsigned int n)
|
||||||
|
{
|
||||||
|
return (pb->session_head + POWERBUF_MAX_SESSIONS - 1 - n) %
|
||||||
|
POWERBUF_MAX_SESSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerbuf_end_session(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
/* (head-1) modulo MAX_SESSIONS */
|
||||||
|
const unsigned int last_idx = rev_index(pb, 0);
|
||||||
|
|
||||||
|
/* If there are no sessions, do nothing */
|
||||||
|
if (pb->session_head == pb->session_tail)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* If no samples were added since the session began, decrement
|
||||||
|
* the head pointer.
|
||||||
|
*/
|
||||||
|
if (pb->sessions[last_idx].start_index == pb->current_head)
|
||||||
|
pb->session_head = last_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int powerbuf_num_sessions(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
/* (head-tail) modulo MAX_SESSIONS */
|
||||||
|
return (pb->session_head + POWERBUF_MAX_SESSIONS - pb->session_tail) %
|
||||||
|
POWERBUF_MAX_SESSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct powerbuf_session *powerbuf_session_info(powerbuf_t pb,
|
||||||
|
unsigned int rev_idx, unsigned int *length)
|
||||||
|
{
|
||||||
|
/* (head-1-rev_idx) modulo MAX_SESSIONS */
|
||||||
|
const unsigned int idx_map = rev_index(pb, rev_idx);
|
||||||
|
|
||||||
|
if (length)
|
||||||
|
*length = session_length(pb, idx_map);
|
||||||
|
|
||||||
|
return &pb->sessions[idx_map];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensure_room(powerbuf_t pb, unsigned int required)
|
||||||
|
{
|
||||||
|
unsigned int room =
|
||||||
|
(pb->current_tail + pb->max_samples - pb->current_head - 1) %
|
||||||
|
pb->max_samples;
|
||||||
|
|
||||||
|
/* Drop old sessions if they're smaller than what we need to
|
||||||
|
* reclaim.
|
||||||
|
*/
|
||||||
|
while (room < required && powerbuf_num_sessions(pb) > 1) {
|
||||||
|
const unsigned int len = session_length(pb, pb->session_tail);
|
||||||
|
|
||||||
|
if (room + len > required)
|
||||||
|
break;
|
||||||
|
|
||||||
|
pop_oldest_session(pb);
|
||||||
|
room += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we still lack space, it must be because the oldest session
|
||||||
|
* is larger than what we still need to reclaim (we'll never be
|
||||||
|
* asked to reclaim more than the buffer can hold).
|
||||||
|
*
|
||||||
|
* We also know at this point that (required-room) is <= the
|
||||||
|
* length of the oldest session.
|
||||||
|
*/
|
||||||
|
while (room < required) {
|
||||||
|
struct powerbuf_session *old =
|
||||||
|
&pb->sessions[pb->session_tail];
|
||||||
|
unsigned int cont_len = pb->max_samples - old->start_index;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (cont_len + room > required)
|
||||||
|
cont_len = required - room;
|
||||||
|
|
||||||
|
/* Un-integrate current */
|
||||||
|
for (i = 0; i < cont_len; i++)
|
||||||
|
old->total_ua -=
|
||||||
|
pb->current_ua[old->start_index + i];
|
||||||
|
|
||||||
|
/* Advance the start index and buffer tail */
|
||||||
|
old->start_index = (old->start_index + cont_len) %
|
||||||
|
pb->max_samples;
|
||||||
|
pb->current_tail = (pb->current_tail + cont_len) %
|
||||||
|
pb->max_samples;
|
||||||
|
|
||||||
|
room += cont_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerbuf_add_samples(powerbuf_t pb, unsigned int count,
|
||||||
|
const unsigned int *current_ua,
|
||||||
|
const address_t *mab)
|
||||||
|
{
|
||||||
|
struct powerbuf_session *cur = &pb->sessions[rev_index(pb, 0)];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* If no session is active, do nothing */
|
||||||
|
if (pb->session_head == pb->session_tail)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Make sure that we can't overflow the buffer in a single
|
||||||
|
* chunk.
|
||||||
|
*/
|
||||||
|
if (count > pb->max_samples - 1) {
|
||||||
|
int extra = pb->max_samples - 1 - count;
|
||||||
|
|
||||||
|
current_ua += extra;
|
||||||
|
mab += extra;
|
||||||
|
count -= extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push old samples/sessions out of the buffer if we need to. */
|
||||||
|
ensure_room(pb, count);
|
||||||
|
|
||||||
|
/* Add current integral to the session's running count */
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
cur->total_ua += current_ua[i];
|
||||||
|
|
||||||
|
/* Add samples in contiguous chunks */
|
||||||
|
while (count) {
|
||||||
|
unsigned int cont_len = pb->max_samples - pb->current_head;
|
||||||
|
|
||||||
|
/* Don't copy more than we have */
|
||||||
|
if (cont_len > count)
|
||||||
|
cont_len = count;
|
||||||
|
|
||||||
|
/* Copy samples */
|
||||||
|
memcpy(pb->current_ua + pb->current_head, current_ua,
|
||||||
|
sizeof(pb->current_ua[0]) * cont_len);
|
||||||
|
memcpy(pb->mab + pb->current_head, mab,
|
||||||
|
sizeof(pb->mab[0]) * cont_len);
|
||||||
|
pb->current_head = (pb->current_head +
|
||||||
|
cont_len) % pb->max_samples;
|
||||||
|
|
||||||
|
/* Advance source pointers and count */
|
||||||
|
current_ua += cont_len;
|
||||||
|
mab += cont_len;
|
||||||
|
count -= cont_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
address_t powerbuf_last_mab(powerbuf_t pb)
|
||||||
|
{
|
||||||
|
const struct powerbuf_session *s = &pb->sessions[rev_index(pb, 0)];
|
||||||
|
const unsigned int last = (pb->current_head + pb->max_samples - 1) %
|
||||||
|
pb->max_samples;
|
||||||
|
|
||||||
|
if (s->start_index == pb->current_head)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return pb->mab[last];
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/* MSPDebug - debugging tool for MSP430 MCUs
|
||||||
|
* Copyright (C) 2009-2012 Daniel Beer
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef POWERBUF_H_
|
||||||
|
#define POWERBUF_H_
|
||||||
|
|
||||||
|
/* This header file describes a data structure for recording power
|
||||||
|
* profiling data.
|
||||||
|
*
|
||||||
|
* Power profile data consists of zero or more discontiguous "sessions".
|
||||||
|
* Within each session is a sequence of evenly spaced current samples
|
||||||
|
* and the corresponding MAB values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
/* Per-session information header. */
|
||||||
|
struct powerbuf_session {
|
||||||
|
/* Time that this session started. */
|
||||||
|
time_t wall_clock;
|
||||||
|
|
||||||
|
/* Index of first sample in sample buffer corresponding to this
|
||||||
|
* session.
|
||||||
|
*/
|
||||||
|
unsigned int start_index;
|
||||||
|
|
||||||
|
/* Integral of current consumed over this session. */
|
||||||
|
unsigned long long total_ua;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define POWERBUF_MAX_SESSIONS 8
|
||||||
|
#define POWERBUF_DEFAULT_SAMPLES 131072
|
||||||
|
|
||||||
|
/* Power buffer data structure. The power buffer contains three circular
|
||||||
|
* buffers, two of which are dynamically allocated. Helper functions are
|
||||||
|
* provided for managing access.
|
||||||
|
*/
|
||||||
|
struct powerbuf {
|
||||||
|
/* These parameters are set at the time of construction and
|
||||||
|
* shouldn't be modified.
|
||||||
|
*/
|
||||||
|
unsigned int interval_us;
|
||||||
|
unsigned int max_samples;
|
||||||
|
|
||||||
|
/* Session circular buffer. New sessions are created by
|
||||||
|
* overwriting the head and advancing it. Old sessions drop out
|
||||||
|
* the end of the buffer.
|
||||||
|
*/
|
||||||
|
struct powerbuf_session sessions[POWERBUF_MAX_SESSIONS];
|
||||||
|
unsigned int session_head;
|
||||||
|
unsigned int session_tail;
|
||||||
|
|
||||||
|
/* Sample circular buffer. A single head/tail pair indicates the
|
||||||
|
* extent of both the current and MAB samples, since they are
|
||||||
|
* synchronous with respect to one another.
|
||||||
|
*
|
||||||
|
* Adding samples to the buffer causes old samples to fall out
|
||||||
|
* the tail end. If enough samples are pushed, old sessions will
|
||||||
|
* also drop out above.
|
||||||
|
*/
|
||||||
|
unsigned int *current_ua;
|
||||||
|
address_t *mab;
|
||||||
|
unsigned int current_head;
|
||||||
|
unsigned int current_tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct powerbuf *powerbuf_t;
|
||||||
|
|
||||||
|
/* Allocate/destroy a power buffer. The number of samples is fixed and
|
||||||
|
* can't change over the lifetime of the buffer.
|
||||||
|
*/
|
||||||
|
powerbuf_t powerbuf_new(unsigned int max_samples, unsigned int interval_us);
|
||||||
|
void powerbuf_free(powerbuf_t pb);
|
||||||
|
|
||||||
|
/* Clear all sessions and samples from the buffer. */
|
||||||
|
void powerbuf_clear(powerbuf_t pb);
|
||||||
|
|
||||||
|
/* Begin a new session. This may cause an old session to drop out the
|
||||||
|
* end of the buffer. If the current session is empty, it will be
|
||||||
|
* overwritten.
|
||||||
|
*
|
||||||
|
* powerbuf_end_session() simply discards any empty session previously
|
||||||
|
* created by powerbuf_begin_session().
|
||||||
|
*/
|
||||||
|
void powerbuf_begin_session(powerbuf_t pb, time_t when);
|
||||||
|
void powerbuf_end_session(powerbuf_t pb);
|
||||||
|
|
||||||
|
/* This interface provides a convenient way of accessing the session
|
||||||
|
* circular buffer. Rather than using direct indices, we present an
|
||||||
|
* interface that mimics a flat array. Indices (rev_idx) start at 0,
|
||||||
|
* with 0 being the index of the most recent session.
|
||||||
|
*/
|
||||||
|
unsigned int powerbuf_num_sessions(powerbuf_t pb);
|
||||||
|
const struct powerbuf_session *powerbuf_session_info(powerbuf_t pb,
|
||||||
|
unsigned int rev_idx, unsigned int *length);
|
||||||
|
|
||||||
|
/* Push samples into the buffer. The number of elements in both the
|
||||||
|
* current_ua and mab arrays is given by the parameter "count".
|
||||||
|
*/
|
||||||
|
void powerbuf_add_samples(powerbuf_t pb, unsigned int count,
|
||||||
|
const unsigned int *current_ua,
|
||||||
|
const address_t *mab);
|
||||||
|
|
||||||
|
/* Retrieve the last known MAB for this session, or 0 if none exists. */
|
||||||
|
address_t powerbuf_last_mab(powerbuf_t pb);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue