/* $Id: bus-el.c,v 1.18 2009/08/29 17:59:17 fredette Exp $ */

/* generic/bus-el.c - a real generic bus element: */

/*
 * Copyright (c) 2003 Matt Fredette
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt Fredette.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <tme/common.h>
_TME_RCSID("$Id: bus-el.c,v 1.18 2009/08/29 17:59:17 fredette Exp $");

/* includes: */
#define TME_BUS_VERSION TME_X_VERSION(0, 0)
#include <tme/generic/bus.h>
#include <stdlib.h>
#include <string.h>

/* macros: */

/* globals: */

/* the generic bus signals: */
static const struct tme_bus_signals _tme_bus_signals_default[] = {
  TME_BUS_SIGNALS_GENERIC
};

/* this adds a bus signal set to the bus: */
static int
_tme_bus_signals_add(struct tme_bus_connection *conn_bus_caller,
		     struct tme_bus_signals *bus_signals)
{
  struct tme_bus *bus;
  unsigned int signal_i;
  tme_uint32_t signals_count_new;
  tme_uint32_t signals_count_old;
  struct tme_bus_connection_int *conn_bus_int;
  int rc;

  /* recover our bus: */
  bus = conn_bus_caller->tme_bus_connection.tme_connection_element->tme_element_private;

  /* lock the bus for writing: */
  rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK);
  if (TME_THREADS_ERRNO(rc) != TME_OK) {
    return (TME_THREADS_ERRNO(rc));
  }

  /* search for an existing bus signals set that matches the caller's: */
  for (signal_i = 0;
       signal_i < bus->tme_bus_signals_count;
       signal_i++) {

    /* stop if this existing bus signals set has the right ID and the
       versions overlap: */
    if ((bus->tme_bus_signals[signal_i].tme_bus_signals_id
	 == bus_signals->tme_bus_signals_id)
	&& TME_X_VERSION_OK(bus->tme_bus_signals[signal_i].tme_bus_signals_version,
			    bus_signals->tme_bus_signals_version)) {
      break;
    }
  }

  /* assume that this call succeeds: */
  rc = TME_OK;

  /* if an existing bus signals set was not found: */
  if (signal_i == bus->tme_bus_signals_count) {

    /* get the old count of bus signals from the current last bus
       signals set in the bus signals sets array: */
    signals_count_old =
      (TME_BUS_SIGNAL_INDEX(bus->tme_bus_signals[bus->tme_bus_signals_count - 1].tme_bus_signals_first)
       + bus->tme_bus_signals[bus->tme_bus_signals_count - 1].tme_bus_signals_count);

    /* resize the bus signals sets array: */
    bus->tme_bus_signals
      = tme_renew(struct tme_bus_signals,
		  bus->tme_bus_signals,
		  bus->tme_bus_signals_count
		  + 1);

    /* add the new bus signals set: */
    signals_count_new = signals_count_old + bus_signals->tme_bus_signals_count;
    assert (signals_count_new > signals_count_old);
    bus_signals->tme_bus_signals_first = TME_BUS_SIGNAL_X(signals_count_old);
    bus->tme_bus_signals[bus->tme_bus_signals_count] = *bus_signals;
    bus->tme_bus_signals_count++;

    /* reallocate the bus' asserted-signals count array: */
    bus->tme_bus_signal_asserts
      = tme_renew(unsigned int,
		  bus->tme_bus_signal_asserts,
		  signals_count_new);
    memset ((char *) &bus->tme_bus_signal_asserts[signals_count_old],
	    0,
	    (sizeof(bus->tme_bus_signal_asserts[0])
	     * (signals_count_new
		- signals_count_old)));

    /* if needed, reallocate each connection's asserted-signals
       bitmap: */
    if (TME_BUS_SIGNAL_BIT_BYTES(signals_count_new)
	> TME_BUS_SIGNAL_BIT_BYTES(signals_count_old)) {
      for (conn_bus_int = bus->tme_bus_connections;
	   conn_bus_int != NULL;
	   conn_bus_int =
	     (struct tme_bus_connection_int *) 
	     conn_bus_int->tme_bus_connection_int
	     .tme_bus_connection
	     .tme_connection_next) {
	conn_bus_int->tme_bus_connection_int_signals
	  = tme_renew(tme_uint8_t,
		      conn_bus_int->tme_bus_connection_int_signals,
		      TME_BUS_SIGNAL_BIT_BYTES(signals_count_new));
	memset ((char *) &conn_bus_int->tme_bus_connection_int_signals[TME_BUS_SIGNAL_BIT_BYTES(signals_count_old)],
		0,
		(sizeof (conn_bus_int->tme_bus_connection_int_signals[0])
		 * (TME_BUS_SIGNAL_BIT_BYTES(signals_count_new)
		    - TME_BUS_SIGNAL_BIT_BYTES(signals_count_old))));
      }
    }
  }

  /* otherwise, we found an existing bus signals set.  however, even
     though the versions overlap, if they don't support the same least
     version, something is wrong: */
  else if ((TME_X_VERSION_CURRENT(bus->tme_bus_signals[signal_i].tme_bus_signals_version)
	    - TME_X_VERSION_AGE(bus->tme_bus_signals[signal_i].tme_bus_signals_version))
	   != (TME_X_VERSION_CURRENT(bus_signals->tme_bus_signals_version)
	       - TME_X_VERSION_AGE(bus_signals->tme_bus_signals_version))) {
    rc = EINVAL;
  }

  /* otherwise, we found an existing bus signals set that fully matches
     and is compatible with the caller's: */
  else {
    
    /* update the versioning on this bus signals set: */
    if (TME_X_VERSION_CURRENT(bus_signals->tme_bus_signals_version)
	> TME_X_VERSION_CURRENT(bus->tme_bus_signals[signal_i].tme_bus_signals_version)) {
      bus->tme_bus_signals[signal_i].tme_bus_signals_version = bus_signals->tme_bus_signals_version;
    }

    /* return the existing bus signals set: */
    *bus_signals = bus->tme_bus_signals[signal_i];
  }
    
  /* unlock the bus and return: */
  tme_rwlock_wrunlock(&bus->tme_bus_rwlock);
  return (rc);
}

