/*  This file is part of the program psim.

    Copyright (C) 1994-1998, Andrew Cagney <cagney@highland.com.au>

    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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
    */

#include "hw-main.h"
#include "hw-base.h"

#include "sim-io.h"
#include "sim-assert.h"

#ifdef HAVE_STRING_H
#include <string.h>
#else
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#endif

#define TRACE(A,B)

/* property entries */

struct hw_property_data {
  struct hw_property_data *next;
  struct hw_property *property;
  const void *init_array;
  unsigned sizeof_init_array;
};

void
create_hw_property_data (struct hw *me)
{
}

void
delete_hw_property_data (struct hw *me)
{
}


/* Device Properties: */

static struct hw_property_data *
find_property_data (struct hw *me,
		    const char *property)
{
  struct hw_property_data *entry;
  ASSERT (property != NULL);
  entry = me->properties_of_hw;
  while (entry != NULL)
    {
      if (strcmp (entry->property->name, property) == 0)
	return entry;
      entry = entry->next;
    }
  return NULL;
}


static void
hw_add_property (struct hw *me,
		 const char *property,
		 hw_property_type type,
		 const void *init_array,
		 unsigned sizeof_init_array,
		 const void *array,
		 unsigned sizeof_array,
		 const struct hw_property *original,
		 object_disposition disposition)
{
  struct hw_property_data *new_entry = NULL;
  struct hw_property *new_value = NULL;
  
  /* find the list end */
  struct hw_property_data **insertion_point = &me->properties_of_hw;
  while (*insertion_point != NULL)
    {
      if (strcmp ((*insertion_point)->property->name, property) == 0)
	return;
      insertion_point = &(*insertion_point)->next;
    }
  
  /* create a new value */
  new_value = HW_ZALLOC (me, struct hw_property);
  new_value->name = (char *) strdup (property);
  new_value->type = type;
  if (sizeof_array > 0)
    {
      void *new_array = hw_zalloc (me, sizeof_array);
      memcpy (new_array, array, sizeof_array);
      new_value->array = new_array;
      new_value->sizeof_array = sizeof_array;
    }
  new_value->owner = me;
  new_value->original = original;
  new_value->disposition = disposition;
  
  /* insert the value into the list */
  new_entry = HW_ZALLOC (me, struct hw_property_data);
  *insertion_point = new_entry;
  if (sizeof_init_array > 0)
    {
      void *new_init_array = hw_zalloc (me, sizeof_init_array);
      memcpy (new_init_array, init_array, sizeof_init_array);
      new_entry->init_array = new_init_array;
      new_entry->sizeof_init_array = sizeof_init_array;
    }
  new_entry->property = new_value;
}


static void
hw_set_property (struct hw *me,
		 const char *property,
		 hw_property_type type,
		 const void *array,
		 int sizeof_array)
{
  /* find the property */
  struct hw_property_data *entry = find_property_data (me, property);
  if (entry != NULL)
    {
      /* existing property - update it */
      void *new_array = 0;
      struct hw_property *value = entry->property;
      /* check the type matches */
      if (value->type != type)
	hw_abort (me, "conflict between type of new and old value for property %s", property);
      /* replace its value */
      if (value->array != NULL)
	hw_free (me, (void*)value->array);
      new_array = (sizeof_array > 0
		   ? hw_zalloc (me, sizeof_array)
		   : (void*)0);
      value->array = new_array;
      value->sizeof_array = sizeof_array;
      if (sizeof_array > 0)
	memcpy (new_array, array, sizeof_array);
      return;
    }
  else
    {
      /* new property - create it */
      hw_add_property (me, property, type,
		       NULL, 0, array, sizeof_array,
		       NULL, temporary_object);
    }
}


