/*
 * Cisco router simulation platform.
 * Copyright (c) 2007 Christophe Fillot (cf@utc.fr)
 *
 * Generic Cisco card routines and definitions.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>

#include "cpu.h"
#include "vm.h"
#include "dynamips.h"
#include "memory.h"
#include "device.h"
#include "cisco_card.h"

/* Get cisco card type description */
char *cisco_card_get_type_desc(int dev_type)
{
   switch(dev_type) {
      case CISCO_CARD_TYPE_PA:
         return("Port Adapter (PA)");
      case CISCO_CARD_TYPE_NM:
         return("Network Module (NM)");
      case CISCO_CARD_TYPE_WIC:
         return("WAN Interface Card (WIC)");
      default:
         return("Unknown");
   }
}

/* Set EEPROM definition for the specified Cisco card */
int cisco_card_set_eeprom(vm_instance_t *vm,struct cisco_card *card,
                          const struct cisco_eeprom *eeprom)
{
   if (!eeprom)
      return(0);

   if (cisco_eeprom_copy(&card->eeprom,eeprom) == -1) {
      vm_error(vm,"cisco_card_set_eeprom: no memory (eeprom=%p).\n",eeprom);
      return(-1);
   }
   
   return(0);
}

/* Unset EEPROM definition */
int cisco_card_unset_eeprom(struct cisco_card *card)
{
   cisco_eeprom_free(&card->eeprom);
   return(0);
}

/* Check if a card has a valid EEPROM defined */
int cisco_card_check_eeprom(struct cisco_card *card)
{
   return(cisco_eeprom_valid(&card->eeprom));
}

/* Create a card structure */
static inline struct cisco_card *cisco_card_create(u_int card_type)
{
   struct cisco_card *card;

   if ((card = malloc(sizeof(*card))) != NULL) {
      memset(card,0,sizeof(*card));
      card->card_type = card_type;
   }
   
   return card;
}

/* Find a NIO binding */
static struct cisco_nio_binding *
cisco_card_find_nio_binding(struct cisco_card *card,u_int port_id)
{   
   struct cisco_nio_binding *nb;

   if (!card)
      return NULL;

   for(nb=card->nio_list;nb;nb=nb->next)
      if (nb->port_id == port_id)
         return nb;

   return NULL;
}

/* Remove all NIO bindings */
static void 
cisco_card_remove_all_nio_bindings(vm_instance_t *vm,struct cisco_card *card)
{
   struct cisco_nio_binding *nb,*next;

   for(nb=card->nio_list;nb;nb=next) {
      next = nb->next;

      /* tell the slot driver to stop using this NIO */
      if (card->driver)
         card->driver->card_unset_nio(vm,card,nb->port_id);

      /* unreference NIO object */
      netio_release(nb->nio->name);
      free(nb);
   }

   card->nio_list = NULL;
}

/* Enable all NIO for the specified card */
static inline
void cisco_card_enable_all_nio(vm_instance_t *vm,struct cisco_card *card)
{
   struct cisco_nio_binding *nb;

   if (card && card->driver && card->drv_info)      
      for(nb=card->nio_list;nb;nb=nb->next)
         card->driver->card_set_nio(vm,card,nb->port_id,nb->nio);
}

/* Disable all NIO for the specified card */
static inline
void cisco_card_disable_all_nio(vm_instance_t *vm,struct cisco_card *card)
{
   struct cisco_nio_binding *nb;

   if (card && card->driver && card->drv_info)
      for(nb=card->nio_list;nb;nb=nb->next)
         card->driver->card_unset_nio(vm,card,nb->port_id);
}

/* Initialize a card */
static inline
int cisco_card_init(vm_instance_t *vm,struct cisco_card *card,u_int id)
{  
   size_t len;

   /* Check that a device type is defined for this card */
   if (!card || !card->dev_type || !card->driver)
      return(-1);

   /* Allocate device name */
   len = strlen(card->dev_type) + 10;
   if (!(card->dev_name = malloc(len))) {
      vm_error(vm,"unable to allocate device name.\n");
      return(-1);
   }

   snprintf(card->dev_name,len,"%s(%u)",card->dev_type,id);

   /* Initialize card driver */
   if (card->driver->card_init(vm,card) == -1) {
      vm_error(vm,"unable to initialize card type '%s' (id %u)\n",
               card->dev_type,id);
      return(-1);
   }

   return(0);
}

