/* $Id: sun4-mainbus.c,v 1.6 2009/08/30 14:03:11 fredette Exp $ */

/* machine/sun4/sun4-mainbus.c - implementation of Sun 4 emulation: */

/*
 * Copyright (c) 2003, 2004, 2006 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: sun4-mainbus.c,v 1.6 2009/08/30 14:03:11 fredette Exp $");

/* includes: */
#include "sun4-impl.h"
#include <tme/ic/z8530.h>
#include <tme/ic/isil7170.h>
#include <tme/ic/mk48txx.h>
#include <stdio.h>

/* this possibly updates that the interrupt priority level driven to the CPU: */
int
_tme_sun4_ipl_check(struct tme_sun4 *sun4)
{
  unsigned int ipl;
  unsigned int ipl_index;
  tme_uint8_t ipl_mask;
  tme_uint32_t interreg;
  int rc;

  /* find the highest ipl now asserted on the buses, that is enabled
     to the CPU: */
  ipl = TME_SPARC_IPL_MAX;
  ipl_index = ipl / 8;
  ipl_mask = TME_BIT(ipl % 8);
  do {

    /* if this ipl is asserted: */
    if ((sun4->tme_sun4_int_signals[ipl_index] & ipl_mask) != 0) {

      /* if this ipl is not disabled: */
      if (TME_SUN4_IS_SUN44C(sun4)
	  ? ((sun4->tme_sun44c_ints & TME_SUN44C_IREG_INTS_ENAB)
	     && (ipl != 10 || (sun4->tme_sun44c_ints & TME_SUN44C_IREG_COUNTER_L10))
	     && (ipl != 14 || (sun4->tme_sun44c_ints & TME_SUN44C_IREG_COUNTER_L14)))
	  : TRUE) {

	/* stop now: */
	break;
      }
    }

    /* advance to the next ipl: */
    ipl--;
    ipl_mask >>= 1;
    if (ipl_mask == 0) {
      ipl_mask = 0x80;
      ipl_index--;
    }
  } while (ipl != TME_SPARC_IPL_NONE);

  /* if this is a sun4 or sun4c: */
  if (TME_SUN4_IS_SUN44C(sun4)) {
    
    /* get the sun4/4c interrupt register: */
    interreg = sun4->tme_sun44c_ints;

    /* if interrupts are enabled: */
    if (interreg & TME_SUN44C_IREG_INTS_ENAB) {

      /* check the soft interrupts: */
      if (interreg & TME_SUN44C_IREG_SOFT_INT_L6) {
	ipl = TME_MAX(ipl, 6);
      }
      else if (interreg & TME_SUN44C_IREG_SOFT_INT_L4) {
	ipl = TME_MAX(ipl, 4);
      }
      else if (interreg & TME_SUN44C_IREG_SOFT_INT_L1) {
	ipl = TME_MAX(ipl, 1);
      }
    }
  }

  /* possibly update the CPU: */
  if (ipl != sun4->tme_sun4_int_ipl_last) {
    sun4->tme_sun4_int_ipl_last = ipl;
    rc = ((*sun4->tme_sun4_sparc->tme_sparc_bus_interrupt)
	  (sun4->tme_sun4_sparc, ipl));
    assert (rc == TME_OK);
  }

  /* return nonzero if any interrupt is pending: */
  return (ipl != TME_SPARC_IPL_NONE);
}

