/* memory.c -- Memory accessor functions for the AArch64 simulator

   Copyright (C) 2015-2018 Free Software Foundation, Inc.

   Contributed by Red Hat.

   This file is part of GDB.

   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 <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "libiberty.h"

#include "memory.h"
#include "simulator.h"

#include "sim-core.h"

static inline void
mem_error (sim_cpu *cpu, const char *message, uint64_t addr)
{
  TRACE_MEMORY (cpu, "ERROR: %s: %" PRIx64, message, addr);
}

/* FIXME: AArch64 requires aligned memory access if SCTRLR_ELx.A is set,
   but we are not implementing that here.  */
#define FETCH_FUNC64(RETURN_TYPE, ACCESS_TYPE, NAME, N)			\
  RETURN_TYPE								\
  aarch64_get_mem_##NAME (sim_cpu *cpu, uint64_t address)		\
  {									\
    RETURN_TYPE val = (RETURN_TYPE) (ACCESS_TYPE)			\
      sim_core_read_unaligned_##N (cpu, 0, read_map, address);		\
    TRACE_MEMORY (cpu, "read of %" PRIx64 " (%d bytes) from %" PRIx64,	\
		  val, N, address);					\
									\
    return val;								\
  }

FETCH_FUNC64 (uint64_t, uint64_t, u64, 8)
FETCH_FUNC64 (int64_t,   int64_t, s64, 8)

#define FETCH_FUNC32(RETURN_TYPE, ACCESS_TYPE, NAME, N)			\
  RETURN_TYPE								\
  aarch64_get_mem_##NAME (sim_cpu *cpu, uint64_t address)		\
  {									\
    RETURN_TYPE val = (RETURN_TYPE) (ACCESS_TYPE)			\
      sim_core_read_unaligned_##N (cpu, 0, read_map, address);		\
    TRACE_MEMORY (cpu, "read of %8x (%d bytes) from %" PRIx64,		\
		  val, N, address);					\
									\
    return val;								\
  }

FETCH_FUNC32 (uint32_t, uint32_t, u32, 4)
FETCH_FUNC32 (int32_t,   int32_t, s32, 4)
FETCH_FUNC32 (uint32_t, uint16_t, u16, 2)
FETCH_FUNC32 (int32_t,   int16_t, s16, 2)
FETCH_FUNC32 (uint32_t,  uint8_t, u8, 1)
FETCH_FUNC32 (int32_t,    int8_t, s8, 1)

void
aarch64_get_mem_long_double (sim_cpu *cpu, uint64_t address, FRegister *a)
{
  a->v[0] = sim_core_read_unaligned_8 (cpu, 0, read_map, address);
  a->v[1] = sim_core_read_unaligned_8 (cpu, 0, read_map, address + 8);
}

/* FIXME: Aarch64 requires aligned memory access if SCTRLR_ELx.A is set,
   but we are not implementing that here.  */
#define STORE_FUNC(TYPE, NAME, N)					\
  void									\
  aarch64_set_mem_##NAME (sim_cpu *cpu, uint64_t address, TYPE value)	\
  {									\
    TRACE_MEMORY (cpu,							\
		  "write of %" PRIx64 " (%d bytes) to %" PRIx64,	\
		  (uint64_t) value, N, address);			\
									\
    sim_core_write_unaligned_##N (cpu, 0, write_map, address, value);	\
  }

STORE_FUNC (uint64_t, u64, 8)
STORE_FUNC (int64_t,  s64, 8)
STORE_FUNC (uint32_t, u32, 4)
STORE_FUNC (int32_t,  s32, 4)
STORE_FUNC (uint16_t, u16, 2)
STORE_FUNC (int16_t,  s16, 2)
STORE_FUNC (uint8_t,  u8, 1)
STORE_FUNC (int8_t,   s8, 1)

void
aarch64_set_mem_long_double (sim_cpu *cpu, uint64_t address, FRegister a)
{
  TRACE_MEMORY (cpu,
		"write of long double %" PRIx64 " %" PRIx64 " to %" PRIx64,
		a.v[0], a.v[1], address);

  sim_core_write_unaligned_8 (cpu, 0, write_map, address, a.v[0]);
  sim_core_write_unaligned_8 (cpu, 0, write_map, address + 8, a.v[1]);
}

void
aarch64_get_mem_blk (sim_cpu *  cpu,
		     uint64_t   address,
		     char *     buffer,
		     unsigned   length)
{
  unsigned len;

  len = sim_core_read_buffer (CPU_STATE (cpu), cpu, read_map,
			      buffer, address, length);
  if (len == length)
    return;

  memset (buffer, 0, length);
  if (cpu)
    mem_error (cpu, "read of non-existant mem block at", address);

  sim_engine_halt (CPU_STATE (cpu), cpu, NULL, aarch64_get_PC (cpu),
		   sim_stopped, SIM_SIGBUS);
}

const char *
aarch64_get_mem_ptr (sim_cpu *cpu, uint64_t address)
{
  char *addr = sim_core_trans_addr (CPU_STATE (cpu), cpu, read_map, address);

  if (addr == NULL)
    {
      mem_error (cpu, "request for non-existant mem addr of", address);
      sim_engine_halt (CPU_STATE (cpu), cpu, NULL, aarch64_get_PC (cpu),
		       sim_stopped, SIM_SIGBUS);
    }

  return addr;
}

/* We implement a combined stack and heap.  That way the sbrk()
   function in libgloss/aarch64/syscalls.c has a chance to detect
   an out-of-memory condition by noticing a stack/heap collision.

   The heap starts at the end of loaded memory and carries on up
   to an arbitary 2Gb limit.  */

uint64_t
aarch64_get_heap_start (sim_cpu *cpu)
{
  uint64_t heap = trace_sym_value (CPU_STATE (cpu), "end");

  if (heap == 0)
    heap = trace_sym_value (CPU_STATE (cpu), "_end");
  if (heap == 0)
    {
      heap = STACK_TOP - 0x100000;
      sim_io_eprintf (CPU_STATE (cpu),
		      "Unable to find 'end' symbol - using addr based "
		      "upon stack instead %" PRIx64 "\n",
		      heap);
    }
  return heap;
}

uint64_t
aarch64_get_stack_start (sim_cpu *cpu)
{
  if (aarch64_get_heap_start (cpu) >= STACK_TOP)
    mem_error (cpu, "executable is too big", aarch64_get_heap_start (cpu));
  return STACK_TOP;
}