/* Shutdown card */
static int cisco_card_shutdown(vm_instance_t *vm,struct cisco_card *card)
{
   /* Check that a device type is defined for this card */
   if (!card || !card->dev_type || !card->driver)
      return(-1);

   /* Shutdown the NM driver */
   if (card->drv_info && (card->driver->card_shutdown(vm,card) == -1)) {
      vm_error(vm,"unable to shutdown card type '%s' (slot %u/%u)\n",
               card->dev_type,card->slot_id,card->subslot_id);
      return(-1);
   }

   free(card->dev_name);
   card->dev_name = NULL;
   card->drv_info = NULL;
   return(0);
}

/* Show info for the specified card */
static int cisco_card_show_info(vm_instance_t *vm,struct cisco_card *card)
{
   /* Check that a device type is defined for this card */
   if (!card || !card->driver || !card->driver->card_show_info)
      return(-1);

   card->driver->card_show_info(vm,card);
   return(0);
}

/* Save config for the specified card */
static int cisco_card_save_config(vm_instance_t *vm,struct cisco_card *card,
                                  FILE *fd)
{
   struct cisco_nio_binding *nb;

   if (card != NULL) {
      fprintf(fd,"vm slot_add_binding %s %u %u %s\n",
              vm->name,card->slot_id,card->subslot_id,card->dev_type);

      for(nb=card->nio_list;nb;nb=nb->next) {
         fprintf(fd,"vm add_nio_binding %s %u %u %s\n",
                 vm->name,card->slot_id,nb->orig_port_id,nb->nio->name);
      }
   }

   return(0);
}

/* Find a driver in a driver array */
static struct cisco_card_driver *
cisco_card_find_driver(struct cisco_card_driver **array,char *dev_type)
{
   int i;

   for(i=0;array[i]!=NULL;i++)
      if (!strcmp(array[i]->dev_type,dev_type))
         return array[i];

   return NULL;
}

/* ======================================================================== */
/* High level routines for managing VM slots.                               */
/* ======================================================================== */

/* Get slot info */
struct cisco_card *vm_slot_get_card_ptr(vm_instance_t *vm,u_int slot_id)
{
   if (slot_id >= vm->nr_slots)
      return NULL;

   return(vm->slots[slot_id]);
}

/* Get info for a slot/port (with sub-cards) */
static int vm_slot_get_info(vm_instance_t *vm,u_int slot_id,u_int port_id,
                            struct cisco_card ***rc,u_int *real_port_id)
{
   struct cisco_card *card;
   u_int wic_id,card_type;

   if (slot_id >= VM_MAX_SLOTS) {
      *rc = NULL;
      return(-1);
   }

   *rc = &vm->slots[slot_id];
   card = vm->slots[slot_id];

   card_type = (card != NULL) ? card->card_type : CISCO_CARD_TYPE_UNDEF;

   switch(card_type) {
      /* 
       * Handle WICs which are sub-slots for Network Modules (NM).
       * Numbering: wic #0 => port_id = 0x10
       *            wic #1 => port_id = 0x20
       */
      case CISCO_CARD_TYPE_NM:
         if (card->driver->wic_slots > 0) {
            wic_id = port_id >> 4;

            if (wic_id >= (CISCO_CARD_MAX_WIC+1)) {
               vm_error(vm,"Invalid wic_id %u (slot %u)\n",wic_id,slot_id);
               return(-1);
            }

            if (wic_id >= 0x01) {
               /* wic card */
               *rc = &card->sub_slots[wic_id - 1];
               *real_port_id = port_id & 0x0F;
            } else {
               /* main card */
               *real_port_id = port_id;
            }
         } else {
            *real_port_id = port_id;
         }
         return(0);

      /* No translation for Cisco 7200 Port Adapters and WICs */
      case CISCO_CARD_TYPE_PA:
      case CISCO_CARD_TYPE_WIC:
         *real_port_id = port_id;
         return(0);

      /* Not initialized yet */
      default:
         *real_port_id = port_id;
         return(0);
   }
}