#if 0
static void
clean_hw_properties (struct hw *me)
{
  struct hw_property_data **delete_point = &me->properties_of_hw;
  while (*delete_point != NULL)
    {
      struct hw_property_data *current = *delete_point;
      switch (current->property->disposition)
	{
	case permenant_object:
	  /* zap the current value, will be initialized later */
	  ASSERT (current->init_array != NULL);
	  if (current->property->array != NULL)
	    {
	      hw_free (me, (void*)current->property->array);
	      current->property->array = NULL;
	    }
	  delete_point = &(*delete_point)->next;
	  break;
	case temporary_object:
	  /* zap the actual property, was created during simulation run */
	  ASSERT (current->init_array == NULL);
	  *delete_point = current->next;
	  if (current->property->array != NULL)
	    hw_free (me, (void*)current->property->array);
	  hw_free (me, current->property);
	  hw_free (me, current);
	  break;
	}
    }
}
#endif

#if 0
void
hw_init_static_properties (SIM_DESC sd,
			   struct hw *me,
			   void *data)
{
  struct hw_property_data *property;
  for (property = me->properties_of_hw;
       property != NULL;
       property = property->next)
    {
      ASSERT (property->init_array != NULL);
      ASSERT (property->property->array == NULL);
      ASSERT(property->property->disposition == permenant_object);
      switch (property->property->type)
	{
	case array_property:
	case boolean_property:
	case range_array_property:
	case reg_array_property:
	case string_property:
	case string_array_property:
	case integer_property:
	  /* delete the property, and replace it with the original */
	  hw_set_property (me, property->property->name,
			   property->property->type,
			   property->init_array,
			   property->sizeof_init_array);
	  break;
#if 0
	case ihandle_property:
	  break;
#endif
	}
    }
}
#endif


#if 0
void
hw_init_runtime_properties (SIM_DESC sd,
			    struct hw *me,
			    void *data)
{
  struct hw_property_data *property;
  for (property = me->properties_of_hw;
       property != NULL;
       property = property->next)
    {
      switch (property->property->disposition)
	{
	case permenant_object:
	  switch (property->property->type)
	    {
#if 0
	    case ihandle_property:
	      {
		struct hw_instance *ihandle;
		ihandle_runtime_property_spec spec;
		ASSERT (property->init_array != NULL);
		ASSERT (property->property->array == NULL);
		hw_find_ihandle_runtime_property (me, property->property->name, &spec);
		ihandle = tree_instance (me, spec.full_path);
		hw_set_ihandle_property (me, property->property->name, ihandle);
		break;
	      }
#endif
	    case array_property:
	    case boolean_property:
	    case range_array_property:
	    case integer_property:
	    case reg_array_property:
	    case string_property:
	    case string_array_property:
	      ASSERT (property->init_array != NULL);
	      ASSERT (property->property->array != NULL);
	      break;
	    }
	  break;
	case temporary_object:
	  ASSERT (property->init_array == NULL);
	  ASSERT (property->property->array != NULL);
	  break;
	}
    }
}
#endif



const struct hw_property *
hw_next_property (const struct hw_property *property)
{
  /* find the property in the list */
  struct hw *owner = property->owner;
  struct hw_property_data *entry = owner->properties_of_hw;
  while (entry != NULL && entry->property != property)
    entry = entry->next;
  /* now return the following property */
  ASSERT (entry != NULL); /* must be a member! */
  if (entry->next != NULL)
    return entry->next->property;
  else
    return NULL;
}


const struct hw_property *
hw_find_property (struct hw *me,
		  const char *property)
{
  if (me == NULL)
    {
      return NULL;
    }
  else if (property == NULL || strcmp (property, "") == 0)
    {
      if (me->properties_of_hw == NULL)
	return NULL;
      else
	return me->properties_of_hw->property;
    }
  else
    {
      struct hw_property_data *entry = find_property_data (me, property);
      if (entry != NULL)
	return entry->property;
    }
  return NULL;
}


void
hw_add_array_property (struct hw *me,
		       const char *property,
		       const void *array,
		       int sizeof_array)
{
  hw_add_property (me, property, array_property,
		   array, sizeof_array, array, sizeof_array,
		   NULL, permenant_object);
}

