2011-03-06 01:20:21 +01:00
|
|
|
/* Blackfin Trace (TBUF) model.
|
|
|
|
|
2020-01-01 07:20:01 +01:00
|
|
|
Copyright (C) 2010-2020 Free Software Foundation, Inc.
|
2011-03-06 01:20:21 +01:00
|
|
|
Contributed by Analog Devices, Inc.
|
|
|
|
|
|
|
|
This file is part of simulators.
|
|
|
|
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>. */
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "sim-main.h"
|
|
|
|
#include "devices.h"
|
|
|
|
#include "dv-bfin_cec.h"
|
|
|
|
#include "dv-bfin_trace.h"
|
|
|
|
|
|
|
|
/* Note: The circular buffering here might look a little buggy wrt mid-reads
|
|
|
|
and consuming the top entry, but this is simulating hardware behavior.
|
|
|
|
The hardware is simple, dumb, and fast. Don't write dumb Blackfin
|
|
|
|
software and you won't have a problem. */
|
|
|
|
|
|
|
|
/* The hardware is limited to 16 entries and defines TBUFCTL. Let's extend it ;). */
|
|
|
|
#ifndef SIM_BFIN_TRACE_DEPTH
|
|
|
|
#define SIM_BFIN_TRACE_DEPTH 6
|
|
|
|
#endif
|
|
|
|
#define SIM_BFIN_TRACE_LEN (1 << SIM_BFIN_TRACE_DEPTH)
|
|
|
|
#define SIM_BFIN_TRACE_LEN_MASK (SIM_BFIN_TRACE_LEN - 1)
|
|
|
|
|
|
|
|
struct bfin_trace_entry
|
|
|
|
{
|
|
|
|
bu32 src, dst;
|
|
|
|
};
|
|
|
|
struct bfin_trace
|
|
|
|
{
|
|
|
|
bu32 base;
|
|
|
|
struct bfin_trace_entry buffer[SIM_BFIN_TRACE_LEN];
|
|
|
|
int top, bottom;
|
|
|
|
bool mid;
|
|
|
|
|
|
|
|
/* Order after here is important -- matches hardware MMR layout. */
|
|
|
|
bu32 tbufctl, tbufstat;
|
|
|
|
char _pad[0x100 - 0x8];
|
|
|
|
bu32 tbuf;
|
|
|
|
};
|
|
|
|
#define mmr_base() offsetof(struct bfin_trace, tbufctl)
|
|
|
|
#define mmr_offset(mmr) (offsetof(struct bfin_trace, mmr) - mmr_base())
|
|
|
|
|
2011-03-15 21:44:11 +01:00
|
|
|
static const char * const mmr_names[] =
|
|
|
|
{
|
2011-03-06 01:20:21 +01:00
|
|
|
"TBUFCTL", "TBUFSTAT", [mmr_offset (tbuf) / 4] = "TBUF",
|
|
|
|
};
|
|
|
|
#define mmr_name(off) (mmr_names[(off) / 4] ? : "<INV>")
|
|
|
|
|
|
|
|
/* Ugh, circular buffers. */
|
|
|
|
#define TBUF_LEN(t) ((t)->top - (t)->bottom)
|
|
|
|
#define TBUF_IDX(i) ((i) & SIM_BFIN_TRACE_LEN_MASK)
|
|
|
|
/* TOP is the next slot to fill. */
|
|
|
|
#define TBUF_TOP(t) (&(t)->buffer[TBUF_IDX ((t)->top)])
|
|
|
|
/* LAST is the latest valid slot. */
|
|
|
|
#define TBUF_LAST(t) (&(t)->buffer[TBUF_IDX ((t)->top - 1)])
|
|
|
|
/* LAST_LAST is the second-to-last valid slot. */
|
|
|
|
#define TBUF_LAST_LAST(t) (&(t)->buffer[TBUF_IDX ((t)->top - 2)])
|
|
|
|
|
|
|
|
static unsigned
|
|
|
|
bfin_trace_io_write_buffer (struct hw *me, const void *source,
|
|
|
|
int space, address_word addr, unsigned nr_bytes)
|
|
|
|
{
|
|
|
|
struct bfin_trace *trace = hw_data (me);
|
|
|
|
bu32 mmr_off;
|
|
|
|
bu32 value;
|
|
|
|
|
2015-12-27 01:02:07 +01:00
|
|
|
/* Invalid access mode is higher priority than missing register. */
|
|
|
|
if (!dv_bfin_mmr_require_32 (me, addr, nr_bytes, true))
|
|
|
|
return 0;
|
|
|
|
|
2011-03-06 01:20:21 +01:00
|
|
|
value = dv_load_4 (source);
|
|
|
|
mmr_off = addr - trace->base;
|
|
|
|
|
|
|
|
HW_TRACE_WRITE ();
|
|
|
|
|
|
|
|
switch (mmr_off)
|
|
|
|
{
|
|
|
|
case mmr_offset(tbufctl):
|
|
|
|
trace->tbufctl = value;
|
|
|
|
break;
|
|
|
|
case mmr_offset(tbufstat):
|
|
|
|
case mmr_offset(tbuf):
|
|
|
|
/* Discard writes to these. */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dv_bfin_mmr_invalid (me, addr, nr_bytes, true);
|
2015-12-27 01:02:07 +01:00
|
|
|
return 0;
|
2011-03-06 01:20:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nr_bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned
|
|
|
|
bfin_trace_io_read_buffer (struct hw *me, void *dest,
|
|
|
|
int space, address_word addr, unsigned nr_bytes)
|
|
|
|
{
|
|
|
|
struct bfin_trace *trace = hw_data (me);
|
|
|
|
bu32 mmr_off;
|
|
|
|
bu32 value;
|
|
|
|
|
2015-12-27 01:02:07 +01:00
|
|
|
/* Invalid access mode is higher priority than missing register. */
|
|
|
|
if (!dv_bfin_mmr_require_32 (me, addr, nr_bytes, false))
|
|
|
|
return 0;
|
|
|
|
|
2011-03-06 01:20:21 +01:00
|
|
|
mmr_off = addr - trace->base;
|
|
|
|
|
|
|
|
HW_TRACE_READ ();
|
|
|
|
|
|
|
|
switch (mmr_off)
|
|
|
|
{
|
|
|
|
case mmr_offset(tbufctl):
|
|
|
|
value = trace->tbufctl;
|
|
|
|
break;
|
|
|
|
case mmr_offset(tbufstat):
|
|
|
|
/* Hardware is limited to 16 entries, so to stay compatible with
|
|
|
|
software, limit the value to 16. For software algorithms that
|
|
|
|
keep reading while (TBUFSTAT != 0), they'll get all of it. */
|
2016-01-05 04:24:03 +01:00
|
|
|
value = min (TBUF_LEN (trace), 16);
|
2011-03-06 01:20:21 +01:00
|
|
|
break;
|
|
|
|
case mmr_offset(tbuf):
|
|
|
|
{
|
|
|
|
struct bfin_trace_entry *e;
|
|
|
|
|
|
|
|
if (TBUF_LEN (trace) == 0)
|
|
|
|
{
|
|
|
|
value = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
e = TBUF_LAST (trace);
|
|
|
|
if (trace->mid)
|
|
|
|
{
|
|
|
|
value = e->src;
|
|
|
|
--trace->top;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
value = e->dst;
|
|
|
|
trace->mid = !trace->mid;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
2015-12-27 01:02:07 +01:00
|
|
|
dv_bfin_mmr_invalid (me, addr, nr_bytes, false);
|
|
|
|
return 0;
|
2011-03-06 01:20:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
dv_store_4 (dest, value);
|
|
|
|
|
|
|
|
return nr_bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
attach_bfin_trace_regs (struct hw *me, struct bfin_trace *trace)
|
|
|
|
{
|
|
|
|
address_word attach_address;
|
|
|
|
int attach_space;
|
|
|
|
unsigned attach_size;
|
|
|
|
reg_property_spec reg;
|
|
|
|
|
|
|
|
if (hw_find_property (me, "reg") == NULL)
|
|
|
|
hw_abort (me, "Missing \"reg\" property");
|
|
|
|
|
|
|
|
if (!hw_find_reg_array_property (me, "reg", 0, ®))
|
|
|
|
hw_abort (me, "\"reg\" property must contain three addr/size entries");
|
|
|
|
|
|
|
|
hw_unit_address_to_attach_address (hw_parent (me),
|
|
|
|
®.address,
|
|
|
|
&attach_space, &attach_address, me);
|
|
|
|
hw_unit_size_to_attach_size (hw_parent (me), ®.size, &attach_size, me);
|
|
|
|
|
|
|
|
if (attach_size != BFIN_COREMMR_TRACE_SIZE)
|
|
|
|
hw_abort (me, "\"reg\" size must be %#x", BFIN_COREMMR_TRACE_SIZE);
|
|
|
|
|
|
|
|
hw_attach_address (hw_parent (me),
|
|
|
|
0, attach_space, attach_address, attach_size, me);
|
|
|
|
|
|
|
|
trace->base = attach_address;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
bfin_trace_finish (struct hw *me)
|
|
|
|
{
|
|
|
|
struct bfin_trace *trace;
|
|
|
|
|
|
|
|
trace = HW_ZALLOC (me, struct bfin_trace);
|
|
|
|
|
|
|
|
set_hw_data (me, trace);
|
|
|
|
set_hw_io_read_buffer (me, bfin_trace_io_read_buffer);
|
|
|
|
set_hw_io_write_buffer (me, bfin_trace_io_write_buffer);
|
|
|
|
|
|
|
|
attach_bfin_trace_regs (me, trace);
|
|
|
|
}
|
|
|
|
|
2011-03-15 21:55:11 +01:00
|
|
|
const struct hw_descriptor dv_bfin_trace_descriptor[] =
|
|
|
|
{
|
2011-03-06 01:20:21 +01:00
|
|
|
{"bfin_trace", bfin_trace_finish,},
|
|
|
|
{NULL, NULL},
|
|
|
|
};
|
|
|
|
|
|
|
|
#define TRACE_STATE(cpu) DV_STATE_CACHED (cpu, trace)
|
|
|
|
|
|
|
|
/* This is not re-entrant, but neither is the cpu state, so this shouldn't
|
|
|
|
be a big deal ... */
|
|
|
|
void bfin_trace_queue (SIM_CPU *cpu, bu32 src_pc, bu32 dst_pc, int hwloop)
|
|
|
|
{
|
|
|
|
struct bfin_trace *trace = TRACE_STATE (cpu);
|
|
|
|
struct bfin_trace_entry *e;
|
|
|
|
int len, ivg;
|
|
|
|
|
|
|
|
/* Only queue if powered. */
|
|
|
|
if (!(trace->tbufctl & TBUFPWR))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Only queue if enabled. */
|
|
|
|
if (!(trace->tbufctl & TBUFEN))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Ignore hardware loops.
|
|
|
|
XXX: This is what the hardware does, but an option to ignore
|
|
|
|
could be useful for debugging ... */
|
|
|
|
if (hwloop >= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Only queue if at right level. */
|
|
|
|
ivg = cec_get_ivg (cpu);
|
|
|
|
if (ivg == IVG_RST)
|
|
|
|
/* XXX: This is what the hardware does, but an option to ignore
|
|
|
|
could be useful for debugging ... */
|
|
|
|
return;
|
|
|
|
if (ivg <= IVG_EVX && (trace->tbufctl & TBUFOVF))
|
|
|
|
/* XXX: This is what the hardware does, but an option to ignore
|
|
|
|
could be useful for debugging ... just don't throw an
|
|
|
|
exception when full and in EVT{0..3}. */
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Are we full ? */
|
|
|
|
len = TBUF_LEN (trace);
|
|
|
|
if (len == SIM_BFIN_TRACE_LEN)
|
|
|
|
{
|
|
|
|
if (trace->tbufctl & TBUFOVF)
|
|
|
|
{
|
|
|
|
cec_exception (cpu, VEC_OVFLOW);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Overwrite next entry. */
|
|
|
|
++trace->bottom;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* One level compression. */
|
|
|
|
if (len >= 1 && (trace->tbufctl & TBUFCMPLP))
|
|
|
|
{
|
|
|
|
e = TBUF_LAST (trace);
|
|
|
|
if (src_pc == (e->src & ~1) && dst_pc == (e->dst & ~1))
|
|
|
|
{
|
|
|
|
/* Hardware sets LSB when level is compressed. */
|
|
|
|
e->dst |= 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Two level compression. */
|
|
|
|
if (len >= 2 && (trace->tbufctl & TBUFCMPLP_DOUBLE))
|
|
|
|
{
|
|
|
|
e = TBUF_LAST_LAST (trace);
|
|
|
|
if (src_pc == (e->src & ~1) && dst_pc == (e->dst & ~1))
|
|
|
|
{
|
|
|
|
/* Hardware sets LSB when level is compressed. */
|
|
|
|
e->src |= 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
e = TBUF_TOP (trace);
|
|
|
|
e->dst = dst_pc;
|
|
|
|
e->src = src_pc;
|
|
|
|
++trace->top;
|
|
|
|
}
|