/* Translate a port ID (for sub-cards) */
static u_int 
vm_slot_translate_port_id(vm_instance_t *vm,u_int slot_id,u_int port_id,
                          struct cisco_card **rc)
{
   struct cisco_card **tmp;
   u_int real_port_id = 0;

   vm_slot_get_info(vm,slot_id,port_id,&tmp,&real_port_id);
   *rc = *tmp;
   return(real_port_id);
}

/* Check if a slot has an active card */
int vm_slot_active(vm_instance_t *vm,u_int slot_id,u_int port_id)
{
   struct cisco_card **rc;
   u_int real_port_id;

   if (vm_slot_get_info(vm,slot_id,port_id,&rc,&real_port_id) == -1)
      return(FALSE);

   if ((*rc == NULL) || ((*rc)->dev_type == NULL))
      return(FALSE);

   return(TRUE);
}

/* Set a flag for a card */
int vm_slot_set_flag(vm_instance_t *vm,u_int slot_id,u_int port_id,u_int flag)
{
   struct cisco_card **rc;
   u_int real_port_id;

   if (vm_slot_get_info(vm,slot_id,port_id,&rc,&real_port_id) == -1)
      return(FALSE);

   if (*rc == NULL)
      return(FALSE);

   (*rc)->card_flags |= flag;
   return(TRUE);
}

/* Add a slot binding */
int vm_slot_add_binding(vm_instance_t *vm,char *dev_type,
                        u_int slot_id,u_int port_id)
{     
   struct cisco_card_driver *driver,**drv_array;
   struct cisco_card **rc,*card,*nc,*parent;
   u_int real_port_id,card_type,card_id;

   if (vm_slot_get_info(vm,slot_id,port_id,&rc,&real_port_id) == -1)
      return(-1);

   /* check that this bay is empty */
   if (*rc != NULL) {
      if ((*rc)->card_flags & CISCO_CARD_FLAG_OVERRIDE) {
         vm_slot_remove_binding(vm,slot_id,port_id);
      } else {
         vm_error(vm,"a card already exists in slot %u/%u (%s)\n",
                  slot_id,port_id,(*rc)->dev_type);
         return(-1);
      }
   }

   card = vm->slots[slot_id];

   if (!card || (card == *rc)) {
      /* Main slot */
      drv_array = vm->slots_drivers;
      card_type = vm->slots_type;
      card_id   = slot_id;
      parent    = NULL;
   } else {
      /* Subslot */
      if (!card->driver->card_get_sub_info) {
         vm_error(vm,"no sub-slot possible for slot %u/%u.\n",slot_id,port_id);
         return(-1);
      }

      if (card->driver->card_get_sub_info(vm,card,port_id,
                                          &drv_array,&card_type) == -1)
      {
         vm_error(vm,"no sub-slot info for slot %u/%u.\n",slot_id,port_id);
         return(-1);
      }

      card_id = port_id;
      parent  = card;
   }

   assert(drv_array != NULL);

   /* Find the card driver */
   if (!(driver = cisco_card_find_driver(drv_array,dev_type))) {
      vm_error(vm,"unknown card type '%s' for slot %u/%u.\n",
               dev_type,slot_id,port_id);
      return(-1);
   }

   /* Allocate new card info */
   if (!(nc = cisco_card_create(card_type)))
      return(-1);

   nc->slot_id    = slot_id;
   nc->subslot_id = port_id;
   nc->card_id    = card_id;
   nc->dev_type   = driver->dev_type;
   nc->driver     = driver;
   nc->parent     = parent;
   *rc = nc;
   return(0);  
}