/* this handles a bus connection signal edge: */
static int
_tme_bus_signal(struct tme_bus_connection *conn_bus_edger, unsigned int signal)
{
  struct tme_bus *bus;
  struct tme_bus_connection_int *conn_bus_int_edger;
  struct tme_bus_connection_int *conn_bus_int;
  unsigned int level_edge;
  struct tme_bus_connection *conn_bus;
  struct tme_bus_connection *conn_bus_other;
  int signal_asserted, need_propagate;
  unsigned int signal_index;
  tme_uint8_t signal_mask;
  int rc;
  int deadlocked;

  /* recover our bus: */
  bus = conn_bus_edger->tme_bus_connection.tme_connection_element->tme_element_private;
  conn_bus_int_edger = (struct tme_bus_connection_int *) conn_bus_edger;

  /* take out the level and edge: */
  level_edge = signal;
  signal = TME_BUS_SIGNAL_WHICH(signal);
  level_edge ^= signal;

  /* lock the bus for writing: */
  rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK);
  if (TME_THREADS_ERRNO(rc) != TME_OK) {
    return (TME_THREADS_ERRNO(rc));
  }
  
  /* if this device doesn't know its interrupt signal, fix it: */
  if (signal == TME_BUS_SIGNAL_INT_UNSPEC) {
    signal = conn_bus_int_edger->tme_bus_connection_int_signal_int;
    if (signal == TME_BUS_SIGNAL_INT_UNSPEC) {
      /* this bus connection is misconfigured: */
      if (!conn_bus_int_edger->tme_bus_connection_int_logged_int) {
	conn_bus_int_edger->tme_bus_connection_int_logged_int = TRUE;
	/* XXX diagnostic */
	abort();
      }
      tme_rwlock_wrunlock(&bus->tme_bus_rwlock);
      return (TME_OK);
    }
  }

  /* assume we don't need to propagate this signal across the bus: */
  need_propagate = FALSE;

  /* see whether the device is asserting or negating this signal.  iff
     one or more devices on a bus are asserting a signal, the signal
     appears asserted on the bus.  this gives an ORed effect: */
  signal_asserted = TRUE;
  switch (level_edge & TME_BUS_SIGNAL_LEVEL_MASK) {
  case TME_BUS_SIGNAL_LEVEL_NEGATED:
    signal_asserted = FALSE;
  case TME_BUS_SIGNAL_LEVEL_ASSERTED:
    break;
  default:
    abort();
  }

  /* get the index and mask of this signal in signal byte arrays: */
  signal_index = TME_BUS_SIGNAL_BIT_INDEX(signal);
  signal_mask = TME_BUS_SIGNAL_BIT_MASK(signal);

  /* if this signal is being asserted: */
  if (signal_asserted) {

    /* if this device wasn't already asserting this signal: */
    if (!(conn_bus_int_edger->tme_bus_connection_int_signals[signal_index]
	  & signal_mask)) {

      /* it is now asserting this signal: */
      conn_bus_int_edger->tme_bus_connection_int_signals[signal_index]
	|= signal_mask;
      bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)]++;

      /* if this is the only device asserting this signal,
	 propagate the change across the bus: */
      if (bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] == 1) {
	need_propagate = TRUE;
      }
    }

    /* otherwise, this device was already asserting this signal: */
    else {
      assert(bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] > 0);
    }
  }

  /* otherwise, this signal is being negated: */
  else {

    /* if this device was asserting this signal: */
    if (conn_bus_int_edger->tme_bus_connection_int_signals[signal_index]
	& signal_mask) {

      /* it is no longer asserting this signal: */
      conn_bus_int_edger->tme_bus_connection_int_signals[signal_index]
	&= ~signal_mask;
      assert(bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] > 0);
      bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)]--;

      /* if this was the last device asserting this signal, propagate
	 the change across the bus: */
      if (bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] == 0) {
	need_propagate = TRUE;
      }
    }

    /* otherwise, this device was not asserting this signal, but it
       negated it anyways.  often, lazy code will only send negating
       edges for signals (for example, see the do_reset code in
       machine/sun2/sun2-mainbus.c, which should really assert RESET,
       sleep, then negate it), so if we get a negated edge for a
       signal that no one else (including the edger) was asserting, we
       propagate the edge anyways.

       so, TME_BUS_SIGNAL_EDGE should only be used for this purpose: */
    else if ((level_edge & TME_BUS_SIGNAL_EDGE)
	     && bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] == 0) {
      need_propagate = TRUE;
    }
  }

  /* if we're propagating this signal across the bus: */
  rc = TME_OK;
  if (need_propagate) {

    /* put the level and edge back in: */
    signal |= level_edge;

    /* assume that we won't deadlock: */
    deadlocked = FALSE;

    /* propagate the signal to each connection to the bus: */
    for (conn_bus_int = bus->tme_bus_connections;
	 conn_bus_int != NULL;
	 conn_bus_int =
	   (struct tme_bus_connection_int *) 
	   conn_bus_int->tme_bus_connection_int
	   .tme_bus_connection
	   .tme_connection_next) {
      conn_bus = &conn_bus_int->tme_bus_connection_int;
      conn_bus_other = 
	(struct tme_bus_connection *) 
	conn_bus->tme_bus_connection.tme_connection_other;
      
      /* skip this device if it edged the line to begin with: */
      if (conn_bus == conn_bus_edger) {
	continue;
      }

      /* skip this device if it doesn't care about bus signals: */
      if (conn_bus_other->tme_bus_signal == NULL) {
	continue;
      }

      /* give the edge to this connection: */
      rc =  (*conn_bus_other->tme_bus_signal)(conn_bus_other, signal);

      /* if we deadlocked, remember to tell the caller: */
      if (rc == TME_EDEADLK) {
	deadlocked = TRUE;
      }
    }
    rc = (deadlocked ? TME_EDEADLK : TME_OK);
  }

  /* unlock the bus: */
  tme_rwlock_wrunlock(&bus->tme_bus_rwlock);

  /* done: */
  return (rc);
}
			      
