/* vim: set filetype=c:
 *
 **************************************************************************
 *
 * UNDI network driver
 *
 * Module:  sample.c86
 * Purpose: Sample UNDI network driver skeleton
 * Entries: pxe_load, pxe_entry
 *
 **************************************************************************
 *
 * Copyright (C) 2005-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: sample.c86,v 1.4 2007/01/06 18:30:50 gkminix Exp $
 */


#include <common.h86>
#include <i386/string.h86>
#include <i386/misc.h86>
#include <i386/conio.h86>
#include <i386/port.h86>
#include <system/pci.h86>
#include <net/ethernet.h86>
#include <pxe/general.h86>
#include <pxe/undi.h86>



/*
 **************************************************************************
 * 
 * Set stack and heap sizes
 */
SET_STACK_SIZE(4096);
SET_HEAP_SIZE(0);



/*
 **************************************************************************
 * 
 * Definitions to access network device
 */
#define PCI_VENDOR_ID	0x1234
#define PCI_DEVICE_ID	0x5678



/*
 **************************************************************************
 * 
 * PCI device definitions
 */
static struct {
	__u16  vendor_id;
	__u16  device_id;
	__u8   revision;
	__u8   progint;
	__u16  devclass;
	__u16  subvendid;
	__u16  subdevid;
} pcidev;



/*
 **************************************************************************
 * 
 * Local variables
 */
static unsigned short pcipfa;
static unsigned short undistate;



/*
 **************************************************************************
 * 
 * Forward declarations
 */
static int undi_start_undi(t_start_undi *params);
static int undi_startup(t_undi_startup *params);
static int undi_cleanup(t_undi_cleanup *params);
static int undi_initialize(t_undi_initialize *params);
static int undi_reset(t_undi_reset *params);
static int undi_shutdown(t_undi_shutdown *params);
static int undi_open(t_undi_open *params);
static int undi_close(t_undi_close *params);
static int undi_transmit(t_undi_transmit *params);
static int undi_set_mcast_addr(t_undi_set_mcast_addr *params);
static int undi_set_station_addr(t_undi_set_station_addr *params);
static int undi_set_packet_filter(t_undi_set_packet_filter *params);
static int undi_get_information(t_undi_get_information *params);
static int undi_get_statistics(t_undi_get_statistics *params);
static int undi_clear_statistics(t_undi_clear_statistics *params);
static int undi_initiate_diags(t_undi_initiate_diags *params);
static int undi_force_interrupt(t_undi_force_interrupt *params);
static int undi_get_mcast_addr(t_undi_get_mcast_addr *params);
static int undi_get_nic_type(t_undi_get_nic_type *params);
static int undi_get_iface_info(t_undi_get_iface_info *params);
static int undi_isr(t_undi_isr *params);
static int undi_stop(t_undi_stop *params);
static int undi_get_state(t_undi_get_state *params);



/*
 **************************************************************************
 * 
 * UNDI loader handler
 *
 * This routine gets called upon loading of the UNDI driver. It should
 * not modify the state of the network card, but only check for it's
 * presence. It gets the register values which the bootrom received
 * from the BIOS:
 *
 *   AX  -  PCI PFA
 *   BX  -  PnP CSN
 *   DX  -  PnP data port
 */