/* Remove a slot binding */
int vm_slot_remove_binding(vm_instance_t *vm,u_int slot_id,u_int port_id)
{
   struct cisco_card **rc,*sc;
   u_int i,real_port_id;

   if (vm_slot_get_info(vm,slot_id,port_id,&rc,&real_port_id) == -1)
      return(-1);

   if (*rc == NULL)
      return(-1);

   if ((*rc)->drv_info != NULL) {
      vm_error(vm,"slot %u/%u is still active\n",slot_id,port_id);
      return(-1);
   }

   for(i=0;i<CISCO_CARD_MAX_SUBSLOTS;i++) {
      if ((sc = (*rc)->sub_slots[i]) != NULL) {
         vm_error(vm,"sub-slot %u/%u is still active\n",
                  slot_id,sc->subslot_id);
         return(-1);
      }
   }

   /* Remove all NIOs bindings */ 
   vm_slot_remove_all_nio_bindings(vm,slot_id);

   /* Free the card info structure */
   free(*rc);
   *rc = NULL;
   return(0);
}

/* Add a network IO binding */
int vm_slot_add_nio_binding(vm_instance_t *vm,u_int slot_id,u_int port_id,
                            char *nio_name)
{
   struct cisco_nio_binding *nb;
   struct cisco_card *card,*rc;
   u_int real_port_id;
   netio_desc_t *nio;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Get the real card (in case this is a sub-slot) */
   real_port_id = vm_slot_translate_port_id(vm,slot_id,port_id,&rc);

   if (rc == NULL)
      return(-1);

   /* check that a NIO is not already bound to this port */
   if (cisco_card_find_nio_binding(rc,real_port_id) != NULL) {
      vm_error(vm,"a NIO already exists for interface %u/%u.\n",
               slot_id,port_id);
      return(-1);
   }

   /* acquire a reference on the NIO object */
   if (!(nio = netio_acquire(nio_name))) {
      vm_error(vm,"unable to find NIO '%s'.\n",nio_name);
      return(-1);
   }

   /* create a new binding */
   if (!(nb = malloc(sizeof(*nb)))) {
      vm_error(vm,"unable to create NIO binding for interface %u/%u.\n",
               slot_id,port_id);
      netio_release(nio_name);
      return(-1);
   }

   memset(nb,0,sizeof(*nb));
   nb->nio          = nio;
   nb->port_id      = real_port_id;
   nb->orig_port_id = port_id;

   nb->next = rc->nio_list;
   if (nb->next) nb->next->prev = nb;
   rc->nio_list = nb;
   return(0);
}

/* Remove a NIO binding */
int vm_slot_remove_nio_binding(vm_instance_t *vm,u_int slot_id,u_int port_id)
{
   struct cisco_nio_binding *nb;
   struct cisco_card *card,*rc;
   u_int real_port_id;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Get the real card (in case this is a sub-slot) */
   real_port_id = vm_slot_translate_port_id(vm,slot_id,port_id,&rc);

   if (rc == NULL)
      return(-1);

   /* no nio binding for this slot/port ? */
   if (!(nb = cisco_card_find_nio_binding(rc,real_port_id)))
      return(-1);

   /* tell the NM driver to stop using this NIO */
   if (rc->driver)
      rc->driver->card_unset_nio(vm,rc,port_id);

   /* remove this entry from the double linked list */
   if (nb->next)
      nb->next->prev = nb->prev;

   if (nb->prev) {
      nb->prev->next = nb->next;
   } else {
      rc->nio_list = nb->next;
   }

   /* unreference NIO object */
   netio_release(nb->nio->name);
   free(nb);
   return(0);
}

/* Remove all NIO bindings for the specified slot (sub-slots included) */
int vm_slot_remove_all_nio_bindings(vm_instance_t *vm,u_int slot_id)
{
   struct cisco_card *card,*sc;
   int i;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Remove NIO bindings for the main slot */
   cisco_card_remove_all_nio_bindings(vm,card);

   /* Remove NIO bindings for all sub-slots */
   for(i=0;i<CISCO_CARD_MAX_SUBSLOTS;i++) {
      if ((sc = card->sub_slots[i]) != NULL)
         cisco_card_remove_all_nio_bindings(vm,sc);
   }

   return(0);
}