/* this handles a bus interrupt acknowledge: */
static int
_tme_bus_intack(struct tme_bus_connection *conn_bus_acker, unsigned int signal, int *vector)
{
  struct tme_bus *bus;
  struct tme_bus_connection_int *conn_bus_int;
  struct tme_bus_connection *conn_bus;
  struct tme_bus_connection *conn_bus_other;
  unsigned int signal_index;
  tme_uint8_t signal_mask;
  int rc;

  /* recover our bus: */
  bus = conn_bus_acker->tme_bus_connection.tme_connection_element->tme_element_private;

  /* get rid of any level and edge: */
  signal = TME_BUS_SIGNAL_WHICH(signal);

  /* this must be an interrupt signal: */
  assert(TME_BUS_SIGNAL_IS_INT(signal));

  /* lock the bus for writing: */
  rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK);
  if (TME_THREADS_ERRNO(rc) != TME_OK) {
    return (TME_THREADS_ERRNO(rc));
  }
  
  /* get the index and mask of this signal in signal byte arrays: */
  signal_index = TME_BUS_SIGNAL_BIT_INDEX(signal);
  signal_mask = TME_BUS_SIGNAL_BIT_MASK(signal);

  /* find the first connection to the bus that is asserting this
     interrupt signal.  if no connection is asserting the signal,
     return ENOENT: */
  rc = ENOENT;
  for (conn_bus_int = bus->tme_bus_connections;
       conn_bus_int != NULL;
       conn_bus_int =
	 (struct tme_bus_connection_int *) 
	 conn_bus_int->tme_bus_connection_int
	 .tme_bus_connection
	 .tme_connection_next) {
    conn_bus = &conn_bus_int->tme_bus_connection_int;
    conn_bus_other = 
      (struct tme_bus_connection *) 
      conn_bus->tme_bus_connection.tme_connection_other;
    
    /* if this device is asserting this interrupt signal: */
    if (conn_bus_int->tme_bus_connection_int_signals[signal_index]
	& signal_mask) {

      /* unlock the bus: */
      tme_rwlock_wrunlock(&bus->tme_bus_rwlock);

      /* if this device doesn't acknowledge interrupts, return any
	 user-specified vector or TME_BUS_INTERRUPT_VECTOR_UNDEF: */
      if (conn_bus_other->tme_bus_intack == NULL) {
	*vector = conn_bus_int->tme_bus_connection_int_vector_int;
	rc = TME_OK;
      }

      /* otherwise, run the interrupt acknowledge with this connection: */
      else {
	rc = (*conn_bus_other->tme_bus_intack)(conn_bus_other, signal, vector);
      }

      /* done: */
      return (rc);
    }
  }

  /* unlock the bus: */
  tme_rwlock_wrunlock(&bus->tme_bus_rwlock);

  /* done: */
  return (rc);
}