void
hw_set_array_property (struct hw *me,
		       const char *property,
		       const void *array,
		       int sizeof_array)
{
  hw_set_property (me, property, array_property, array, sizeof_array);
}

const struct hw_property *
hw_find_array_property (struct hw *me,
			const char *property)
{
  const struct hw_property *node;
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != array_property)
    hw_abort (me, "property \"%s\" of wrong type (array)", property);
  return node;
}



void
hw_add_boolean_property (struct hw *me,
			 const char *property,
			 int boolean)
{
  signed32 new_boolean = (boolean ? -1 : 0);
  hw_add_property (me, property, boolean_property,
		   &new_boolean, sizeof(new_boolean),
		   &new_boolean, sizeof(new_boolean),
		   NULL, permenant_object);
}

int
hw_find_boolean_property (struct hw *me,
			  const char *property)
{
  const struct hw_property *node;
  unsigned_cell boolean;
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != boolean_property)
    hw_abort (me, "property \"%s\" of wrong type (boolean)", property);
  ASSERT (sizeof (boolean) == node->sizeof_array);
  memcpy (&boolean, node->array, sizeof (boolean));
  return boolean;
}



#if 0
void
hw_add_ihandle_runtime_property (struct hw *me,
				 const char *property,
				 const ihandle_runtime_property_spec *ihandle)
{
  /* enter the full path as the init array */
  hw_add_property (me, property, ihandle_property,
		   ihandle->full_path, strlen(ihandle->full_path) + 1,
		   NULL, 0,
		   NULL, permenant_object);
}
#endif

#if 0
void
hw_find_ihandle_runtime_property (struct hw *me,
				  const char *property,
				  ihandle_runtime_property_spec *ihandle)
{
  struct hw_property_data *entry = find_property_data (me, property);
  TRACE (trace_devices,
	 ("hw_find_ihandle_runtime_property(me=0x%lx, property=%s)\n",
	  (long)me, property));
  if (entry == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (entry->property->type != ihandle_property
      || entry->property->disposition != permenant_object)
    hw_abort (me, "property \"%s\" of wrong type", property);
  ASSERT (entry->init_array != NULL);
  /* the full path */
  ihandle->full_path = entry->init_array;
}
#endif



#if 0
void
hw_set_ihandle_property (struct hw *me,
			 const char *property,
			 hw_instance *ihandle)
{
  unsigned_cell cells;
  cells = H2BE_cell (hw_instance_to_external (ihandle));
  hw_set_property (me, property, ihandle_property,
		   &cells, sizeof (cells));
		      
}
#endif

#if 0
hw_instance *
hw_find_ihandle_property (struct hw *me,
			  const char *property)
{
  const hw_property_data *node;
  unsigned_cell ihandle;
  hw_instance *instance;

  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != ihandle_property)
    hw_abort(me, "property \"%s\" of wrong type (ihandle)", property);
  if (node->array == NULL)
    hw_abort(me, "runtime property \"%s\" not yet initialized", property);

  ASSERT (sizeof(ihandle) == node->sizeof_array);
  memcpy (&ihandle, node->array, sizeof(ihandle));
  instance = external_to_hw_instance (me, BE2H_cell(ihandle));
  ASSERT (instance != NULL);
  return instance;
}
#endif


void
hw_add_integer_property (struct hw *me,
			 const char *property,
			 signed_cell integer)
{
  H2BE (integer);
  hw_add_property (me, property, integer_property,
		   &integer, sizeof(integer),
		   &integer, sizeof(integer),
		   NULL, permenant_object);
}