int pxe_load(__u16 reg_ax, __u16 reg_bx, __u16 reg_cx)
{
  /* Print copyright notice */
  printf("Sample UNDI network driver, " VERSION "\n");
  printf(COPYRIGHT "\n\n");

  /* Save PCI device address */
  pcipfa = reg_ax;
  if (pcipfa == 0xFFFF) {
	printf("Sample device not initialized by BIOS\n\n");
	return(PXENV_STATUS_FAILURE);
  }

  /* Read vendor and device ID */
  pcidev.device_id = pci_read_word(pcipfa, PCI_REG_DEVICE_ID);
  pcidev.vendor_id = pci_read_word(pcipfa, PCI_REG_VENDOR_ID);
  if (pcidev.device_id != PCI_DEVICE_ID || pcidev.vendor_id != PCI_VENDOR_ID) {
	printf("Unable to find sample device\n\n");
	return(PXENV_STATUS_FAILURE);
  }

  /* Read remaining PCI device information */
  pcidev.devclass = pci_read_word(pcipfa, PCI_REG_CLASS_DEVICE);
  pcidev.progint = pci_read_byte(pcipfa, PCI_REG_CLASS_PROG);
  pcidev.revision = pci_read_byte(pcipfa, PCI_REG_REVISION);
  pcidev.subvendid = pci_read_word(pcipfa, PCI_REG_SUBSYS_VEND);
  pcidev.subdevid = pci_read_word(pcipfa, PCI_REG_SUBSYS_ID);

  /* Let the user know that we found a suitable device */
  printf("Found sample device\n\n");
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 * 
 * UNDI function dispatcher
 */
int pxe_entry(unsigned short func, void *params)
{
  switch (func) {
	case UNDI_START_UNDI:
		return(undi_start_undi(params));
	case UNDI_STARTUP:
		return(undi_startup(params));
	case UNDI_CLEANUP:
		return(undi_cleanup(params));
	case UNDI_INITIALIZE:
		return(undi_initialize(params));
	case UNDI_RESET_NIC:
		return(undi_reset(params));
	case UNDI_SHUTDOWN:
		return(undi_shutdown(params));
	case UNDI_OPEN:
		return(undi_open(params));
	case UNDI_CLOSE:
		return(undi_close(params));
	case UNDI_TRANSMIT:
		return(undi_transmit(params));
	case UNDI_SET_MCAST_ADDR:
		return(undi_set_mcast_addr(params));
	case UNDI_SET_STATION_ADDR:
		return(undi_set_station_addr(params));
	case UNDI_SET_PACKET_FILTER:
		return(undi_set_packet_filter(params));
	case UNDI_GET_INFORMATION:
		return(undi_get_information(params));
	case UNDI_GET_STATISTICS:
		return(undi_get_statistics(params));
	case UNDI_CLEAR_STATISTICS:
		return(undi_clear_statistics(params));
	case UNDI_INITIATE_DIAGS:
		return(undi_initiate_diags(params));
	case UNDI_FORCE_INTERRUPT:
		return(undi_force_interrupt(params));
	case UNDI_GET_MCAST_ADDR:
		return(undi_get_mcast_addr(params));
	case UNDI_GET_NIC_TYPE:
		return(undi_get_nic_type(params));
	case UNDI_GET_IFACE_INFO:
		return(undi_get_iface_info(params));
	case UNDI_ISR:
		return(undi_isr(params));
	case UNDI_STOP_UNDI:
		return(undi_stop(params));
	case UNDI_GET_STATE:
		return(undi_get_state(params));
	default:
		return(PXENV_STATUS_BAD_FUNC);
  }
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_START_UNDI
 */
static int undi_start_undi(t_start_undi *params)
{
  if (pcipfa != params->AX)
	return(PXENV_STATUS_FAILURE);

  undistate = 0;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_STARTUP
 */
static int undi_startup(t_undi_startup *params)
{
  if (undistate != 0)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  undistate = UNDI_STATE_STARTED;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_CLEANUP
 */
static int undi_cleanup(t_undi_cleanup *params)
{
  if (undistate != UNDI_STATE_STARTED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  undistate = 0;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_INITIALIZE
 */
static int undi_initialize(t_undi_initialize *params)
{
  if (undistate != UNDI_STATE_STARTED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  undistate = UNDI_STATE_INITIALIZED;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_RESET
 */
static int undi_reset(t_undi_reset *params)
{
  if (undistate < UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  if (undistate == UNDI_STATE_OPENED)
	undistate = UNDI_STATE_INITIALIZED;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_SHUTDOWN
 */
static int undi_shutdown(t_undi_shutdown *params)
{
  if (undistate != UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  undistate = UNDI_STATE_STARTED;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_OPEN
 */
static int undi_open(t_undi_open *params)
{
  if (undistate != UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  undistate = UNDI_STATE_OPENED;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_CLOSE
 */
static int undi_close(t_undi_close *params)
{
  if (undistate != UNDI_STATE_OPENED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  undistate = UNDI_STATE_INITIALIZED;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_TRANSMIT
 */
static int undi_transmit(t_undi_transmit *params)
{
  if (undistate != UNDI_STATE_OPENED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_SET_MCAST_ADDR
 */
static int undi_set_mcast_addr(t_undi_set_mcast_addr *params)
{
  if (undistate < UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_SET_STATION_ADDR
 */
static int undi_set_station_addr(t_undi_set_station_addr *params)
{
  if (undistate < UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_SET_PACKET_FILTER
 */
static int undi_set_packet_filter(t_undi_set_packet_filter *params)
{
  if (undistate < UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_GET_INFORMATION
 */
static int undi_get_information(t_undi_get_information *params)
{
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_GET_STATISTICS
 */
static int undi_get_statistics(t_undi_get_statistics *params)
{
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_CLEAR_STATISTICS
 */
static int undi_clear_statistics(t_undi_clear_statistics *params)
{
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_INITIATE_DIAGS
 */
static int undi_initiate_diags(t_undi_initiate_diags *params)
{
  if (undistate != UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_FORCE_INTERRUPT
 */
static int undi_force_interrupt(t_undi_force_interrupt *params)
{
  if (undistate != UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_GET_MCAST_ADDR
 */
static int undi_get_mcast_addr(t_undi_get_mcast_addr *params)
{
  if (undistate < UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_GET_NIC_TYPE
 */
static int undi_get_nic_type(t_undi_get_nic_type *params)
{
  params->nictype = UNDI_NIC_PCI;
  params->info.pci.vendorid = pcidev.vendor_id;
  params->info.pci.devid = pcidev.device_id;
  params->info.pci.baseclass = (pcidev.devclass >> 8) & 0x00FF;
  params->info.pci.subclass = pcidev.devclass & 0x00FF;
  params->info.pci.progint = pcidev.progint;
  params->info.pci.revision = pcidev.revision;
  params->info.pci.busdevfunc = pcipfa;
  params->info.pci.subvendorid = pcidev.subvendid;
  params->info.pci.subdevid = pcidev.subdevid;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_GET_IFACE_INFO
 */
static int undi_get_iface_info(t_undi_get_iface_info *params)
{
  memcpy(params->ifacetype, "SAMPLEMAC", 10);
  params->linkspeed = 10000000;
  params->serviceflags = 0;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_ISR
 */
static int undi_isr(t_undi_isr *params)
{
  if (undistate < UNDI_STATE_INITIALIZED)
	return(PXENV_STATUS_UNDI_INVALID_STATE);
  if (params->funcflag == UNDI_ISR_IN_START)
	params->funcflag = UNDI_ISR_OUT_NOT_OURS;
  else if (params->funcflag == UNDI_ISR_IN_PROCESS ||
           params->funcflag == UNDI_ISR_IN_GET_NEXT)
	params->funcflag = UNDI_ISR_OUT_DONE;
  else
	return(PXENV_STATUS_FAILURE);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_STOP
 */
static int undi_stop(t_undi_stop *params)
{
  pcipfa = 0xFFFF;
  undistate = 0;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * UNDI_GET_STATE
 */
static int undi_get_state(t_undi_get_state *params)
{
  params->state = undistate;
  return(PXENV_STATUS_SUCCESS);
}