static int
_tme_bus_fault(void *junk0, struct tme_bus_cycle *junk1)
{
  return (ENOENT);
}

/* this fills a TLB entry: */
static int
_tme_bus_tlb_fill(struct tme_bus_connection *conn_bus_asker, 
		  struct tme_bus_tlb *tlb,
		  tme_bus_addr_t address, 
		  unsigned int cycles)
{
  struct tme_bus *bus;
  struct tme_bus_connection_int *conn_int;
  int rc;

  /* recover our bus and our connection to the asker: */
  bus = conn_bus_asker->tme_bus_connection.tme_connection_element->tme_element_private;
  conn_int = (struct tme_bus_connection_int *) conn_bus_asker;

  /* put our fault handler in the TLB entry: */
  tlb->tme_bus_tlb_cycle_private = NULL;
  tlb->tme_bus_tlb_cycle = _tme_bus_fault;

  /* lock the bus for reading: */
  rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK);
  if (TME_THREADS_ERRNO(rc) != TME_OK) {
    return (TME_THREADS_ERRNO(rc));
  }

  /* call the generic bus support function: */
  rc = tme_bus_tlb_fill(bus,
			conn_int,
			tlb, address, cycles);

  /* unlock the bus: */
  tme_rwlock_wrunlock(&bus->tme_bus_rwlock);

  /* done: */
  return (rc);
}