signed_cell
hw_find_integer_property (struct hw *me,
			  const char *property)
{
  const struct hw_property *node;
  signed_cell integer;
  TRACE (trace_devices,
	 ("hw_find_integer(me=0x%lx, property=%s)\n",
	  (long)me, property));
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != integer_property)
    hw_abort (me, "property \"%s\" of wrong type (integer)", property);
  ASSERT (sizeof(integer) == node->sizeof_array);
  memcpy (&integer, node->array, sizeof (integer));
  return BE2H_cell (integer);
}

int
hw_find_integer_array_property (struct hw *me,
				const char *property,
				unsigned index,
				signed_cell *integer)
{
  const struct hw_property *node;
  int sizeof_integer = sizeof (*integer);
  signed_cell *cell;
  TRACE (trace_devices,
	 ("hw_find_integer(me=0x%lx, property=%s)\n",
	  (long)me, property));
  
  /* check things sane */
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != integer_property
      && node->type != array_property)
    hw_abort (me, "property \"%s\" of wrong type (integer or array)", property);
  if ((node->sizeof_array % sizeof_integer) != 0)
    hw_abort (me, "property \"%s\" contains an incomplete number of cells", property);
  if (node->sizeof_array <= sizeof_integer * index)
    return 0;
  
  /* Find and convert the value */
  cell = ((signed_cell*)node->array) + index;
  *integer = BE2H_cell (*cell);
  
  return node->sizeof_array / sizeof_integer;
}


static unsigned_cell *
unit_address_to_cells (const hw_unit *unit,
		       unsigned_cell *cell,
		       int nr_cells)
{
  int i;
  ASSERT(nr_cells == unit->nr_cells);
  for (i = 0; i < unit->nr_cells; i++)
    {
      *cell = H2BE_cell (unit->cells[i]);
      cell += 1;
    }
  return cell;
}


static const unsigned_cell *
cells_to_unit_address (const unsigned_cell *cell,
		       hw_unit *unit,
		       int nr_cells)
{
  int i;
  memset(unit, 0, sizeof(*unit));
  unit->nr_cells = nr_cells;
  for (i = 0; i < unit->nr_cells; i++)
    {
      unit->cells[i] = BE2H_cell (*cell);
      cell += 1;
    }
  return cell;
}


static unsigned
nr_range_property_cells (struct hw *me,
			 int nr_ranges)
{
  return ((hw_unit_nr_address_cells (me)
	   + hw_unit_nr_address_cells (hw_parent (me))
	   + hw_unit_nr_size_cells (me))
	  ) * nr_ranges;
}

void
hw_add_range_array_property (struct hw *me,
			     const char *property,
			     const range_property_spec *ranges,
			     unsigned nr_ranges)
{
  unsigned sizeof_cells = (nr_range_property_cells (me, nr_ranges)
			   * sizeof (unsigned_cell));
  unsigned_cell *cells = hw_zalloc (me, sizeof_cells);
  unsigned_cell *cell;
  int i;
  
  /* copy the property elements over */
  cell = cells;
  for (i = 0; i < nr_ranges; i++)
    {
      const range_property_spec *range = &ranges[i];
      /* copy the child address */
      cell = unit_address_to_cells (&range->child_address, cell,
				    hw_unit_nr_address_cells (me));
      /* copy the parent address */
      cell = unit_address_to_cells (&range->parent_address, cell, 
				    hw_unit_nr_address_cells (hw_parent (me)));
      /* copy the size */
      cell = unit_address_to_cells (&range->size, cell, 
				    hw_unit_nr_size_cells (me));
    }
  ASSERT (cell == &cells[nr_range_property_cells (me, nr_ranges)]);
  
  /* add it */
  hw_add_property (me, property, range_array_property,
		   cells, sizeof_cells,
		   cells, sizeof_cells,
		   NULL, permenant_object);
  
  hw_free (me, cells);
}