/* Enable a Network IO descriptor for the specified slot */
int vm_slot_enable_nio(vm_instance_t *vm,u_int slot_id,u_int port_id)
{
   struct cisco_nio_binding *nb;
   struct cisco_card *card,*rc;
   u_int real_port_id;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Get the real card (in case this is a sub-slot) */
   real_port_id = vm_slot_translate_port_id(vm,slot_id,port_id,&rc);

   if (rc == NULL)
      return(-1);

   /* no nio binding for this slot/port ? */
   if (!(nb = cisco_card_find_nio_binding(rc,real_port_id)))
      return(-1);

   /* check that the driver is defined and successfully initialized */
   if (!rc->driver || !rc->drv_info)
      return(-1);

   return(rc->driver->card_set_nio(vm,rc,real_port_id,nb->nio));
}

/* Disable Network IO descriptor for the specified slot */
int vm_slot_disable_nio(vm_instance_t *vm,u_int slot_id,u_int port_id)
{
   struct cisco_nio_binding *nb;
   struct cisco_card *card,*rc;
   u_int real_port_id;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Get the real card (in case this is a sub-slot) */
   real_port_id = vm_slot_translate_port_id(vm,slot_id,port_id,&rc);

   if (rc == NULL)
      return(-1);

   /* no nio binding for this slot/port ? */
   if (!(nb = cisco_card_find_nio_binding(rc,real_port_id)))
      return(-1);

   /* check that the driver is defined and successfully initialized */
   if (!rc->driver || !rc->drv_info)
      return(-1);

   return(rc->driver->card_unset_nio(vm,rc,real_port_id));
}

/* Enable all NIO for the specified slot (sub-slots included) */
int vm_slot_enable_all_nio(vm_instance_t *vm,u_int slot_id)
{
   struct cisco_card *card;
   int i;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Enable slot NIOs */
   cisco_card_enable_all_nio(vm,card);

   /* Enable NIO of sub-slots */
   for(i=0;i<CISCO_CARD_MAX_SUBSLOTS;i++)
      cisco_card_enable_all_nio(vm,card->sub_slots[i]);

   return(0);
}

/* Disable all NIO for the specified slot (sub-slots included) */
int vm_slot_disable_all_nio(vm_instance_t *vm,u_int slot_id)
{
   struct cisco_card *card;
   int i;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Disable slot NIOs */
   cisco_card_disable_all_nio(vm,card);

   /* Disable NIO of sub-slots */
   for(i=0;i<CISCO_CARD_MAX_SUBSLOTS;i++)
      cisco_card_disable_all_nio(vm,card->sub_slots[i]);

   return(0);
}

/* Initialize the specified slot (sub-slots included) */
int vm_slot_init(vm_instance_t *vm,u_int slot_id)
{
   struct cisco_card *card;
   int i;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(0);

   /* Initialize card main module */
   cisco_card_init(vm,card,slot_id);

   /* Initialize sub-slots */
   for(i=0;i<CISCO_CARD_MAX_SUBSLOTS;i++)
      cisco_card_init(vm,card->sub_slots[i],slot_id);

   /* Enable all NIO */
   vm_slot_enable_all_nio(vm,slot_id);
   return(0);
}

/* Initialize all slots of a VM */
int vm_slot_init_all(vm_instance_t *vm)
{
   int i;

   for(i=0;i<vm->nr_slots;i++) {
      if (vm_slot_init(vm,i) == -1) {
         vm_error(vm,"unable to initialize slot %u\n",i);
         return(-1);
      }
   }

   return(0);
}

/* Shutdown the specified slot (sub-slots included) */
int vm_slot_shutdown(vm_instance_t *vm,u_int slot_id)
{
   struct cisco_card *card;
   int i;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Disable all NIO */
   vm_slot_disable_all_nio(vm,slot_id);

   /* Shutdown sub-slots */
   for(i=0;i<CISCO_CARD_MAX_SUBSLOTS;i++)
      cisco_card_shutdown(vm,card->sub_slots[i]);

   /* Shutdown card main module */
   cisco_card_shutdown(vm,card);
   return(0);
}

/* Shutdown all slots of a VM */
int vm_slot_shutdown_all(vm_instance_t *vm)
{
   int i;

   for(i=0;i<vm->nr_slots;i++)
      vm_slot_shutdown(vm,i);

   return(0);
}

/* Show info about the specified slot (sub-slots included) */
int vm_slot_show_info(vm_instance_t *vm,u_int slot_id)
{   
   struct cisco_card *card;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   cisco_card_show_info(vm,card);
   return(0);
}