/* our mainbus signal handler: */
static int
_tme_sun4_bus_signal(struct tme_bus_connection *conn_bus_raiser, unsigned int signal)
{
  struct tme_sun4 *sun4;
  int signal_asserted;
  unsigned int ipl, ipl_index;
  tme_uint8_t ipl_mask;

  /* recover our sun4: */
  sun4 = (struct tme_sun4 *) conn_bus_raiser->tme_bus_connection.tme_connection_element->tme_element_private;

  /* see whether the signal is asserted or negated: */
  signal_asserted = TRUE;
  switch (signal & TME_BUS_SIGNAL_LEVEL_MASK) {
  case TME_BUS_SIGNAL_LEVEL_NEGATED:
    signal_asserted = FALSE;
  case TME_BUS_SIGNAL_LEVEL_ASSERTED:
    break;
  default:
    abort();
  }
  signal = TME_BUS_SIGNAL_WHICH(signal);

  /* dispatch on the signal: */

  /* halt: */
  if (signal == TME_BUS_SIGNAL_HALT) {
    abort();
  }

  /* reset: */
  else if (signal == TME_BUS_SIGNAL_RESET) {
    /* XXX reset is just ignored for now: */
  }

  /* an interrupt signal: */
  else if (TME_BUS_SIGNAL_IS_INT(signal)) {
    ipl = TME_BUS_SIGNAL_INDEX_INT(signal);
    if (ipl >= TME_SPARC_IPL_MIN
	&& ipl <= TME_SPARC_IPL_MAX) {
      
      /* update this ipl in the byte array: */
      ipl_index = ipl / 8;
      ipl_mask = TME_BIT(ipl % 8);
      sun4->tme_sun4_int_signals[ipl_index]
	= ((sun4->tme_sun4_int_signals[ipl_index]
	    & ~ipl_mask)
	   | (signal_asserted
	      ? ipl_mask
	      : 0));

      /* possibly update the ipl being driven to the CPU: */
      _tme_sun4_ipl_check(sun4);
      return (TME_OK);
    }
  }

  /* an unknown signal: */
  else {
    abort();
  }

  return (TME_OK);
}

