497 lines
15 KiB
C
497 lines
15 KiB
C
/*
|
|
* Copyright (c) 2017, Intel Corporation
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the Intel Corporation nor the names of its
|
|
* contributors may be used to endorse or promote products derived from this
|
|
* software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL CORPORATION OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* SoC Watch - QMSI power profiler
|
|
*/
|
|
|
|
#if (SOC_WATCH_ENABLE)
|
|
|
|
/*
|
|
* Header files common to LMT and ARC Sensor.
|
|
*/
|
|
#include "soc_watch.h"
|
|
#include "qm_common.h"
|
|
#include "qm_soc_regs.h"
|
|
|
|
/*
|
|
* Header files and macro defines specific to LMT and ARC Sensor.
|
|
*/
|
|
#if (!QM_SENSOR)
|
|
#include <x86intrin.h>
|
|
/* 64bit Timestamp counter. */
|
|
#define get_ticks() _rdtsc()
|
|
#else
|
|
#include "qm_sensor_regs.h"
|
|
/* Timestamp counter for sensor subsystem is 32bit. */
|
|
#define get_ticks() __builtin_arc_lr(QM_SS_TSC_BASE + QM_SS_TIMER_COUNT)
|
|
#endif
|
|
/*
|
|
* Define a macro for exposing some functions and other definitions
|
|
* only when unit testing. If we're not unit testing, then declare
|
|
* them as static, so that their declarations are hidden to normal
|
|
* code.
|
|
*/
|
|
#if (UNIT_TEST)
|
|
#define NONUTSTATIC
|
|
#else
|
|
#define NONUTSTATIC static
|
|
#endif
|
|
|
|
/**
|
|
* "Event strings" table, describing message structures.
|
|
* The first character is the event code to write to the data file.
|
|
* The 2nd and subsequent characters describe how to format the record's
|
|
* data. Note that the ordering here needs to agree with the
|
|
* enumeration list in qmsw_stub.h.
|
|
*
|
|
* Table characters:
|
|
* + First char = event code to write into the result file.
|
|
* + T = TSC Timestamp (Hi-res timestamp)
|
|
* + t = RTC Timestamp (lo-res timestamp)
|
|
* + 1 = interpret ev_data as a 1-byte value
|
|
* + 4 = interpret ev_data as a 4-byte value
|
|
* + R = Using ev_data as a register enumeration, read that register,
|
|
* + and put that 4-byte value into the data file.
|
|
* + L = Trigger an RTC timestamp Later
|
|
*/
|
|
NONUTSTATIC const char *ev_strs[] = {
|
|
"HT", /* Halt event */
|
|
"IT1", /* Interrupt event */
|
|
"STtL", /* Sleep event */
|
|
"RT1R", /* Register read event: Timestamp, reg enum, reg value*/
|
|
"UTs4", /* User event: timestamp, subtype, data value. */
|
|
"FTf", /* Frequency change event */
|
|
};
|
|
|
|
/*
|
|
* This list of registers corresponds to the SoC Watch register ID
|
|
* enumeration in soc_watch.h, and MUST STAY IN AGREEMENT with that
|
|
* list, since that enumeration is used to index this list.
|
|
*
|
|
* To record a register value, the SoC Watch code indexes into this
|
|
* array, and reads the corresponding address found in that slot.
|
|
*/
|
|
#if (QUARK_D2000)
|
|
static const uint32_t *platform_regs[] = {
|
|
(uint32_t *)(&QM_SCSS_CCU->osc0_cfg1),
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_lp_clk_ctl),
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_sys_clk_ctl),
|
|
/* Clock Gating Registers */
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_periph_clk_gate_ctl),
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_ext_clock_ctl),
|
|
/* Power Consumption regs */
|
|
(uint32_t *)(&QM_SCSS_CMP->cmp_pwr),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_pullup),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_slew),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_in_en)};
|
|
#elif(QUARK_SE)
|
|
static const uint32_t *platform_regs[] = {
|
|
(uint32_t *)(&QM_SCSS_CCU->osc0_cfg1),
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_lp_clk_ctl),
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_sys_clk_ctl),
|
|
/* Clock Gating Registers */
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_periph_clk_gate_ctl),
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_ss_periph_clk_gate_ctl),
|
|
(uint32_t *)(&QM_SCSS_CCU->ccu_ext_clock_ctl),
|
|
/* Power Consumption regs */
|
|
(uint32_t *)(&QM_SCSS_CMP->cmp_pwr), (uint32_t *)(&QM_SCSS_PMU->slp_cfg),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_pullup),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_pullup[1]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_pullup[2]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_pullup[3]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_slew),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_slew[1]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_slew[2]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_slew[3]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_in_en),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_in_en[1]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_in_en[2]),
|
|
(uint32_t *)(&QM_SCSS_PMUX->pmux_in_en[3])};
|
|
#endif /* QUARK_SE */
|
|
|
|
/* Define VERBOSE to turn on printf-based logging */
|
|
#ifdef VERBOSE
|
|
#define SOC_WATCH_TRACE QM_PRINTF
|
|
#else
|
|
#define SOC_WATCH_TRACE(...)
|
|
#endif
|
|
|
|
/*
|
|
* mlog routines -- low-level memory debug logging.
|
|
* Only enable if there's a need to debug this module.
|
|
*/
|
|
#ifdef MLOG_ENABLE
|
|
#define MLOG(e) mlog(e)
|
|
#define MLOG_BYTE(b) mlog_byte(b)
|
|
#define MLOG_SIZE 512 /* Must be a power of 2 */
|
|
static uint8_t mlog_events[MLOG_SIZE];
|
|
static uint16_t mlog_idx = 0;
|
|
void mlog(uint8_t event)
|
|
{
|
|
mlog_events[++mlog_idx % (MLOG_SIZE)] = event;
|
|
}
|
|
void mlog_byte(uint8_t byte)
|
|
{
|
|
const char c[] = {"0123456789ABCDEF"};
|
|
MLOG(c[byte >> 4]);
|
|
MLOG(c[byte & 4]);
|
|
}
|
|
#else /* !MLOG_ENABLE */
|
|
#define MLOG(event)
|
|
#define MLOG_BYTE(b)
|
|
#endif /* !MLOG_ENABLE */
|
|
|
|
/*
|
|
* Defines for frequency related platform registers.
|
|
*/
|
|
#define SW_OSC0_CFG1 (0)
|
|
#define SW_SYS_CLK_CTL (2)
|
|
|
|
/*
|
|
* CONFIGURABLE: Set this to control the number of bytes of RAM you
|
|
* want to dedicate to event buffering. The larger the buffer,
|
|
* the fewer (expensive) flushes we will have to do. The smaller,
|
|
* the lower the memory cost, but the more flushes you will do.
|
|
*/
|
|
#define SOC_WATCH_EVENT_BUFFER_SIZE (256) /* Measured in bytes */
|
|
|
|
/**
|
|
* Power profiling event data buffer. Symbol must be globally
|
|
* visible, so that it can be seen by external tools.
|
|
*/
|
|
struct sw_profiling_event_buffer {
|
|
uint8_t eb_idx; /* Index of next byte to be written */
|
|
uint8_t eb_size; /* Buffer size == SOC_WATCH_EVENT_BUFFER_SIZE */
|
|
uint8_t event_data[SOC_WATCH_EVENT_BUFFER_SIZE - 2]; /* Event data -
|
|
sizeof(idx + size) */
|
|
} soc_watch_event_buffer = {0, SOC_WATCH_EVENT_BUFFER_SIZE - 1, {0}};
|
|
|
|
/* High water mark, i.e. "start trying to flush" point. */
|
|
#define SW_EB_HIGH_WATER (((SOC_WATCH_EVENT_BUFFER_SIZE - 2) * 7) >> 3)
|
|
|
|
NONUTSTATIC int soc_watch_buffer_full(void)
|
|
{
|
|
return (soc_watch_event_buffer.eb_idx >= SW_EB_HIGH_WATER);
|
|
}
|
|
|
|
/**
|
|
* Flag used by the JTAG data extraction routine. During setup, a HW
|
|
* watchpoint is placed on this address. During the flush routine,
|
|
* software writes to it, causing the HW watchpoint to fire, and
|
|
* OpenOCD to extract the data. This symbol MUST be globally visible
|
|
* in order for JTAG data transfer to work.
|
|
*/
|
|
volatile uint8_t soc_watch_flush_flag = 0;
|
|
|
|
/*
|
|
* soc_watch_event_buffer_flush -- Trigger the data buffer flush.
|
|
*/
|
|
static void soc_watch_event_buffer_flush(void)
|
|
{
|
|
/* Figure out if we can successfully flush the data out.
|
|
* If we're sleeping, the JTAG query will fail.
|
|
*/
|
|
#if (QUARK_D2000)
|
|
/**
|
|
* If the "we're going to sleep" bit is set, parts of the
|
|
* SOC are already asleep, and transferring data over
|
|
* the JTAG port is not always reliable. So defer transferring
|
|
* the data until later.
|
|
* @TODO: Determine if there is also a sensitivity to the
|
|
* clock rate.
|
|
*/
|
|
MLOG('F');
|
|
if (QM_SCSS_PMU->aon_vr & QM_AON_VR_VREG_SEL) {
|
|
MLOG('-');
|
|
return; /* We would only send junk, so don't flush. */
|
|
}
|
|
#endif
|
|
|
|
soc_watch_flush_flag = 1; /* Trigger the data extract brkpt */
|
|
soc_watch_event_buffer.eb_idx = 0;
|
|
MLOG('+');
|
|
}
|
|
|
|
/* Store a byte in the event buffer. */
|
|
static void eb_write_char(uint8_t data)
|
|
{
|
|
SOC_WATCH_TRACE("c%d:0x%x [0]=%x\n", soc_watch_event_buffer.eb_idx,
|
|
data, soc_watch_event_buffer.event_data[0]);
|
|
soc_watch_event_buffer.event_data[soc_watch_event_buffer.eb_idx++] =
|
|
data;
|
|
}
|
|
|
|
/* Store a word in the event buffer. */
|
|
static void eb_write_uint32(uint32_t *data)
|
|
{
|
|
uint32_t dst_data = *data;
|
|
uint8_t byte_count = 0;
|
|
SOC_WATCH_TRACE("I%d:0x%x\n", soc_watch_event_buffer.eb_idx, *data);
|
|
while (byte_count < sizeof(uint32_t)) {
|
|
soc_watch_event_buffer
|
|
.event_data[soc_watch_event_buffer.eb_idx++] =
|
|
((dst_data & 0xFF));
|
|
dst_data = dst_data >> 8;
|
|
byte_count++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* soc_watch is duplicating the implementation of qm_irq_lock/unlock APIs
|
|
* for both sensor and x86. This is required to remove the dependency on
|
|
* qm_interrupt.h
|
|
* Reason: soc_watch driver is common to both zephyr and QMSI based applications
|
|
* and zephyr
|
|
* has it's own interrupt handling implementation.
|
|
* Both the functions are added as static inline and hence no side-effects.
|
|
*/
|
|
|
|
#if (QM_SENSOR)
|
|
static inline unsigned int soc_watch_irq_lock(void)
|
|
{
|
|
unsigned int key = 0;
|
|
|
|
/*
|
|
* Store the ARC STATUS32 register fields relating to interrupts into
|
|
* the variable `key' and disable interrupt delivery to the core.
|
|
*/
|
|
__asm__ __volatile__("clri %0" : "=r"(key));
|
|
|
|
return key;
|
|
}
|
|
|
|
static inline void soc_watch_irq_unlock(unsigned int key)
|
|
{
|
|
/*
|
|
* Restore the ARC STATUS32 register fields relating to interrupts based
|
|
* on the variable `key' populated by qm_irq_lock().
|
|
*/
|
|
__asm__ __volatile__("seti %0" : : "ir"(key));
|
|
}
|
|
|
|
#else /* x86 */
|
|
/* x86 CPU FLAGS.IF register field (Interrupt enable Flag, bit 9), indicating
|
|
* whether or not CPU interrupts are enabled.
|
|
*/
|
|
#define X86_FLAGS_IF BIT(9)
|
|
|
|
/**
|
|
* Save interrupt state and disable all interrupts on the CPU.
|
|
* Defined locally for modularity when used in other contexts (i.e. RTOS)
|
|
*
|
|
* @return An architecture-dependent lock-out key representing the "interrupt
|
|
* disable state" prior to the call.
|
|
*/
|
|
static inline unsigned int soc_watch_irq_lock(void)
|
|
{
|
|
unsigned int key = 0;
|
|
|
|
/*
|
|
* Store the CPU FLAGS register into the variable `key' and disable
|
|
* interrupt delivery to the core.
|
|
*/
|
|
__asm__ __volatile__("pushfl;\n\t"
|
|
"cli;\n\t"
|
|
"popl %0;\n\t"
|
|
: "=g"(key)
|
|
:
|
|
: "memory");
|
|
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Restore previous interrupt state on the CPU saved via soc_watch_irq_lock().
|
|
* Defined locally for modularity when used in other contexts (i.e. RTOS)
|
|
*
|
|
* @param[in] key architecture-dependent lock-out key returned by a previous
|
|
* invocation of soc_watch_irq_lock().
|
|
*/
|
|
static inline void soc_watch_irq_unlock(unsigned int key)
|
|
{
|
|
/*
|
|
* `key' holds the CPU FLAGS register content at the time when
|
|
* soc_watch_irq_lock() was called.
|
|
*/
|
|
if (!(key & X86_FLAGS_IF)) {
|
|
/*
|
|
* Interrupts were disabled when soc_watch_irq_lock() was
|
|
* invoked:
|
|
* do not re-enable interrupts.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/* Enable interrupts */
|
|
__asm__ __volatile__("sti;\n\t" : :);
|
|
}
|
|
#endif
|
|
|
|
/* Log an event with one parameter. */
|
|
void soc_watch_log_event(soc_watch_event_t event_id, uintptr_t ev_data)
|
|
{
|
|
soc_watch_log_app_event(event_id, 0, ev_data);
|
|
}
|
|
|
|
/*
|
|
* Log an event with two parameters, where the subtype comes from
|
|
* the user. Note that what actually makes this an 'application event' is
|
|
* the event_id, not the fact that it is coming in via this interface.
|
|
*/
|
|
void soc_watch_log_app_event(soc_watch_event_t event_id, uint8_t ev_subtype,
|
|
uintptr_t ev_data)
|
|
{
|
|
static uint8_t record_rtc = 0;
|
|
const uint32_t *rtc_ctr = (uint32_t *)&QM_RTC[QM_RTC_0]->rtc_ccvr;
|
|
const char *cp;
|
|
unsigned int irq_flag = 0;
|
|
#if (!QM_SENSOR)
|
|
uint64_t tsc = 0; /* hi-res timestamp */
|
|
#else
|
|
uint32_t tsc = 0;
|
|
#endif
|
|
uint32_t rtc_val = *rtc_ctr;
|
|
|
|
#define AVG_EVENT_SIZE 8 /* Size of a typical message in bytes. */
|
|
|
|
MLOG('[');
|
|
irq_flag = soc_watch_irq_lock();
|
|
/* TODO: We know exactly how many bytes of storage we need,
|
|
* since we know the event code. So don't do an "AVG" size thing
|
|
* here--use the exact size!
|
|
*/
|
|
if ((soc_watch_event_buffer.eb_idx + AVG_EVENT_SIZE) <=
|
|
soc_watch_event_buffer.eb_size) {
|
|
|
|
/* Map a halt event to a sleep event where appropriate. */
|
|
#if (QUARK_D2000)
|
|
if (event_id == SOCW_EVENT_HALT) {
|
|
if (QM_SCSS_PMU->aon_vr & QM_AON_VR_VREG_SEL) {
|
|
event_id = SOCW_EVENT_SLEEP;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Record the RTC of the waking event, if it's rousing us from
|
|
* sleep. */
|
|
if (record_rtc) {
|
|
eb_write_char('t');
|
|
eb_write_uint32((uint32_t *)(&rtc_val)); /* Timestamp */
|
|
record_rtc = 0;
|
|
}
|
|
|
|
if (event_id >= SOCW_EVENT_MAX) {
|
|
SOC_WATCH_TRACE("Unknown event id: 0x%x\n", event_id);
|
|
MLOG('?');
|
|
soc_watch_irq_unlock(irq_flag);
|
|
return;
|
|
}
|
|
cp = ev_strs[event_id]; /* Look up event string */
|
|
SOC_WATCH_TRACE("%c", *cp);
|
|
MLOG(*cp);
|
|
eb_write_char(*cp); /* Write event code */
|
|
while (*++cp) {
|
|
switch (*cp) {
|
|
case 'T':
|
|
tsc = get_ticks();
|
|
eb_write_uint32((uint32_t *)(&tsc)); /* Hi-res
|
|
Timestamp */
|
|
break;
|
|
case 't':
|
|
eb_write_uint32(
|
|
(uint32_t *)(&rtc_val)); /* Lo-res
|
|
Timestamp */
|
|
break;
|
|
case 'L':
|
|
record_rtc = 1;
|
|
break;
|
|
case 'R': /* Register data value */
|
|
eb_write_uint32(
|
|
(uint32_t *)platform_regs[ev_data]);
|
|
break;
|
|
case '4': /* 32-bit data value */
|
|
eb_write_uint32((uint32_t *)&ev_data);
|
|
break;
|
|
case '1':
|
|
/* Register ID */
|
|
eb_write_char(((uint32_t)ev_data) & 0xff);
|
|
break;
|
|
case 's':
|
|
/* Event subtype */
|
|
eb_write_char(((uint32_t)ev_subtype) & 0xff);
|
|
break;
|
|
case 'f':
|
|
eb_write_uint32(
|
|
(uint32_t *)platform_regs[SW_OSC0_CFG1]);
|
|
eb_write_uint32(
|
|
(uint32_t *)platform_regs[SW_SYS_CLK_CTL]);
|
|
break;
|
|
default:
|
|
SOC_WATCH_TRACE(
|
|
"Unknown string char: 0x%x on string "
|
|
"0x%x\n",
|
|
*cp, event_id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this is an interrupt which roused the CPU out of a sleep state,
|
|
* don't flush the buffer. (Due to a bug in OpenOCD, doing so will
|
|
* clear the HW watchpoint, ensuring no further flushes are seen by
|
|
* OpenOCD.)
|
|
*/
|
|
if ((soc_watch_buffer_full()) && (event_id != SOCW_EVENT_INTERRUPT)) {
|
|
SOC_WATCH_TRACE("\n --- FLUSH: idx= %d ---\n",
|
|
soc_watch_event_buffer.eb_idx);
|
|
soc_watch_event_buffer_flush();
|
|
}
|
|
MLOG(':');
|
|
MLOG_BYTE(soc_watch_event_buffer.eb_idx);
|
|
soc_watch_irq_unlock(irq_flag);
|
|
MLOG(']');
|
|
}
|
|
|
|
/*
|
|
* Trigger the Watchpoint to flush the data.
|
|
* Application can use this API to trigger the transfer of
|
|
* profiler information to the host whenever it requires.
|
|
* The static function soc_watch_event_buffer_flush() is also used internally
|
|
* when the soc_watch_buffer_full flag is set and is not exposed to the
|
|
* application.
|
|
*/
|
|
void soc_watch_trigger_flush()
|
|
{
|
|
soc_watch_event_buffer_flush();
|
|
}
|
|
#endif /* !(defined(SOC_WATCH) && (!QM_SENSOR)) */
|