/* this adds a new TLB set: */
static int
_tme_bus_tlb_set_add(struct tme_bus_connection *conn_bus_asker,
		     struct tme_bus_tlb_set_info *tlb_set_info)
{
  struct tme_bus *bus;
  struct tme_bus_connection_int *conn_int;
  int rc;

  /* recover our bus and our connection to the asker: */
  bus = conn_bus_asker->tme_bus_connection.tme_connection_element->tme_element_private;
  conn_int = (struct tme_bus_connection_int *) conn_bus_asker;

  /* lock the bus for reading: */
  rc = tme_rwlock_timedrdlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK);
  if (TME_THREADS_ERRNO(rc) != TME_OK) {
    return (TME_THREADS_ERRNO(rc));
  }

  /* call the generic bus support function: */
  rc = tme_bus_tlb_set_add(bus,
			   conn_int,
			   tlb_set_info);

  /* unlock the bus: */
  tme_rwlock_rdunlock(&bus->tme_bus_rwlock);

  /* done: */
  return (rc);
}

/* this scores a new connection: */
static int
_tme_bus_connection_score(struct tme_connection *conn, unsigned int *_score)
{
  struct tme_bus *bus;
  struct tme_bus_connection_int *conn_int;
  int rc, ok;

  /* both sides must be generic bus connections: */
  assert(conn->tme_connection_type == TME_CONNECTION_BUS_GENERIC);
  assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_BUS_GENERIC);

  /* recover our bus and our internal connection side: */
  bus = conn->tme_connection_element->tme_element_private;
  conn_int = (struct tme_bus_connection_int *) conn;

  /* lock the bus for reading: */
  rc = tme_rwlock_timedrdlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK);
  if (TME_THREADS_ERRNO(rc) != TME_OK) {
    return (TME_THREADS_ERRNO(rc));
  }
  
  /* call the generic bus support function: */
  ok = tme_bus_connection_ok(bus,
			     conn_int);

  /* unlock the bus: */
  tme_rwlock_rdunlock(&bus->tme_bus_rwlock);

  /* return the score: */
  *_score = (ok ? 1 : 0);
  return (TME_OK);
}

/* this makes a new connection: */
static int
_tme_bus_connection_make(struct tme_connection *conn, unsigned int state)
{
  struct tme_bus *bus;
  struct tme_bus_connection_int *conn_int;
  const struct tme_bus_signals *bus_signals;
  int rc;

  /* both sides must be generic bus connections: */
  assert(conn->tme_connection_type == TME_CONNECTION_BUS_GENERIC);
  assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_BUS_GENERIC);

  /* recover our bus and our internal connection side: */
  bus = conn->tme_connection_element->tme_element_private;
  conn_int = (struct tme_bus_connection_int *) conn;
  
  /* lock the bus for writing: */
  rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK);
  if (TME_THREADS_ERRNO(rc) != TME_OK) {
    return (TME_THREADS_ERRNO(rc));
  }

  /* call the generic bus support function: */
  rc = tme_bus_connection_make(bus,
			       conn_int,
			       state);

  /* XXX this is a perfect example of the poor division between
     bus-el.c and bus.c.  should the signal handling code be in bus.c?  */
  if (rc == TME_OK) {
    bus_signals = &bus->tme_bus_signals[bus->tme_bus_signals_count - 1];
    conn_int->tme_bus_connection_int_signals
      = tme_new0(tme_uint8_t,
		 TME_BUS_SIGNAL_BIT_BYTES(TME_BUS_SIGNAL_INDEX(bus_signals->tme_bus_signals_first)
					  + bus_signals->tme_bus_signals_count));
  }

  /* unlock the bus: */
  tme_rwlock_wrunlock(&bus->tme_bus_rwlock);

  return (rc);
}

/* this breaks a connection: */
static int 
_tme_bus_connection_break(struct tme_connection *conn, unsigned int state)
{
  abort();
}