/* this handles a CPU interrupt acknowledge: */
static int
_tme_sun4_bus_intack(struct tme_bus_connection *conn_sparc, unsigned int ipl, int *vector)
{
  struct tme_sun4 *sun4;
  tme_uint32_t interreg;
  unsigned int signal;
  int rc;

  /* recover our sun4: */
  sun4 = (struct tme_sun4 *) conn_sparc->tme_bus_connection.tme_connection_element->tme_element_private;

  /* acknowledge any soft interrupt: */
  if (TME_SUN4_IS_SUN44C(sun4)) {
    interreg = sun4->tme_sun44c_ints;
    if ((ipl == 6
	 && (interreg & TME_SUN44C_IREG_SOFT_INT_L6))
	|| (ipl == 4
	    && (interreg & TME_SUN44C_IREG_SOFT_INT_L4))
	|| (ipl == 1
	    && (interreg & TME_SUN44C_IREG_SOFT_INT_L1))) {
      *vector = TME_BUS_INTERRUPT_VECTOR_UNDEF;
      return (TME_OK);
    }
  }

  /* turn the ipl into a bus signal number: */
  signal = TME_BUS_SIGNAL_INT(ipl);

  /* try the acknowledge on these buses, in order: */

  /* obio: */
  rc = (*sun4->tme_sun4_32_obio->tme_bus_intack)
    (sun4->tme_sun4_32_obio, signal, vector);
  if (rc != ENOENT) {
    return (rc);
  }
  /* obmem: */
  if (!TME_SUN4_IS_SUN4C(sun4)) {
    rc = (*sun4->tme_sun4_32_obmem->tme_bus_intack)
      (sun4->tme_sun4_32_obmem, signal, vector);
    if (rc != ENOENT) {
      return (rc);
    }
  }
  /* VMEbus: */
  if (TME_SUN4_IS_SUN4(sun4)) {
    rc = (*sun4->tme_sun4_vmebus->tme_bus_intack)
      (sun4->tme_sun4_vmebus, signal, vector);
    if (rc != ENOENT) {
      return (rc);
    }
  }

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

/* this resets the board: */
int
_tme_sun4_reset(struct tme_sun4 *sun4, int soft)
{

  /* clear the sun4/4c enable register: */
  sun4->tme_sun44c_enable = 0;
  
  /* clear the sun4/4c interrupt register: */
  sun4->tme_sun44c_ints = 0;

  /* clear any NMI: */
  sun4->tme_sun4_int_signals[TME_SPARC_IPL_NMI / 8] &= ~TME_BIT(TME_SPARC_IPL_NMI % 8);

  /* reset the CPU: */
  (*sun4->tme_sun4_sparc->tme_sparc_bus_connection.tme_bus_signal)
    (&sun4->tme_sun4_sparc->tme_sparc_bus_connection,
     TME_BUS_SIGNAL_RESET
     | TME_BUS_SIGNAL_LEVEL_NEGATED
     | TME_BUS_SIGNAL_EDGE);

  /* reset all busses: */
  (*sun4->tme_sun4_32_obio->tme_bus_signal)
    (sun4->tme_sun4_32_obio,
     TME_BUS_SIGNAL_RESET
     | TME_BUS_SIGNAL_LEVEL_NEGATED
     | TME_BUS_SIGNAL_EDGE);
  if (!TME_SUN4_IS_SUN4C(sun4)) {
    (*sun4->tme_sun4_32_obmem->tme_bus_signal)
      (sun4->tme_sun4_32_obmem,
       TME_BUS_SIGNAL_RESET
       | TME_BUS_SIGNAL_LEVEL_NEGATED
       | TME_BUS_SIGNAL_EDGE);
  }
  if (TME_SUN4_IS_SUN4(sun4)) {
    (*sun4->tme_sun4_vmebus->tme_bus_signal)
      (sun4->tme_sun4_vmebus,
       TME_BUS_SIGNAL_RESET
       | TME_BUS_SIGNAL_LEVEL_NEGATED
       | TME_BUS_SIGNAL_EDGE);
  }

  /* in case this reset was caused by a bus cycle, be sure to tell the
     initiator to check for a synchronous event: */
  return (TME_BUS_CYCLE_SYNCHRONOUS_EVENT);
}

/* our command function: */
static int
_tme_sun4_command(struct tme_element *element, const char * const * args, char **_output)
{
  struct tme_sun4 *sun4;
  int do_reset;

  /* recover our sun4: */
  sun4 = (struct tme_sun4 *) element->tme_element_private;

  /* assume no reset: */
  do_reset = FALSE;

  /* the "power" command: */
  if (TME_ARG_IS(args[1], "power")) {

    if (TME_ARG_IS(args[2], "up")
	&& args[3] == NULL) {
      do_reset = TRUE;
    }

    else if (TME_ARG_IS(args[2], "down")
	     && args[3] == NULL) {
      /* nothing */
    }

    /* return an error: */
    else {
      tme_output_append_error(_output,
			      "%s %s power [ up | down ]",
			      _("usage:"),
			      args[0]);
      return (EINVAL);
    }
  }

  /* the "diag-switch" command: */
  else if (TME_SUN4_IS_SUN4(sun4)
	   && TME_ARG_IS(args[1], "diag-switch")) {

    if (args[2] == NULL) {
      tme_output_append_error(_output,
			      "diag-switch %s",
			      (sun4->tme_sun44c_enable & TME_SUN4_ENA_DIAG
			       ? "true"
			       : "false"));
    }

    else if (TME_ARG_IS(args[2], "true")
	     && args[3] == NULL) {
      sun4->tme_sun44c_enable |= TME_SUN4_ENA_DIAG;
    }

    else if (TME_ARG_IS(args[2], "false")
	     && args[3] == NULL) {
      sun4->tme_sun44c_enable &= ~TME_SUN4_ENA_DIAG;
    }

    /* return an error: */
    else {
      tme_output_append_error(_output,
			      "%s %s diag-switch [ true | false ]",
			      _("usage:"),
			      args[0]);
      return (EINVAL);
    }
  }

  /* any other command: */
  else {
    if (args[1] != NULL) {
      tme_output_append_error(_output,
			      "%s '%s', ",
			      _("unknown command"),
			      args[1]);
    }
    tme_output_append_error(_output,
			    _("available %s commands: %s%s"),
			    args[0],
			    "power",
			    (TME_SUN4_IS_SUN4(sun4)
			     ? "diag-switch"
			     : ""));
    return (EINVAL);
  }

  if (do_reset) {
    _tme_sun4_reset(sun4, FALSE);
  }

  return (TME_OK);
}

/* the connection scorer: */
static int
_tme_sun4_connection_score(struct tme_connection *conn, unsigned int *_score)
{
  struct tme_sparc_bus_connection *conn_sparc;
  struct tme_sun4_bus_connection *conn_sun4;
  struct tme_bus_connection *conn_bus;
  struct tme_sun4 *sun4;
  unsigned int score;

  /* recover our sun4: */
  sun4 = (struct tme_sun4 *) conn->tme_connection_element->tme_element_private;

  /* assume that this connection is useless: */
  score = 0;

  /* dispatch on the connection type: */
  conn_sparc = (struct tme_sparc_bus_connection *) conn->tme_connection_other;
  conn_sun4 = (struct tme_sun4_bus_connection *) conn;
  conn_bus = (struct tme_bus_connection *) conn->tme_connection_other;
  switch (conn->tme_connection_type) {

    /* this must be an SPARC chip, and not another bus: */
  case TME_CONNECTION_BUS_SPARC:
    if (conn_bus->tme_bus_tlb_set_add == NULL
	&& conn_sparc->tme_sparc_bus_tlb_fill == NULL
	&& conn_sparc->tme_sparc_bus_fpu_strict != NULL) {
      score = 10;
    }
    break;

    /* this must be a bus, and not a chip, and the bus must still be
       free: */
  case TME_CONNECTION_BUS_GENERIC:
    if ((conn_bus->tme_bus_tlb_set_add != NULL
	 && conn_bus->tme_bus_tlb_fill != NULL)
	&& (conn_sun4->tme_sun4_bus_connection_which >= TME_SUN4_32_CONN_BUS_COUNT
	    || sun4->tme_sun4_buses[conn_sun4->tme_sun4_bus_connection_which] == NULL)) {
      score = 1;
    }
    break;

  default: abort();
  }

  *_score = score;
  return (TME_OK);
}

/* this makes a new connection: */
static int
_tme_sun4_connection_make(struct tme_connection *conn, unsigned int state)
{
  struct tme_sun4 *sun4;
  struct tme_sparc_bus_connection *conn_sparc;
  struct tme_sun4_bus_connection *conn_sun4;
  struct tme_bus_connection *conn_bus;
  struct tme_connection *conn_other;

  /* recover our sun4: */
  sun4 = (struct tme_sun4 *) conn->tme_connection_element->tme_element_private;

  /* dispatch on the connection type: */
  conn_other = conn->tme_connection_other;
  conn_sparc = (struct tme_sparc_bus_connection *) conn_other;
  conn_sun4 = (struct tme_sun4_bus_connection *) conn;
  conn_bus = (struct tme_bus_connection *) conn_other;
  switch (conn->tme_connection_type) {
      
  case TME_CONNECTION_BUS_SPARC:
    sun4->tme_sun4_sparc = conn_sparc;
    break;
      
  case TME_CONNECTION_BUS_GENERIC:

    /* remember the connection to this bus: */
    if (state == TME_CONNECTION_FULL) {
      assert (sun4->tme_sun4_buses[conn_sun4->tme_sun4_bus_connection_which] == NULL);
      sun4->tme_sun4_buses[conn_sun4->tme_sun4_bus_connection_which] = conn_bus;
    }
    break;

  default: 
    assert(FALSE);
    break;
  }
  return (TME_OK);
}

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

/* this makes new connection sides: */
static int
_tme_sun4_connections_new(struct tme_element *element, const char * const *args, struct tme_connection **_conns, char **_output)
{
  struct tme_sparc_bus_connection *conn_sparc;
  struct tme_sun4_bus_connection *conn_sun4;
  struct tme_bus_connection *conn_bus;
  struct tme_connection *conn;
  struct tme_sun4 *sun4;
  char *free_buses;
  int which_conn;

  /* recover our sun4: */
  sun4 = (struct tme_sun4 *) element->tme_element_private;

  /* if we have no arguments, and we don't have a CPU yet, we can take
     a SPARC bus connection: */
  if (args[1] == NULL
      && sun4->tme_sun4_sparc == NULL) {

    /* create our side of a SPARC bus connection: */
    conn_sparc = tme_new0(struct tme_sparc_bus_connection, 1);
    conn_bus = &conn_sparc->tme_sparc_bus_connection;
    conn = &conn_bus->tme_bus_connection;
    conn->tme_connection_next = *_conns;

    /* fill in the generic connection: */
    conn->tme_connection_type = TME_CONNECTION_BUS_SPARC;
    conn->tme_connection_score = _tme_sun4_connection_score;
    conn->tme_connection_make = _tme_sun4_connection_make;
    conn->tme_connection_break = _tme_sun4_connection_break;

    /* fill in the generic bus connection: */
    conn_bus->tme_bus_signal = _tme_sun4_bus_signal;
    conn_bus->tme_bus_intack = _tme_sun4_bus_intack;
    conn_bus->tme_bus_tlb_set_add
      = (TME_SUN4_IS_SUN44C(sun4)
	 ? _tme_sun44c_mmu_tlb_set_add
	 : NULL);

    /* full in the SPARC bus connection: */
    conn_sparc->tme_sparc_bus_tlb_fill
      = (TME_SUN4_IS_SUN44C(sun4)
	 ? _tme_sun44c_tlb_fill_sparc
	 : NULL);

    /* add in this connection side possibility: */
    *_conns = conn;
  }

  /* create our side of a generic bus connection: */
  conn_sun4 = tme_new0(struct tme_sun4_bus_connection, 1);
  conn_bus = &conn_sun4->tme_sun4_bus_connection;
  conn = &conn_bus->tme_bus_connection;
  conn->tme_connection_next = *_conns;

  /* fill in the generic connection: */
  conn->tme_connection_type = TME_CONNECTION_BUS_GENERIC;
  conn->tme_connection_score = _tme_sun4_connection_score;
  conn->tme_connection_make = _tme_sun4_connection_make;
  conn->tme_connection_break = _tme_sun4_connection_break;
    
  /* fill in the generic bus connection: */
  conn_bus->tme_bus_signal = _tme_sun4_bus_signal;
  conn_bus->tme_bus_intack = NULL;
  if (TME_SUN4_IS_SUN44C(sun4)) {
    conn_bus->tme_bus_tlb_fill = _tme_sun44c_tlb_fill_bus;
  }
  else {
    abort();
  }

  /* if we have no argument: */
  if (args[1] == NULL) {

    /* return no more connections: */
    tme_free(conn_sun4);
    return (TME_OK);
  }

  /* otherwise, we have at least one argument: */
  else {

    /* we must have no other arguments: */
    if (args[2] != NULL) {
      tme_output_append_error(_output,
			      "%s %s",
			      args[2],
			      _("unexpected"));
      tme_free(conn_sun4);
      return (EINVAL);
    }

    /* start the list of buses that we don't have yet: */
    free_buses = NULL;

    /* poison the which connection: */
    which_conn = -1;

    /* check each bus: */

    /* obio: */
    if (sun4->tme_sun4_32_obio == NULL) {
      tme_output_append(&free_buses, 
			(TME_SUN4_IS_SUN4C(sun4)
			 ? " sbus"
			 : " obio"));
    }
    if (TME_ARG_IS(args[1],
		   (TME_SUN4_IS_SUN4C(sun4)
		    ? "sbus"
		    : "obio"))) {
      which_conn = TME_SUN4_32_CONN_BUS_OBIO;
    }

    /* obmem: */
    if (!TME_SUN4_IS_SUN4C(sun4)) {
      if (sun4->tme_sun4_32_obmem == NULL) {
	tme_output_append(&free_buses, " obmem");
      }
      if (TME_ARG_IS(args[1], "obmem")) {
	which_conn = TME_SUN4_32_CONN_BUS_OBMEM;
      }
    }

    /* VMEbus: */
    if (TME_SUN4_IS_SUN4(sun4)) {
      if (sun4->tme_sun4_vmebus == NULL) {
	tme_output_append(&free_buses, " vme");
      }
      if (TME_ARG_IS(args[1], "vme")) {
	which_conn = TME_SUN4_CONN_BUS_VME;
	conn_bus->tme_bus_subregions.tme_bus_subregion_address_last
	  = TME_SUN4_DVMA_SIZE_VME;
      }
    }

    /* random connections: */
    
    if (TME_ARG_IS(args[1], "timer")) {
      which_conn = TME_SUN4_32_CONN_REG_TIMER;
      conn_bus->tme_bus_subregions.tme_bus_subregion_address_last
	= TME_SUN44C_TIMER_SIZ_REG * 2;
    }
    else if (TME_ARG_IS(args[1], "memerr")) {
      which_conn = TME_SUN4_32_CONN_REG_MEMERR;
      conn_bus->tme_bus_subregions.tme_bus_subregion_address_last
	= (TME_SUN4_IS_MODEL(sun4, TME_SUN_IDPROM_TYPE_CODE_CALVIN)
	   ? (TME_SUN44C_MEMERR_SIZ_REG * 2)
	   : TME_SUN44C_MEMERR_SIZ_REG);
    }
    else if (TME_ARG_IS(args[1], "intreg")) {
      which_conn = TME_SUN4_32_CONN_REG_INTREG;
      conn_bus->tme_bus_subregions.tme_bus_subregion_address_last
	= sizeof(sun4->tme_sun44c_ints);
    }
    else if (TME_SUN4_IS_SUN4C4M(sun4)
	     && TME_ARG_IS(args[1], "auxreg")) {
      which_conn = TME_SUN4C4M_CONN_REG_AUXREG;
      conn_bus->tme_bus_subregions.tme_bus_subregion_address_last
	= sizeof(sun4->tme_sun4c4m_aux);
    }

    /* if the which connection is still poison, or if this is trying
       to connect a bus that we already have, complain: */
    if (which_conn < 0
	|| (which_conn < TME_SUN4_32_CONN_BUS_COUNT
	    && sun4->tme_sun4_buses[which_conn] != NULL)) {
      if (which_conn < 0) {
	tme_output_append_error(_output,
				"%s %s ",
				_("unknown bus or register:"),
				args[1]);
      }
      if (free_buses != NULL) {
	tme_output_append_error(_output, 
				"%s %s",
				_("remaining buses:"),
				free_buses);
	tme_free(free_buses);
      }
      else {
	tme_output_append_error(_output, _("all buses present"));
      }
      tme_free(conn_sun4);
      return (EINVAL);
    }

    /* free the remaining bus list: */
    if (free_buses != NULL) {
      tme_free(free_buses);
    }

    /* fill in the sun4 connection: */
    conn_sun4->tme_sun4_bus_connection_which = which_conn;
    
    /* fill in the generic bus connection for a bus: */
    if (which_conn < TME_SUN4_32_CONN_BUS_COUNT) {
      conn_bus->tme_bus_tlb_set_add = _tme_sun44c_mmu_tlb_set_add;
    }
  }

  /* add in this connection side possibility: */
  *_conns = conn;

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

/* this creates a new sun4 element: */
TME_ELEMENT_NEW_DECL(tme_machine_sun4) {
  int usage;
  struct tme_sun4 *sun4;
  const char *idprom_filename;
  FILE *idprom_fp;
  tme_uint8_t idprom[TME_SUN_IDPROM_SIZE];
  int arg_i;

  arg_i = 1;
  usage = FALSE;

  /* if we've been given an IDPROM filename: */
  idprom_filename = NULL;
  if (TME_ARG_IS(args[arg_i], "idprom")) {
    arg_i++;
    idprom_filename = args[arg_i];
    if (idprom_filename == NULL) {
      usage = TRUE;
    }
    else {
      arg_i++;
    }
  }

  /* otherwise, if we've been given a board name: */
  else if (TME_ARG_IS(args[arg_i], "name")) {
    arg_i++;
    if (TME_ARG_IS(args[arg_i], "Calvin")) {
      idprom[TME_SUN_IDPROM_OFF_MACHTYPE] = TME_SUN_IDPROM_TYPE_CODE_CALVIN;
      arg_i++;
    }
    else if (args[arg_i] != NULL) {
      tme_output_append_error(_output,
			      "%s %s, ",
			      _("unknown name"),
			      args[arg_i]);
      usage = TRUE;
    }
    else {
      tme_output_append_error(_output,
			      "%s, ",
			      _("missing name"));
      usage = TRUE;
    }
  }

  /* we must have no more arguments: */
  if (args[arg_i] != NULL) {
    tme_output_append_error(_output,
			    "%s %s, ",
			    args[arg_i],
			    _("unexpected"));
    usage = TRUE;
  }

  /* if our usage was bad: */
  if (usage) {
    tme_output_append_error(_output,
			    "%s %s { idprom IDPROM%s | name { Calvin } } ",
			    _("usage:"),
			    args[0],
			    _("-FILENAME"));
    return (EINVAL);
  }

  /* if we've been given an IDPROM filename, try to read it in: */
  if (idprom_filename != NULL) {
    idprom_fp = fopen(idprom_filename, "r");
    if (idprom_fp == NULL) {
      tme_output_append_error(_output, "%s", idprom_filename);
      return (errno);
    }
    if (fread(idprom, sizeof(tme_uint8_t), sizeof(idprom), idprom_fp) != sizeof(idprom)) {
      tme_output_append_error(_output, "%s", idprom_filename);
      fclose(idprom_fp);
      return (ENOEXEC);
    }
    fclose(idprom_fp);
  }

  /* allocate and initialize the new sun4: */
  sun4 = tme_new0(struct tme_sun4, 1);
  sun4->tme_sun4_element = element;
  tme_mutex_init(&sun4->tme_sun4_mutex);

  /* set the IDPROM: */
  memcpy(sun4->tme_sun4_idprom_contents, idprom, sizeof(idprom));
  
  /* the MMU: */
  if (TME_SUN4_IS_SUN44C(sun4)) {
    _tme_sun44c_mmu_new(sun4);
    sun4->tme_sun4_tlb_fill = _tme_sun44c_tlb_fill_mmu;
  }
  else {
    abort();
  }

  /* the cache: */
  if (TME_SUN4_IS_SUN44C(sun4)) {
    _tme_sun44c_cache_new(sun4);
  }
  else {
    abort();
  }

  /* the control ASIs: */
  sun4->tme_sun4_asis[TME_SUN4_32_ASI_CONTROL].tme_sun4_asi_sun4 = sun4;
  if (TME_SUN4_IS_SUN44C(sun4)) {
    sun4->tme_sun4_asis[TME_SUN44C_ASI_SEGMAP].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN44C_ASI_PGMAP].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN4C_ASI_HW_FLUSH_SEG /* TME_SUN4_ASI_COPY */].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN4C_ASI_HW_FLUSH_PG /* TME_SUN4_ASI_REGMAP */].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN4C_ASI_HW_FLUSH_CONTEXT /* TME_SUN4_ASI_FLUSH_REG */].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN44C_ASI_FLUSH_SEG].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN44C_ASI_FLUSH_PG].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN44C_ASI_FLUSH_CONTEXT].tme_sun4_asi_sun4 = sun4;
    sun4->tme_sun4_asis[TME_SUN4C_ASI_HW_FLUSH_ALL /* TME_SUN4_ASI_FLUSH_USER */].tme_sun4_asi_sun4 = sun4;
  }
  else {
    abort();
  }

  /* the timers: */
  _tme_sun4_timer_new(sun4);

  /* fill the element: */
  element->tme_element_private = sun4;
  element->tme_element_connections_new = _tme_sun4_connections_new;
  element->tme_element_command = _tme_sun4_command;

  return (TME_OK);
}