/* Show info about all slots */
int vm_slot_show_all_info(vm_instance_t *vm)
{
   int i;

   for(i=0;i<vm->nr_slots;i++)
      vm_slot_show_info(vm,i);

   return(0);
}

/* Check if the specified slot has a valid EEPROM defined */
int vm_slot_check_eeprom(vm_instance_t *vm,u_int slot_id,u_int port_id)
{
   struct cisco_card *card,*rc;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(FALSE);

   /* Get the real card (in case this is a sub-slot) */
   vm_slot_translate_port_id(vm,slot_id,port_id,&rc);
   
   if (rc == NULL)
      return(FALSE);

   return(cisco_card_check_eeprom(rc));
}

/* Returns the EEPROM data of the specified slot */
struct cisco_eeprom *
vm_slot_get_eeprom(vm_instance_t *vm,u_int slot_id,u_int port_id)
{
   struct cisco_card *card,*rc;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return NULL;

   /* Get the real card (in case this is a sub-slot) */
   vm_slot_translate_port_id(vm,slot_id,port_id,&rc);

   if (rc == NULL)
      return NULL;

   return(&rc->eeprom);
}

/* Save config for the specified slot (sub-slots included) */
int vm_slot_save_config(vm_instance_t *vm,u_int slot_id,FILE *fd)
{
   struct cisco_card *card;
   int i;

   if (!(card = vm_slot_get_card_ptr(vm,slot_id)))
      return(-1);

   /* Main slot info */
   cisco_card_save_config(vm,card,fd);

   /* Shutdown sub-slots */
   for(i=0;i<CISCO_CARD_MAX_SUBSLOTS;i++)
      cisco_card_save_config(vm,card->sub_slots[i],fd);

   return(0);
}

/* Save config for all slots */
int vm_slot_save_all_config(vm_instance_t *vm,FILE *fd)
{
   int i;

   for(i=0;i<vm->nr_slots;i++)
      vm_slot_save_config(vm,i,fd);

   return(0);
}

/* Show slot drivers */
int vm_slot_show_drivers(vm_instance_t *vm)
{
   char *slot_type;
   int i;

   if (!vm->slots_drivers)
      return(-1);

   slot_type = cisco_card_get_type_desc(vm->slots_type);

   printf("Available %s %s drivers:\n",vm->platform->log_name,slot_type);

   for(i=0;vm->slots_drivers[i];i++) {
      printf("  * %s %s\n",
             vm->slots_drivers[i]->dev_type,
             !vm->slots_drivers[i]->supported ? "(NOT WORKING)" : "");
   }
   
   printf("\n");
   return(0);
}

/* Maximum number of tokens in a slot description */
#define SLOT_DESC_MAX_TOKENS  8

/* Create a Network Module (command line) */
int vm_slot_cmd_create(vm_instance_t *vm,char *str)
{
   char *tokens[SLOT_DESC_MAX_TOKENS];
   int i,count,res;
   u_int slot_id,port_id;

   /* A port adapter description is like "1:0:NM-1FE" */
   count = m_strsplit(str,':',tokens,SLOT_DESC_MAX_TOKENS);

   if ((count < 2) || (count > 3)) {
      vm_error(vm,"unable to parse slot description '%s'.\n",str);
      return(-1);
   }

   /* Parse the slot id */
   slot_id = atoi(tokens[0]);

   /* Parse the sub-slot id */
   if (count == 3)
      port_id = atoi(tokens[1]);
   else
      port_id = 0;

   /* Add this new slot to the current slot list */
   res = vm_slot_add_binding(vm,tokens[count-1],slot_id,port_id);

   /* The complete array was cleaned by strsplit */
   for(i=0;i<SLOT_DESC_MAX_TOKENS;i++)
      free(tokens[i]);

   return(res);
}