/* this returns the new connections possible: */
static int
_tme_bus_connections_new(struct tme_element *element,
			 const char * const *args,
			 struct tme_connection **_conns,
			 char **_output)
{
  const struct tme_bus *bus;
  struct tme_bus_connection_int *conn_int;
  struct tme_bus_connection *conn_bus;
  struct tme_connection *conn;
  int ipl;
  int vector;
  const struct tme_bus_slot *bus_slot;
  int arg_i;
  int usage;

  /* recover our bus.  we only read the address mask, so we don't lock
     the rwlock: */
  bus = element->tme_element_private;

  /* allocate the new connection side: */
  conn_int = tme_new0(struct tme_bus_connection_int, 1);
  conn_bus = &conn_int->tme_bus_connection_int;
  conn = &conn_bus->tme_bus_connection;

  /* loop reading our arguments: */
  usage = FALSE;
  arg_i = 1;
  conn_int->tme_bus_connection_int_vector_int = TME_BUS_INTERRUPT_VECTOR_UNDEF;
  bus_slot = NULL;
  for (;;) {

    /* the address of this connection: */
    if (TME_ARG_IS(args[arg_i + 0], "addr")) {
      conn_int->tme_bus_connection_int_flags |= TME_BUS_CONNECTION_INT_FLAG_ADDRESSABLE;
      conn_int->tme_bus_connection_int_address = tme_bus_addr_parse_any(args[arg_i + 1], &usage);
      if (usage
	  || (conn_int->tme_bus_connection_int_address
	      > bus->tme_bus_address_mask)) {
	usage = TRUE;
	break;
      }
      arg_i += 2;
    }

    /* the interrupt signal for this connection: */
    else if (TME_ARG_IS(args[arg_i + 0], "ipl")
	     && args[arg_i + 1] != NULL
	     && (ipl = atoi(args[arg_i + 1])) > 0) {
      conn_int->tme_bus_connection_int_signal_int = TME_BUS_SIGNAL_INT(ipl);
      arg_i += 2;
    }

    /* the interrupt vector for this connection: */
    else if (TME_ARG_IS(args[arg_i + 0], "vector")
	     && args[arg_i + 1] != NULL
	     && (vector = atoi(args[arg_i + 1])) > 0) {
      conn_int->tme_bus_connection_int_vector_int = vector;
      arg_i += 2;
    }      

    /* the slot for this connection: */
    else if (TME_ARG_IS(args[arg_i + 0], "slot")
	     && args[arg_i + 1] != NULL) {

      /* you can't give more than one slot for a connection: */
      if (bus_slot != NULL) {
	tme_output_append_error(_output,
				"slot %s %s, ",
				args[arg_i + 1],
				_("redefined"));
	usage = TRUE;
	break;
      }

      /* make sure this slot has been defined: */
      for (bus_slot = bus->tme_bus_slots;
	   bus_slot != NULL;
	   bus_slot = bus_slot->tme_bus_slot_next) {
	if (strcmp(bus_slot->tme_bus_slot_name,
		   args[arg_i + 1]) == 0) {
	  break;
	}
      }
      if (bus_slot == NULL) {
	tme_output_append_error(_output,
				"slot %s %s, ",
				args[arg_i + 1],
				_("unknown"));
	usage = TRUE;
	break;
      }
      arg_i += 2;
    }

    /* the slot offset for this connection: */
    else if (TME_ARG_IS(args[arg_i + 0], "offset")) {
      if (bus_slot == NULL) {
	tme_output_append_error(_output,
				"slot %s, ",
				_("unknown"));
	usage = TRUE;
	break;
      }
      conn_int->tme_bus_connection_int_flags |= TME_BUS_CONNECTION_INT_FLAG_ADDRESSABLE;
      conn_int->tme_bus_connection_int_address
	= (bus_slot->tme_bus_slot_address
	   + tme_bus_addr_parse_any(args[arg_i + 1], &usage));
      if (usage
	  || (conn_int->tme_bus_connection_int_address
	      > bus->tme_bus_address_mask)) {
	usage = TRUE;
	break;
      }
      arg_i += 2;
    }

    /* if this connection is for a slot controller: */
    else if (TME_ARG_IS(args[arg_i + 0], "controller")) {
      if (bus->tme_bus_controller != NULL) {
	tme_free(conn_int);
	return (EEXIST);
      }
      conn_int->tme_bus_connection_int_flags |= TME_BUS_CONNECTION_INT_FLAG_CONTROLLER;
      arg_i++;
    }

    /* if this connection has an automatic DMA offset: */
    else if (TME_ARG_IS(args[arg_i + 0], "dma-offset")) {
      conn_int->tme_bus_connection_int_sourced = tme_bus_addr_parse_any(args[arg_i + 1], &usage);
      if (usage
	  || (conn_int->tme_bus_connection_int_sourced
	      > bus->tme_bus_address_mask)) {
	usage = TRUE;
	break;
      }
      arg_i += 2;
    }

    /* if we've run out of arguments: */
    else if (args[arg_i + 0] == NULL) {
      break;
    }

    /* this is a bad argument: */
    else {
      tme_output_append_error(_output,
			      "%s %s, ",
			      args[arg_i],
			      _("unexpected"));
      usage = TRUE;
      break;
    }
  }

  if (usage) {
    tme_output_append_error(_output, 
			    "%s %s [ controller ] [ addr %s ] [ slot %s offset %s ] [ dma-offset %s ] [ ipl %s ] [ vector %s ]",
			    _("usage:"),
			    args[0],
			    _("BUS-ADDRESS"),
			    _("SLOT"),
			    _("OFFSET"),
			    _("OFFSET"),
			    _("INTERRUPT-LEVEL"),
			    _("INTERRUPT-VECTOR"));
    tme_free(conn_int);
    return (EINVAL);
  }

  /* fill in the bus connection: */
  conn_bus->tme_bus_subregions.tme_bus_subregion_address_first
    = 0;
  conn_bus->tme_bus_subregions.tme_bus_subregion_address_last
    = bus->tme_bus_address_mask;
  conn_bus->tme_bus_subregions.tme_bus_subregion_next
    = NULL;
  conn_bus->tme_bus_signals_add = _tme_bus_signals_add;
  conn_bus->tme_bus_signal = _tme_bus_signal;
  conn_bus->tme_bus_intack = _tme_bus_intack;
  conn_bus->tme_bus_tlb_set_add = _tme_bus_tlb_set_add;
  conn_bus->tme_bus_tlb_fill = _tme_bus_tlb_fill;

  /* fill in the generic connection: */
  conn->tme_connection_next = *_conns;
  conn->tme_connection_type = TME_CONNECTION_BUS_GENERIC;
  conn->tme_connection_score = _tme_bus_connection_score;
  conn->tme_connection_make = _tme_bus_connection_make;
  conn->tme_connection_break = _tme_bus_connection_break;
  
  /* return the new connection side: */
  *_conns = conn;
  return (TME_OK);
}

