/* Hardware ports.
   Copyright (C) 1998-2013 Free Software Foundation, Inc.
   Contributed by Andrew Cagney and Cygnus Solutions.

This file is part of GDB, the GNU debugger.

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 "hw-main.h"
#include "hw-base.h"

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

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

#include <ctype.h>


struct hw_port_edge
{
  int my_port;
  struct hw *dest;
  int dest_port;
  struct hw_port_edge *next;
  object_disposition disposition;
};

struct hw_port_data
{
  hw_port_event_method *to_port_event;
  const struct hw_port_descriptor *ports;
  struct hw_port_edge *edges;
};

const struct hw_port_descriptor empty_hw_ports[] =
{
  { NULL, 0, 0, 0 },
};

static void
panic_hw_port_event (struct hw *me,
		     int my_port,
		     struct hw *source,
		     int source_port,
		     int level)
{
  hw_abort (me, "no port method");
}

void
create_hw_port_data (struct hw *me)
{
  me->ports_of_hw = HW_ZALLOC (me, struct hw_port_data);
  set_hw_port_event (me, panic_hw_port_event);
  set_hw_ports (me, empty_hw_ports);
}

void
delete_hw_port_data (struct hw *me)
{
  hw_free (me, me->ports_of_hw);
  me->ports_of_hw = NULL;
}

void
set_hw_ports (struct hw *me,
	      const struct hw_port_descriptor ports[])
{
  me->ports_of_hw->ports = ports;
}

void
set_hw_port_event (struct hw *me,
		   hw_port_event_method *port_event)
{
  me->ports_of_hw->to_port_event = port_event;
}


static void
attach_hw_port_edge (struct hw *me,
		     struct hw_port_edge **list,
		     int my_port,
		     struct hw *dest,
		     int dest_port,
		     object_disposition disposition)
{
  struct hw_port_edge *new_edge = HW_ZALLOC (me, struct hw_port_edge);
  new_edge->my_port = my_port;
  new_edge->dest = dest;
  new_edge->dest_port = dest_port;
  new_edge->next = *list;
  new_edge->disposition = disposition;
  *list = new_edge;
}


static void
detach_hw_port_edge (struct hw *me,
		     struct hw_port_edge **list,
		     int my_port,
		     struct hw *dest,
		     int dest_port)
{
  while (*list != NULL)
    {
      struct hw_port_edge *old_edge = *list;
      if (old_edge->dest == dest
	  && old_edge->dest_port == dest_port
	  && old_edge->my_port == my_port)
	{
	  if (old_edge->disposition == permenant_object)
	    hw_abort (me, "attempt to delete permenant port edge");
	  *list = old_edge->next;
	  hw_free (me, old_edge);
	  return;
	}
    }
  hw_abort (me, "attempt to delete unattached port");
}


#if 0
static void
clean_hw_port_edges (struct hw_port_edge **list)
{
  while (*list != NULL)
    {
      struct hw_port_edge *old_edge = *list;
      switch (old_edge->disposition)
	{
	case permenant_object:
	  list = &old_edge->next;
	  break;
	case temporary_object:
	  *list = old_edge->next;
	  hw_free (me, old_edge);
	  break;
	}
    }
}
#endif


/* Ports: */

void
hw_port_event (struct hw *me,
	       int my_port,
	       int level)
{
  int found_an_edge = 0;
  struct hw_port_edge *edge;
  /* device's lines directly connected */
  for (edge = me->ports_of_hw->edges;
       edge != NULL;
       edge = edge->next)
    {
      if (edge->my_port == my_port)
	{
	  edge->dest->ports_of_hw->to_port_event (edge->dest,
						  edge->dest_port,
						  me,
						  my_port,
						  level);
	  found_an_edge = 1;
	}
    }
  if (!found_an_edge)
    hw_abort (me, "No edge for port %d", my_port);
}


void
hw_port_attach (struct hw *me,
		int my_port,
		struct hw *dest,
		int dest_port,
		object_disposition disposition)
{
  attach_hw_port_edge (me,
		       &me->ports_of_hw->edges,
		       my_port,
		       dest,
		       dest_port,
		       disposition);
}


void
hw_port_detach (struct hw *me,
		int my_port,
		struct hw *dest,
		int dest_port)
{
  detach_hw_port_edge (me,
		       &me->ports_of_hw->edges,
		       my_port,
		       dest,
		       dest_port);
}


void
hw_port_traverse (struct hw *me,
		  hw_port_traverse_function *handler,
		  void *data)
{
  struct hw_port_edge *port_edge;
  for (port_edge = me->ports_of_hw->edges;
       port_edge != NULL;
       port_edge = port_edge->next)
    {
      handler (me, port_edge->my_port,
	       port_edge->dest, port_edge->dest_port,
	       data);
    }
}


int
hw_port_decode (struct hw *me,
		const char *port_name,
		port_direction direction)
{
  if (port_name == NULL || port_name[0] == '\0')
    return 0;
  if (isdigit (port_name[0]))
    {
      return strtoul (port_name, NULL, 0);
    }
  else
    {
      const struct hw_port_descriptor *ports =
	me->ports_of_hw->ports;
      if (ports != NULL)
	{
	  while (ports->name != NULL)
	    {
	      if (ports->direction == bidirect_port
		  || ports->direction == direction)
		{
		  if (ports->nr_ports > 0)
		    {
		      int len = strlen (ports->name);
		      if (strncmp (port_name, ports->name, len) == 0)
			{
			  if (port_name[len] == '\0')
			    return ports->number;
			  else if (isdigit (port_name[len]))
			    {
			      int port = (ports->number
					  + strtoul (&port_name[len], NULL, 0));
			      if (port >= ports->number + ports->nr_ports)
				hw_abort (me,
					  "Port %s out of range",
					  port_name);
			      return port;
			    }
			}
		    }
		  else if (strcmp (port_name, ports->name) == 0)
		    return ports->number;
		}
	      ports++;
	    }
	}
    }
  hw_abort (me, "Unrecognized port %s", port_name);
  return 0;
}


int
hw_port_encode (struct hw *me,
		int port_number,
		char *buf,
		int sizeof_buf,
		port_direction direction)
{
  const struct hw_port_descriptor *ports = NULL;
  ports = me->ports_of_hw->ports;
  if (ports != NULL) {
    while (ports->name != NULL)
      {
	if (ports->direction == bidirect_port
	    || ports->direction == direction)
	  {
	    if (ports->nr_ports > 0)
	      {
		if (port_number >= ports->number
		    && port_number < ports->number + ports->nr_ports)
		  {
		    strcpy (buf, ports->name);
		    sprintf (buf + strlen (buf), "%d", port_number - ports->number);
		    if (strlen (buf) >= sizeof_buf)
		      hw_abort (me, "hw_port_encode: buffer overflow");
		    return strlen (buf);
		  }
	      }
	    else
	      {
		if (ports->number == port_number)
		  {
		    if (strlen (ports->name) >= sizeof_buf)
		      hw_abort (me, "hw_port_encode: buffer overflow");
		    strcpy (buf, ports->name);
		    return strlen (buf);
		  }
	      }
	  }
	ports++;
      }
  }
  sprintf (buf, "%d", port_number);
  if (strlen (buf) >= sizeof_buf)
    hw_abort (me, "hw_port_encode: buffer overflow");
  return strlen (buf);
}