/* vim: set filetype=c:
 *
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  undi.c86
 * Purpose: UNDI interface
 * Entries: net_start, net_stop, undi_open, undi_close, undi_send,
 *          undi_recv, undi_set_mcast
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: undi.c86,v 1.14 2007/01/06 18:30:47 gkminix Exp $
 *
 **************************************************************************
 */


#include <common.h86>
#include <romconfig.h86>
#include <i386/string.h86>
#include <i386/misc.h86>
#include <i386/time.h86>
#include <pxe/general.h86>
#include <pxe/undi.h86>
#include "headers/net.h86"
#include "headers/pxe.h86"
#include "undi.h86"



/*
 **************************************************************************
 * 
 * Global variables:
 */
struct iobuf readbuf;			/* Global read buffer */



/*
 **************************************************************************
 * 
 * Startup the network module
 */
short net_start(void *biosparams)
{
  union {
	t_undi_startup         startup;
	t_undi_initialize      init;
	t_undi_shutdown        shutdown;
	t_undi_cleanup         cleanup;
	t_undi_stop            stop;
	t_undi_get_information info;
  } params;
  register short ret;

  /* Let UNDI know the BIOS parameters */
  if ((ret = pxe(UNDI_START_UNDI, biosparams)) != PXENV_STATUS_SUCCESS)
	return(ret);

  /* Startup the UNDI interface */
  if ((ret = pxe(UNDI_STARTUP, &params.startup)) != PXENV_STATUS_SUCCESS) {
l_net_stop:
	(void)pxe(UNDI_STOP_UNDI, &params.stop);
	return(ret);
  }

  /* Initialize the UNDI interface */
  params.init.protocolini = LNULL;
  if ((ret = pxe(UNDI_INITIALIZE, &params.init)) != PXENV_STATUS_SUCCESS) {
l_net_cleanup:
	(void)pxe(UNDI_CLEANUP, &params.cleanup);
	goto l_net_stop;
  }

  /* Wait some seconds for the network card to settle down */
  wait_seconds(2);

  /* Get information about the hardware */
  if ((ret = pxe(UNDI_GET_INFORMATION, &params.info)) != PXENV_STATUS_SUCCESS) {
net_fail:
	(void)pxe(UNDI_SHUTDOWN, &params.shutdown);
	goto l_net_cleanup;
  }

  /* Set maximum transfer unit (MTU) */
  ret = PXENV_STATUS_UNDI_CONFIG_DATA;
  if (params.info.MTU < ETH_DATA_MIN)
	goto net_fail;
  mymtu = (unsigned short)params.info.MTU;
  if (mymtu > ETH_DATA_MAX)
	mymtu = ETH_DATA_MAX;

  /* Set hardware interface type */
  if (params.info.hwtype != UNDI_TYPE_ETHER)
	goto net_fail;
  mytype = (unsigned short)params.info.hwtype;

  /* Set hardware address */
  if (params.info.hwaddrlen != ETH_ALEN)
	goto net_fail;
  _memcpy(myhwaddr, params.info.currentaddr, ETH_ALEN);

  /* Finally initialize the interrupt request routine */
  set_isr((size_t)params.info.intnumber, sizeof(readbuf), (__u8 *)&readbuf);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 * 
 * Stop the network module
 */
short net_stop(void)
{
  union {
	t_undi_shutdown  shutdown;
	t_undi_get_state getstate;
	t_undi_close     close;
  } params;

  if (pxe(UNDI_GET_STATE, &params.getstate) == PXENV_STATUS_SUCCESS &&
      params.getstate.state == UNDI_STATE_OPENED)
	(void)pxe(UNDI_CLOSE, &params.close);

  if (rst_isr() != 0)
	return(PXENV_STATUS_KEEP_ALL);

  (void)pxe(UNDI_SHUTDOWN, &params.shutdown);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 * 
 * Shutdown the network services
 */
short net_cleanup(void)
{
  union {
    t_undi_stop     stop;
    t_undi_cleanup  cleanup;
  } params;

  if (pxe(UNDI_CLEANUP, &params.cleanup) != PXENV_STATUS_SUCCESS)
	return(PXENV_STATUS_CLEANUP_UNDI);
  if (pxe(UNDI_STOP_UNDI, &params.stop) != PXENV_STATUS_SUCCESS)
	return(PXENV_STATUS_STOP_UNDI);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 * 
 * Open a connection to the UNDI driver
 */
short undi_open(void)
{
  union {
	t_undi_initialize init;
	t_undi_get_state  state;
	t_undi_open       open;
  } params;
  register short ret;

  /* Check if the UNDI interface is initialized */
  if ((ret = pxe(UNDI_GET_STATE, &params.state)) != PXENV_STATUS_SUCCESS)
	return(ret);
  switch (params.state.state) {
	case UNDI_STATE_OPENED:
		/* Already open, nothing to do */
		return(PXENV_STATUS_SUCCESS);
	case UNDI_STATE_INITIALIZED:
		/* UNDI initialized, need to open */
		break;
	case UNDI_STATE_STARTED:
		/* UNDI not initialized, need to initialize first */
		params.init.protocolini = LNULL;
		if ((ret = pxe(UNDI_INITIALIZE, &params.init)) !=
							PXENV_STATUS_SUCCESS)
			return(ret);
		break;
	default:
		/* Invalid UNDI state */
		return(PXENV_STATUS_UNDI_INVALID_STATE);
  }

  /* Now open the UNDI interface */
  params.open.openflag = 0;
  params.open.pktfilter = UNDI_FLTR_DIRECTED + UNDI_FLTR_BROADCAST;
  params.open.mcastbuf.count = 0;
  return(pxe(UNDI_OPEN, &params.open));
}



/*
 **************************************************************************
 * 
 * Close a connection to the UNDI driver
 */
short undi_close(void)
{
  t_undi_close params;

  return(pxe(UNDI_CLOSE, &params));
}



/*
 **************************************************************************
 * 
 * Determine hardware address for multicast address
 */
short undi_get_mcast(t_ipaddr maddr, __u8 *hw)
{
  t_undi_get_mcast_addr getparams;
  register short ret;

  getparams.inetaddr = maddr;
  if ((ret = pxe(UNDI_GET_MCAST_ADDR, &getparams)) == PXENV_STATUS_SUCCESS)
	memcpy(hw, getparams.hwaddr, ETH_ALEN);
  return(ret);
}



/*
 **************************************************************************
 * 
 * Set multicast address in UNDI driver
 */
short undi_set_mcast(const t_ipaddr *maddr, short count)
{
  t_undi_set_mcast_addr setparams;
  register __u8 *hw;
  register short ret, i;

  /* Convert multicast IP addresses into multicast hardware addresses */
  i = 0;
  while (i < count) {
	hw = setparams.mcastbuf.addrs[i];
	if ((ret = undi_get_mcast(*maddr, hw)) != PXENV_STATUS_SUCCESS)
		return(ret);
	maddr++;
	i++;
  }

  /* Set multicast hardware address */
  setparams.mcastbuf.count = i;
  return(pxe(UNDI_SET_MCAST_ADDR, &setparams));
}



/*
 **************************************************************************
 * 
 * Send a packet using UNDI
 */
short undi_send(__u8 *dest, short proto, short num,
				size_t sizes[], __fptr bufs[])

{
  t_undi_transmit params;
  t_undi_tbd      tbd;
  register short  i;

  /* Check that we have anything to do at all */
  if (num == 0)
	return(PXENV_STATUS_SUCCESS);

  /* Setup the UNDI parameter structure */
  params.protocol = (__u8)proto;
  params.tbd = MK_FP(getds(), &tbd);
  if (dest == NULL) {
	params.xmtflag = UNDI_XMT_BROADCAST;
	params.dest_addr = FNULL;
  } else {
	params.xmtflag = UNDI_XMT_DESTADDR;
	params.dest_addr = MK_FP(getds(), dest);
  }

  /* Setup the transmit buffer data block */
  tbd.xmtlength = 0;
  tbd.blocks = (__u16)num;
  i = 0;
  while (i < num) {
	tbd.datablock[i].tdptrtype = UNDI_XMT_TD_SEGOFS;
	tbd.datablock[i].tdlength = sizes[i];
	tbd.datablock[i].tdaddr = bufs[i];
	i++;
  }

  /* Finally send the data */
  return(pxe(UNDI_TRANSMIT, &params));
}



/*
 **************************************************************************
 * 
 * Receive a packet through UNDI
 */
size_t undi_recv(void)
{
  return(read_isr());
}