/* this creates a new bus element: */
TME_ELEMENT_SUB_NEW_DECL(tme_generic,bus) {
  struct tme_bus *bus;
  tme_bus_addr_t bus_size_mask;
  tme_bus_addr_t bus_slot_size;
  tme_bus_addr_t bus_slot_addr;
  int bus_slot_addr_defined;
  struct tme_bus_slot *bus_slot;
  struct tme_bus_slot *bus_slots;
  int arg_i;
  int failed;

  /* our arguments must include the bus size, and the
     bus size must be a power of two: */
  failed = FALSE;
  arg_i = 1;
  bus_size_mask = 0;
  bus_slot_size = 0;
  bus_slot_addr_defined = FALSE;
  bus_slot_addr = 0;
  bus_slots = NULL;
  for (; !failed; ) {

    /* the bus size: */
    if (TME_ARG_IS(args[arg_i + 0], "size")) {
      /* XXX FIXME - this is a hack: */
      if (sizeof(bus_size_mask) == sizeof(tme_uint32_t) &&
	  TME_ARG_IS(args[arg_i + 1], "4GB")) {
	bus_size_mask = ((tme_bus_addr_t) 0) - 1;
      }
      else {
	bus_size_mask = tme_bus_addr_parse_any(args[arg_i + 1], &failed);
	if (!failed
	    && bus_size_mask < 2) {
	  failed = TRUE;
	}
	else {
	  bus_size_mask -= 1;
	}
      }
      if (bus_size_mask & (bus_size_mask + 1)) {
	failed = TRUE;
      }
      arg_i += 2;
    }

    /* the address for the next slots: */
    else if (TME_ARG_IS(args[arg_i + 0], "slot-addr")) {
      bus_slot_addr = tme_bus_addr_parse_any(args[arg_i + 1], &failed);
      bus_slot_addr_defined = TRUE;
      arg_i += 2;
    }

    /* the size for the next slots: */
    else if (TME_ARG_IS(args[arg_i + 0], "slot-size")) {
      bus_slot_size = tme_bus_addr_parse_any(args[arg_i + 1], &failed);
      if (bus_slot_size < 1) {
	failed = TRUE;
      }
      arg_i += 2;
    }

    /* a slot definition: */
    else if (TME_ARG_IS(args[arg_i + 0], "slot")) {
      if (args[arg_i + 1] == NULL) {
	failed = TRUE;
	break;
      }
      if (!bus_slot_addr_defined) {
	failed = TRUE;
	break;
      }
      if (bus_slot_size == 0) {
	failed = TRUE;
	break;
      }

      /* make sure this slot hasn't already been defined: */
      for (bus_slot = bus_slots;
	   bus_slot != NULL;
	   bus_slot = bus_slot->tme_bus_slot_next) {
	if (strcmp(bus_slot->tme_bus_slot_name,
		   args[arg_i + 1]) == 0) {
	  tme_output_append_error(_output,
				  "slot %s %s",
				  args[arg_i + 1],
				  _("redefined"));
	  failed = TRUE;
	  break;
	}
      }
      if (failed) {
	break;
      }

      /* add this slot: */
      bus_slot = tme_new0(struct tme_bus_slot, 1);
      bus_slot->tme_bus_slot_next = bus_slots;
      bus_slots = bus_slot;
      bus_slot->tme_bus_slot_name = tme_strdup(args[arg_i + 1]);
      bus_slot->tme_bus_slot_address = bus_slot_addr;
      bus_slot->tme_bus_slot_size = bus_slot_size;

      /* advance for the next slot: */
      bus_slot_addr += bus_slot_size;
      arg_i += 2;
    }

    /* if we've run out of arguments: */
    else if (args[arg_i + 0] == NULL) {
      break;
    }

    /* an unknown argument: */
    else {
      tme_output_append_error(_output,
			      "%s %s, ",
			      args[arg_i],
			      _("unexpected"));
      failed = TRUE;
    }
  }
  if (failed) {
    tme_output_append_error(_output,
			    "%s %s size %s [ slot-addr %s slot-size %s slot %s0 .. slot %sN ]",
			    _("usage:"),
			    args[0],
			    _("SIZE"),
			    _("ADDRESS"),
			    _("SIZE"),
			    _("SLOT-NAME"),
			    _("SLOT-NAME"));
    for (; (bus_slot = bus_slots) != NULL; ) {
      bus_slots = bus_slots->tme_bus_slot_next;
      tme_free(bus_slot->tme_bus_slot_name);
      tme_free(bus_slot);
    }
    return (EINVAL);
  }

  /* allocate and initialize the new bus: */
  bus = tme_new0(struct tme_bus, 1);
  tme_rwlock_init(&bus->tme_bus_rwlock);
  bus->tme_bus_address_mask = bus_size_mask;
  bus->tme_bus_addressables_count = 0;
  bus->tme_bus_addressables_size = 1;
  bus->tme_bus_addressables = tme_new(struct tme_bus_addressable,
				      bus->tme_bus_addressables_size);
  bus->tme_bus_signals_count = TME_ARRAY_ELS(_tme_bus_signals_default);
  bus->tme_bus_signals = tme_dup(struct tme_bus_signals,
				 _tme_bus_signals_default,
				 TME_ARRAY_ELS(_tme_bus_signals_default));
  bus->tme_bus_signal_asserts = tme_new0(unsigned int,
					 _tme_bus_signals_default[0].tme_bus_signals_count);
  bus->tme_bus_slots = bus_slots;

  /* fill the element: */
  element->tme_element_private = bus;
  element->tme_element_connections_new = _tme_bus_connections_new;

  return (TME_OK);
}