/* Add a Network IO descriptor binding (command line) */
int vm_slot_cmd_add_nio(vm_instance_t *vm,char *str)
{
   char *tokens[SLOT_DESC_MAX_TOKENS];
   int i,count,nio_type,res=-1;
   u_int slot_id,port_id;
   netio_desc_t *nio;
   char nio_name[128];

   /* A NIO binding description is like "1:3:tap:tap0" */
   if ((count = m_strsplit(str,':',tokens,SLOT_DESC_MAX_TOKENS)) < 3) {
      vm_error(vm,"unable to parse NIO description '%s'.\n",str);
      return(-1);
   }

   /* Parse the slot id */
   slot_id = atoi(tokens[0]);

   /* Parse the port id */
   port_id = atoi(tokens[1]);

   /* Autogenerate a NIO name */
   snprintf(nio_name,sizeof(nio_name),"%s-i%u/%u/%u",
            vm_get_type(vm),vm->instance_id,slot_id,port_id);

   /* Create the Network IO descriptor */
   nio = NULL;
   nio_type = netio_get_type(tokens[2]);

   switch(nio_type) {
      case NETIO_TYPE_UNIX:
         if (count != 5) {
            vm_error(vm,"invalid number of arguments for UNIX NIO '%s'\n",str);
            goto done;
         }

         nio = netio_desc_create_unix(nio_name,tokens[3],tokens[4]);
         break;

      case NETIO_TYPE_VDE:
         if (count != 5) {
            vm_error(vm,"invalid number of arguments for VDE NIO '%s'\n",str);
            goto done;
         }

         nio = netio_desc_create_vde(nio_name,tokens[3],tokens[4]);
         break;

      case NETIO_TYPE_TAP:
         if (count != 4) {
            vm_error(vm,"invalid number of arguments for TAP NIO '%s'\n",str);
            goto done;
         }

         nio = netio_desc_create_tap(nio_name,tokens[3]);
         break;

      case NETIO_TYPE_UDP:
         if (count != 6) {
            vm_error(vm,"invalid number of arguments for UDP NIO '%s'\n",str);
            goto done;
         }

         nio = netio_desc_create_udp(nio_name,atoi(tokens[3]),
                                     tokens[4],atoi(tokens[5]));
         break;

      case NETIO_TYPE_TCP_CLI:
         if (count != 5) {
            vm_error(vm,"invalid number of arguments for TCP CLI NIO '%s'\n",
                     str);
            goto done;
         }

         nio = netio_desc_create_tcp_cli(nio_name,tokens[3],tokens[4]);
         break;

      case NETIO_TYPE_TCP_SER:
         if (count != 4) {
            vm_error(vm,"invalid number of arguments for TCP SER NIO '%s'\n",
                     str);
            goto done;
         }

         nio = netio_desc_create_tcp_ser(nio_name,tokens[3]);
         break;

      case NETIO_TYPE_NULL:
         nio = netio_desc_create_null(nio_name);
         break;

#ifdef LINUX_ETH
      case NETIO_TYPE_LINUX_ETH:
         if (count != 4) {
            vm_error(vm,"invalid number of arguments for Linux Eth NIO '%s'\n",
                     str);
            goto done;
         }
         
         nio = netio_desc_create_lnxeth(nio_name,tokens[3]);
         break;
#endif

#ifdef GEN_ETH
      case NETIO_TYPE_GEN_ETH:
         if (count != 4) {
            vm_error(vm,
                     "invalid number of arguments for Generic Eth NIO '%s'\n",
                     str);
            goto done;
         }
         
         nio = netio_desc_create_geneth(nio_name,tokens[3]);
         break;
#endif

      default:
         vm_error(vm,"unknown NETIO type '%s'\n",tokens[2]);
         goto done;
   }

   if (!nio) {
      vm_error(vm,"unable to create NETIO descriptor for slot %u\n",slot_id);
      goto done;
   }

   if (vm_slot_add_nio_binding(vm,slot_id,port_id,nio_name) == -1) {
      vm_error(vm,"unable to add NETIO binding for slot %u\n",slot_id);
      netio_release(nio_name);
      netio_delete(nio_name);
      goto done;
   }
   
   netio_release(nio_name);
   res = 0;

 done:
   /* The complete array was cleaned by strsplit */
   for(i=0;i<SLOT_DESC_MAX_TOKENS;i++)
      free(tokens[i]);

   return(res);
}