int
hw_find_range_array_property (struct hw *me,
			      const char *property,
			      unsigned index,
			      range_property_spec *range)
{
  const struct hw_property *node;
  unsigned sizeof_entry = (nr_range_property_cells (me, 1)
			   * sizeof (unsigned_cell));
  const unsigned_cell *cells;
  
  /* locate the property */
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != range_array_property)
    hw_abort (me, "property \"%s\" of wrong type (range array)", property);
  
  /* aligned ? */
  if ((node->sizeof_array % sizeof_entry) != 0)
    hw_abort (me, "property \"%s\" contains an incomplete number of entries",
	      property);
  
  /* within bounds? */
  if (node->sizeof_array < sizeof_entry * (index + 1))
    return 0;
  
  /* find the range of interest */
  cells = (unsigned_cell*)((char*)node->array + sizeof_entry * index);
  
  /* copy the child address out - converting as we go */
  cells = cells_to_unit_address (cells, &range->child_address,
				 hw_unit_nr_address_cells (me));
  
  /* copy the parent address out - converting as we go */
  cells = cells_to_unit_address (cells, &range->parent_address,
				 hw_unit_nr_address_cells (hw_parent (me)));
  
  /* copy the size - converting as we go */
  cells = cells_to_unit_address (cells, &range->size,
				 hw_unit_nr_size_cells (me));
  
  return node->sizeof_array / sizeof_entry;
}


static unsigned
nr_reg_property_cells (struct hw *me,
		       int nr_regs)
{
  return (hw_unit_nr_address_cells (hw_parent(me))
	  + hw_unit_nr_size_cells (hw_parent(me))
	  ) * nr_regs;
}

void
hw_add_reg_array_property (struct hw *me,
			   const char *property,
			   const reg_property_spec *regs,
			   unsigned nr_regs)
{
  unsigned sizeof_cells = (nr_reg_property_cells (me, nr_regs)
			   * sizeof (unsigned_cell));
  unsigned_cell *cells = hw_zalloc (me, sizeof_cells);
  unsigned_cell *cell;
  int i;
  
  /* copy the property elements over */
  cell = cells;
  for (i = 0; i < nr_regs; i++)
    {
      const reg_property_spec *reg = &regs[i];
      /* copy the address */
      cell = unit_address_to_cells (&reg->address, cell,
				    hw_unit_nr_address_cells (hw_parent (me)));
      /* copy the size */
      cell = unit_address_to_cells (&reg->size, cell,
				    hw_unit_nr_size_cells (hw_parent (me)));
    }
  ASSERT (cell == &cells[nr_reg_property_cells (me, nr_regs)]);
  
  /* add it */
  hw_add_property (me, property, reg_array_property,
		   cells, sizeof_cells,
		   cells, sizeof_cells,
		   NULL, permenant_object);
  
  hw_free (me, cells);
}

int
hw_find_reg_array_property (struct hw *me,
			    const char *property,
			    unsigned index,
			    reg_property_spec *reg)
{
  const struct hw_property *node;
  unsigned sizeof_entry = (nr_reg_property_cells (me, 1)
			   * sizeof (unsigned_cell));
  const unsigned_cell *cells;
  
  /* locate the property */
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != reg_array_property)
    hw_abort (me, "property \"%s\" of wrong type (reg array)", property);
  
  /* aligned ? */
  if ((node->sizeof_array % sizeof_entry) != 0)
    hw_abort (me, "property \"%s\" contains an incomplete number of entries",
	      property);
  
  /* within bounds? */
  if (node->sizeof_array < sizeof_entry * (index + 1))
    return 0;
  
  /* find the range of interest */
  cells = (unsigned_cell*)((char*)node->array + sizeof_entry * index);
  
  /* copy the address out - converting as we go */
  cells = cells_to_unit_address (cells, &reg->address,
				 hw_unit_nr_address_cells (hw_parent (me)));
  
  /* copy the size out - converting as we go */
  cells = cells_to_unit_address (cells, &reg->size,
				 hw_unit_nr_size_cells (hw_parent (me)));
  
  return node->sizeof_array / sizeof_entry;
}