/* this creates a new Sun-4 isil7170: */
TME_ELEMENT_SUB_NEW_DECL(tme_machine_sun4,oclock) {
  struct tme_isil7170_socket socket;
  const char *sub_args[4];
  int arg_i;

  /* create the isil7170 socket: */
  memset (&socket, 0, sizeof(socket));
  socket.tme_isil7170_socket_version = TME_ISIL7170_SOCKET_0;
  socket.tme_isil7170_socket_addr_shift = 0;
  socket.tme_isil7170_socket_port_least_lane = 3; /* D31-D24 */
  socket.tme_isil7170_socket_clock_basic = TME_ISIL7170_FREQ_32K;
  socket.tme_isil7170_socket_int_signal = TME_BUS_SIGNAL_INT_UNSPEC;

  /* create the isil7170.  we allow at most two arguments to pass
     through, which is an awful hack: */
  sub_args[0] = "tme/ic/isil7170";
  for (arg_i = 1;; arg_i++) {
    if (arg_i == TME_ARRAY_ELS(sub_args)) {
      abort();
    }
    sub_args[arg_i] = args[arg_i];
    if (args[arg_i] == NULL) {
      break;
    }
  }
  return (tme_element_new(element, (const char * const *) sub_args, &socket, _output));
}

/* this creates a new Sun-4 mk48txx: */
TME_ELEMENT_SUB_NEW_DECL(tme_machine_sun4,clock) {
  struct tme_mk48txx_socket socket;

  /* create the mk48txx socket: */
  memset (&socket, 0, sizeof(socket));
  socket.tme_mk48txx_socket_version = TME_MK48TXX_SOCKET_0;
  socket.tme_mk48txx_socket_addr_shift = 0;
  socket.tme_mk48txx_socket_port_least_lane = 3; /* D31-D24 */
  socket.tme_mk48txx_socket_year_zero = 1968;

  if (!TME_ARG_IS(args[1], "type")
      || args[2] == NULL) {
    tme_output_append_error(_output,
			    "%s %s type CLOCK-%s",
			    _("usage:"),
			    args[0],
			    _("TYPE"));
    return (EINVAL);
  }

  return (tme_element_new(element, args + 2, &socket, _output));
}

/* this creates a new Sun-4 z8530: */
TME_ELEMENT_SUB_NEW_DECL(tme_machine_sun4,zs) {
  struct tme_z8530_socket socket = TME_SUN_Z8530_SOCKET_INIT;
  char *sub_args[2];

  /* create the z8530: */
  sub_args[0] = "tme/ic/z8530";
  sub_args[1] = NULL;
  return (tme_element_new(element, (const char * const *) sub_args, &socket, _output));
}
