/* Blackfin Trace (TBUF) model.

   Copyright (C) 2010-2026 Free Software Foundation, Inc.
   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/>.  */

/* This must come before any other includes.  */
#include "defs.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())

static const char * const mmr_names[] =
{
  "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;

  /* Invalid access mode is higher priority than missing register.  */
  if (!dv_bfin_mmr_require_32 (me, addr, nr_bytes, true))
    return 0;

  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);
      return 0;
    }

  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;

  /* Invalid access mode is higher priority than missing register.  */
  if (!dv_bfin_mmr_require_32 (me, addr, nr_bytes, false))
    return 0;

  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.  */
      value = min (TBUF_LEN (trace), 16);
      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:
      dv_bfin_mmr_invalid (me, addr, nr_bytes, false);
      return 0;
    }

  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, &reg))
    hw_abort (me, "\"reg\" property must contain three addr/size entries");

  hw_unit_address_to_attach_address (hw_parent (me),
				     &reg.address,
				     &attach_space, &attach_address, me);
  hw_unit_size_to_attach_size (hw_parent (me), &reg.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);
}

const struct hw_descriptor dv_bfin_trace_descriptor[] =
{
  {"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;
}