void
hw_add_string_property (struct hw *me,
			const char *property,
			const char *string)
{
  hw_add_property (me, property, string_property,
		   string, strlen(string) + 1,
		   string, strlen(string) + 1,
		   NULL, permenant_object);
}

const char *
hw_find_string_property (struct hw *me,
			 const char *property)
{
  const struct hw_property *node;
  const char *string;
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  if (node->type != string_property)
    hw_abort (me, "property \"%s\" of wrong type (string)", property);
  string = node->array;
  ASSERT (strlen(string) + 1 == node->sizeof_array);
  return string;
}

void
hw_add_string_array_property (struct hw *me,
			      const char *property,
			      const string_property_spec *strings,
			      unsigned nr_strings)
{
  int sizeof_array;
  int string_nr;
  char *array;
  char *chp;
  if (nr_strings == 0)
    hw_abort (me, "property \"%s\" must be non-null", property);
  /* total up the size of the needed array */
  for (sizeof_array = 0, string_nr = 0;
       string_nr < nr_strings;
       string_nr ++)
    {
      sizeof_array += strlen (strings[string_nr]) + 1;
    }
  /* create the array */
  array = (char*) hw_zalloc (me, sizeof_array);
  chp = array;
  for (string_nr = 0;
       string_nr < nr_strings;
       string_nr++)
    {
      strcpy (chp, strings[string_nr]);
      chp += strlen (chp) + 1;
    }
  ASSERT (chp == array + sizeof_array);
  /* now enter it */
  hw_add_property (me, property, string_array_property,
		   array, sizeof_array,
		   array, sizeof_array,
		   NULL, permenant_object);
}

int
hw_find_string_array_property (struct hw *me,
			       const char *property,
			       unsigned index,
			       string_property_spec *string)
{
  const struct hw_property *node;
  node = hw_find_property (me, property);
  if (node == NULL)
    hw_abort (me, "property \"%s\" not found", property);
  switch (node->type)
    {
    default:
      hw_abort (me, "property \"%s\" of wrong type", property);
      break;
    case string_property:
      if (index == 0)
	{
	  *string = node->array;
	  ASSERT (strlen(*string) + 1 == node->sizeof_array);
	  return 1;
	}
      break;
    case array_property:
      if (node->sizeof_array == 0
	  || ((char*)node->array)[node->sizeof_array - 1] != '\0')
	hw_abort (me, "property \"%s\" invalid for string array", property);
      /* FALL THROUGH */
    case string_array_property:
      ASSERT (node->sizeof_array > 0);
      ASSERT (((char*)node->array)[node->sizeof_array - 1] == '\0');
      {
	const char *chp = node->array;
	int nr_entries = 0;
	/* count the number of strings, keeping an eye out for the one
	   we're looking for */
	*string = chp;
	do
	  {
	    if (*chp == '\0')
	      {
		/* next string */
		nr_entries++;
		chp++;
		if (nr_entries == index)
		  *string = chp;
	      }
	    else
	      {
		chp++;
	      }
	  } while (chp < (char*)node->array + node->sizeof_array);
	if (index < nr_entries)
	  return nr_entries;
	else
	  {
	    *string = NULL;
	    return 0;
	  }
      }
      break;
    }
  return 0;
}

void
hw_add_duplicate_property (struct hw *me,
			   const char *property,
			   const struct hw_property *original)
{
  struct hw_property_data *master;
  TRACE (trace_devices,
	 ("hw_add_duplicate_property(me=0x%lx, property=%s, ...)\n",
	  (long)me, property));
  if (original->disposition != permenant_object)
    hw_abort (me, "Can only duplicate permenant objects");
  /* find the original's master */
  master = original->owner->properties_of_hw;
  while (master->property != original)
    {
      master = master->next;
      ASSERT(master != NULL);
    }
  /* now duplicate it */
  hw_add_property (me, property,
		   original->type,
		   master->init_array, master->sizeof_init_array,
		   original->array, original->sizeof_array,
		   original, permenant_object);
}
