/*  
 * Cisco router Simulation Platform.
 * Copyright (c) 2005-2007 Christophe Fillot.  All rights reserved.
 *
 * EEPROM types:
 *   - 0x95: PA-POS-OC3SMI
 *   - 0x96: PA-POS-OC3MM
 *
 * Just an experimentation (I don't have any PA-POS-OC3).
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <assert.h>

#include "cpu.h"
#include "vm.h"
#include "dynamips.h"
#include "memory.h"
#include "device.h"
#include "net.h"
#include "net_io.h"
#include "ptask.h"
#include "dev_c7200.h"
#include "dev_plx.h"

/* Debugging flags */
#define DEBUG_ACCESS    0
#define DEBUG_UNKNOWN   0
#define DEBUG_TRANSMIT  0
#define DEBUG_RECEIVE   0

/* PCI vendor/product codes */
#define POS_OC3_PCI_VENDOR_ID    0x10b5
#define POS_OC3_PCI_PRODUCT_ID   0x9060

/* Maximum packet size */
#define POS_OC3_MAX_PKT_SIZE  8192

/* RX descriptors */
#define POS_OC3_RXDESC_OWN        0x80000000  /* Ownership */
#define POS_OC3_RXDESC_WRAP       0x40000000  /* Wrap ring */
#define POS_OC3_RXDESC_CONT       0x08000000  /* Packet continues */
#define POS_OC3_RXDESC_LEN_MASK   0x1fff

/* TX descriptors */
#define POS_OC3_TXDESC_OWN        0x80000000  /* Ownership */
#define POS_OC3_TXDESC_WRAP       0x40000000  /* Wrap ring */
#define POS_OC3_TXDESC_CONT       0x08000000  /* Packet continues */
#define POS_OC3_TXDESC_LEN_MASK   0x1fff

/* RX Descriptor */
struct rx_desc {
   m_uint32_t rdes[2];
};

/* TX Descriptor */
struct tx_desc {
   m_uint32_t tdes[2];
};

/* PA-POS-OC3 Data */
struct pos_oc3_data {
   char *name;

   /* IRQ clearing count */
   u_int irq_clearing_count;

   /* Control register #1 */
   m_uint16_t ctrl_reg1;

   /* CRC size */
   u_int crc_size;

   /* physical addresses for start and end of RX/TX rings */
   m_uint32_t rx_start,rx_end,tx_start,tx_end;
  
   /* physical addresses of current RX and TX descriptors */
   m_uint32_t rx_current,tx_current;

   /* Virtual machine */
   vm_instance_t *vm;

   /* Virtual devices */
   char *rx_name,*tx_name,*cs_name;
   vm_obj_t *rx_obj,*tx_obj,*cs_obj;
   struct vdevice rx_dev,tx_dev,cs_dev;

   /* PCI device information */
   struct vdevice dev;
   struct pci_device *pci_dev;

   /* NetIO descriptor */
   netio_desc_t *nio;

   /* TX ring scanner task id */
   ptask_id_t tx_tid;
};

/* Log a PA-POS-OC3 message */
#define POS_LOG(d,msg...) vm_log((d)->vm,(d)->name,msg)

/*
 * pos_access()
 */
static void *dev_pos_access(cpu_gen_t *cpu,struct vdevice *dev,
                            m_uint32_t offset,u_int op_size,u_int op_type,
                            m_uint64_t *data)
{
   __maybe_unused struct pos_oc3_data *d = dev->priv_data;

   if (op_type == MTS_READ)
      *data = 0;

#if DEBUG_ACCESS
   if (op_type == MTS_READ) {
      cpu_log(cpu,d->name,"read  access to offset = 0x%x, pc = 0x%llx\n",
              offset,cpu_get_pc(cpu));
   } else {
      if (offset != 0x404)
         cpu_log(cpu,d->name,"write access to vaddr = 0x%x, pc = 0x%llx, "
                 "val = 0x%llx\n",offset,cpu_get_pc(cpu),*data);
   }
#endif

   switch(offset) {
      case 0x404:
         if (op_type == MTS_READ)
            *data = 0xFFFFFFFF;
         break;
      case 0x406:
         if (op_type == MTS_READ)
            *data = 0xFFFFFFFF;
         break;
      case 0x407:
         if (op_type == MTS_READ)
            *data = 0xFFFFFFFF;
         break;

#if DEBUG_UNKNOWN
      default:
         if (op_type == MTS_READ) {
            cpu_log(cpu,d->name,
                    "read from unknown addr 0x%x, pc=0x%llx (size=%u)\n",
                    offset,cpu_get_pc(cpu),op_size);
         } else {
            cpu_log(cpu,d->name,
                    "write to unknown addr 0x%x, value=0x%llx, "
                    "pc=0x%llx (size=%u)\n",
                    offset,*data,cpu_get_pc(cpu),op_size);
         }
#endif
   }

   return NULL;
}

/*
 * pos_rx_access()
 */
static void *dev_pos_rx_access(cpu_gen_t *cpu,struct vdevice *dev,
                               m_uint32_t offset,u_int op_size,u_int op_type,
                               m_uint64_t *data)
{
   struct pos_oc3_data *d = dev->priv_data;

   if (op_type == MTS_READ)
      *data = 0;

#if DEBUG_ACCESS
   if (op_type == MTS_READ) {
      cpu_log(cpu,d->name,"read  access to offset = 0x%x, pc = 0x%llx\n",
              offset,cpu_get_pc(cpu));
   } else {
      cpu_log(cpu,d->name,"write access to vaddr = 0x%x, pc = 0x%llx, "
              "val = 0x%llx\n",offset,cpu_get_pc(cpu),*data);
   }
#endif

   switch(offset) {
      case 0x04:
         if (op_type == MTS_READ)
            *data = d->rx_start;
         else
            d->rx_start = *data;
         break;

      case 0x08:
         if (op_type == MTS_READ)
            *data = d->rx_current;
         else
            d->rx_current = *data;
         break;

#if DEBUG_UNKNOWN
      default:
         if (op_type == MTS_READ) {
            cpu_log(cpu,d->rx_name,
                    "read from unknown addr 0x%x, pc=0x%llx (size=%u)\n",
                    offset,cpu_get_pc(cpu),op_size);
         } else {
            cpu_log(cpu,d->rx_name,
                    "write to unknown addr 0x%x, value=0x%llx, "
                    "pc=0x%llx (size=%u)\n",
                    offset,*data,cpu_get_pc(cpu),op_size);
         }
#endif
   }

   return NULL;
}

/*
 * pos_tx_access()
 */
static void *dev_pos_tx_access(cpu_gen_t *cpu,struct vdevice *dev,
                               m_uint32_t offset,u_int op_size,u_int op_type,
                               m_uint64_t *data)
{
   struct pos_oc3_data *d = dev->priv_data;

   if (op_type == MTS_READ)
      *data = 0;

#if DEBUG_ACCESS
   if (op_type == MTS_READ) {
      cpu_log(cpu,d->tx_name,"read  access to offset = 0x%x, pc = 0x%llx\n",
              offset,cpu_get_pc(cpu));
   } else {
      cpu_log(cpu,d->tx_name,"write access to vaddr = 0x%x, pc = 0x%llx, "
              "val = 0x%llx\n",offset,cpu_get_pc(cpu),*data);
   }
#endif

   switch(offset) {
     case 0x04:
         if (op_type == MTS_READ)
            *data = d->tx_start;
         else
            d->tx_start = *data;
         break;

      case 0x08:
         if (op_type == MTS_READ)
            *data = d->tx_current;
         else
            d->tx_current = *data;
         break;

#if DEBUG_UNKNOWN
      default:
         if (op_type == MTS_READ) {
            cpu_log(cpu,d->tx_name,
                    "read from unknown addr 0x%x, pc=0x%llx (size=%u)\n",
                    offset,cpu_get_pc(cpu),op_size);
         } else {
            cpu_log(cpu,d->tx_name,
                    "write to unknown addr 0x%x, value=0x%llx, "
                    "pc=0x%llx (size=%u)\n",
                    offset,*data,cpu_get_pc(cpu),op_size);
         }
#endif
   }

   return NULL;
}

/*
 * pos_cs_access()
 */
static void *dev_pos_cs_access(cpu_gen_t *cpu,struct vdevice *dev,
                               m_uint32_t offset,u_int op_size,u_int op_type,
                               m_uint64_t *data)
{
   struct pos_oc3_data *d = dev->priv_data;

   if (op_type == MTS_READ)
      *data = 0;

#if DEBUG_ACCESS
   if (op_type == MTS_READ) {
      cpu_log(cpu,d->cs_name,"read  access to offset = 0x%x, pc = 0x%llx\n",
              offset,cpu_get_pc(cpu));
   } else {
      cpu_log(cpu,d->cs_name,"write access to vaddr = 0x%x, pc = 0x%llx, "
              "val = 0x%llx\n",offset,cpu_get_pc(cpu),*data);
   }
#endif

   switch(offset) {
      case 0x300000:
      case 0x300004:
      case 0x30001c:
         if (op_type == MTS_READ) {
            *data = 0x00000FFF;

            /* Add a delay before clearing the IRQ */
            if (++d->irq_clearing_count == 20) {
               pci_dev_clear_irq(d->vm,d->pci_dev);
               d->irq_clearing_count = 0;
            }
         }     
         break;

      case 0x300008:
         if (op_type == MTS_READ)
            *data = 0x000007F;      
         break;

      case 0x300028:
         if (op_type == MTS_READ) {
            *data = d->ctrl_reg1;
         } else {
            d->ctrl_reg1 = *data;

            switch(*data) {
               case 0x06:
                  d->crc_size = 2;
                  break;
               case 0x07:
                  d->crc_size = 4;
                  break;
               default:
                  d->crc_size = 2;
                  cpu_log(cpu,d->cs_name,
                          "unknown value 0x%4.4llx written in ctrl_reg1\n",
                          *data);
            }
            cpu_log(cpu,d->cs_name,"CRC size set to 0x%4.4x\n",d->crc_size);
         }
         break;

#if DEBUG_UNKNOWN
      default:
         if (op_type == MTS_READ) {
            cpu_log(cpu,d->cs_name,
                    "read from unknown addr 0x%x, pc=0x%llx (size=%u)\n",
                    offset,cpu_get_pc(cpu),op_size);
         } else {
            cpu_log(cpu,d->cs_name,
                    "write to unknown addr 0x%x, value=0x%llx, "
                    "pc=0x%llx (size=%u)\n",
                    offset,*data,cpu_get_pc(cpu),op_size);
         }
#endif
   }

   return NULL;
}

/*
 * Get the address of the next RX descriptor.
 */
static m_uint32_t rxdesc_get_next(struct pos_oc3_data *d,m_uint32_t rxd_addr,
                                  struct rx_desc *rxd)
{
   m_uint32_t nrxd_addr;

   if (rxd->rdes[0] & POS_OC3_RXDESC_WRAP)
      nrxd_addr = d->rx_start;
   else
      nrxd_addr = rxd_addr + sizeof(struct rx_desc);

   return(nrxd_addr);
}

/* Read an RX descriptor */
static void rxdesc_read(struct pos_oc3_data *d,m_uint32_t rxd_addr,
                        struct rx_desc *rxd)
{
#if DEBUG_RECEIVE
   POS_LOG(d,"reading RX descriptor at address 0x%x\n",rxd_addr);
#endif

   /* get the next descriptor from VM physical RAM */
   physmem_copy_from_vm(d->vm,rxd,rxd_addr,sizeof(struct rx_desc));

   /* byte-swapping */
   rxd->rdes[0] = vmtoh32(rxd->rdes[0]);
   rxd->rdes[1] = vmtoh32(rxd->rdes[1]);
}

/* 
 * Try to acquire the specified RX descriptor. Returns TRUE if we have it.
 * It assumes that the byte-swapping is done.
 */
static inline int rxdesc_acquire(m_uint32_t rdes0)
{
   return(rdes0 & POS_OC3_RXDESC_OWN);
}

/* Put a packet in buffer of a descriptor */
static ssize_t rxdesc_put_pkt(struct pos_oc3_data *d,struct rx_desc *rxd,
                              u_char **pkt,ssize_t *pkt_len)
{
   ssize_t len,cp_len;

   len = rxd->rdes[0] & POS_OC3_RXDESC_LEN_MASK;

   /* compute the data length to copy */
   cp_len = m_min(len,*pkt_len);

#if DEBUG_RECEIVE
   POS_LOG(d,"copying %d bytes at 0x%x\n",cp_len,rxd->rdes[1]);
#endif
      
   /* copy packet data to the VM physical RAM */
   physmem_copy_to_vm(d->vm,*pkt,rxd->rdes[1],cp_len);
      
   *pkt += cp_len;
   *pkt_len -= cp_len;
   return(cp_len);
}

/*
 * Put a packet in the RX ring.
 */
static void dev_pos_oc3_receive_pkt(struct pos_oc3_data *d,
                                    u_char *pkt,ssize_t pkt_len)
{
   m_uint32_t rx_start,rxdn_addr,rxdn_rdes0;
   struct rx_desc rxd0,rxdn,*rxdc;
   ssize_t cp_len,tot_len = pkt_len;
   u_char *pkt_ptr = pkt;
   int i;

   if (d->rx_start == 0)
      return;

   /* Truncate the packet if it is too big */
   pkt_len = m_min(pkt_len,POS_OC3_MAX_PKT_SIZE);

   /* Copy the current rxring descriptor */
   rxdesc_read(d,d->rx_current,&rxd0);
   
   /* We must have the first descriptor... */
   if (!rxdesc_acquire(rxd0.rdes[0]))
      return;

   /* Remember the first RX descriptor address */
   rx_start = d->rx_current;

   for(i=0,rxdc=&rxd0;tot_len>0;i++)
   {
      /* Put data into the descriptor buffers */
      cp_len = rxdesc_put_pkt(d,rxdc,&pkt_ptr,&tot_len);

      /* Get address of the next descriptor */
      rxdn_addr = rxdesc_get_next(d,d->rx_current,rxdc);

      /* We have finished if the complete packet has been stored */
      if (tot_len == 0) {
         rxdc->rdes[0] = (cp_len + d->crc_size);

         if (i != 0)
            physmem_copy_u32_to_vm(d->vm,d->rx_current,rxdc->rdes[0]);

         d->rx_current = rxdn_addr;
         break;
      }

#if DEBUG_RECEIVE
      POS_LOG(d,"trying to acquire new descriptor at 0x%x\n",rxdn_addr);
#endif
      /* Get status of the next descriptor to see if we can acquire it */
      rxdn_rdes0 = physmem_copy_u32_from_vm(d->vm,rxdn_addr);

      if (!rxdesc_acquire(rxdn_rdes0))
         rxdc->rdes[0] = 0;  /* error, no buf available (special flag?) */
      else
         rxdc->rdes[0] = POS_OC3_RXDESC_CONT;  /* packet continues */

      rxdc->rdes[0] |= cp_len;

      /* Update the new status (only if we are not on the first desc) */
      if (i != 0)
         physmem_copy_u32_to_vm(d->vm,d->rx_current,rxdc->rdes[0]);

      /* Update the RX pointer */
      d->rx_current = rxdn_addr;

      if (!(rxdc->rdes[0] & POS_OC3_RXDESC_CONT))
         break;

      /* Read the next descriptor from VM physical RAM */
      rxdesc_read(d,rxdn_addr,&rxdn);
      rxdc = &rxdn;
   }

   /* Update the first RX descriptor */
   physmem_copy_u32_to_vm(d->vm,rx_start,rxd0.rdes[0]);

   /* Generate IRQ on CPU */
   pci_dev_trigger_irq(d->vm,d->pci_dev);
}

/* Handle the RX ring */
static int dev_pos_oc3_handle_rxring(netio_desc_t *nio,
                                     u_char *pkt,ssize_t pkt_len,
                                     struct pos_oc3_data *d)
{
#if DEBUG_RECEIVE
   POS_LOG(d,"receiving a packet of %d bytes\n",pkt_len);
   mem_dump(log_file,pkt,pkt_len);
#endif

   dev_pos_oc3_receive_pkt(d,pkt,pkt_len);
   return(TRUE);
}

/* Read a TX descriptor */
static void txdesc_read(struct pos_oc3_data *d,m_uint32_t txd_addr,
                        struct tx_desc *txd)
{
   /* get the next descriptor from VM physical RAM */
   physmem_copy_from_vm(d->vm,txd,txd_addr,sizeof(struct tx_desc));

   /* byte-swapping */
   txd->tdes[0] = vmtoh32(txd->tdes[0]);
   txd->tdes[1] = vmtoh32(txd->tdes[1]);
}

/* Set the address of the next TX descriptor */
static void txdesc_set_next(struct pos_oc3_data *d,struct tx_desc *txd)
{
   if (txd->tdes[0] & POS_OC3_TXDESC_WRAP)
      d->tx_current = d->tx_start;
   else
      d->tx_current += sizeof(struct tx_desc);
}

/* Handle the TX ring */
static int dev_pos_oc3_handle_txring(struct pos_oc3_data *d)
{
   u_char pkt[POS_OC3_MAX_PKT_SIZE],*pkt_ptr;
   m_uint32_t clen,tot_len,norm_len;
   m_uint32_t tx_start,addr;
   struct tx_desc txd0,ctxd,*ptxd;
   int i,done = FALSE;

   if ((d->tx_start == 0) || (d->nio == NULL))
      return(FALSE);

   /* Copy the current txring descriptor */
   tx_start = d->tx_current;   
   ptxd = &txd0;
   txdesc_read(d,d->tx_current,ptxd);

   /* If we don't own the descriptor, we cannot transmit */
   if (!(txd0.tdes[0] & POS_OC3_TXDESC_OWN))
      return(FALSE);

#if DEBUG_TRANSMIT
   POS_LOG(d,"pos_oc3_handle_txring: 1st desc: tdes[0]=0x%x, tdes[1]=0x%x\n",
           ptxd->tdes[0],ptxd->tdes[1]);
#endif

   pkt_ptr = pkt;
   tot_len = 0;
   i = 0;

   do {
#if DEBUG_TRANSMIT
      POS_LOG(d,"pos_oc3_handle_txring: loop: tdes[0]=0x%x, tdes[1]=0x%x\n",
              ptxd->tdes[0],ptxd->tdes[1]);
#endif

      if (!(ptxd->tdes[0] & POS_OC3_TXDESC_OWN)) {
         POS_LOG(d,"pos_oc3_handle_txring: descriptor not owned!\n");
         return(FALSE);
      }

      clen = ptxd->tdes[0] & POS_OC3_TXDESC_LEN_MASK;

      /* Be sure that we have length not null */
      if (clen != 0) {
         addr = ptxd->tdes[1];

         norm_len = normalize_size(clen,4,0);
         physmem_copy_from_vm(d->vm,pkt_ptr,addr,norm_len);
         mem_bswap32(pkt_ptr,norm_len);
      }

      pkt_ptr += clen;
      tot_len += clen;

      /* Clear the OWN bit if this is not the first descriptor */
      if (i != 0)
         physmem_copy_u32_to_vm(d->vm,d->tx_current,0);

      /* Go to the next descriptor */
      txdesc_set_next(d,ptxd);

      /* Copy the next txring descriptor */
      if (ptxd->tdes[0] & POS_OC3_TXDESC_CONT) {
         txdesc_read(d,d->tx_current,&ctxd);
         ptxd = &ctxd;
         i++;
      } else
         done = TRUE;
   }while(!done);

   if (tot_len != 0) {
#if DEBUG_TRANSMIT
      POS_LOG(d,"sending packet of %u bytes (flags=0x%4.4x)\n",
              tot_len,txd0.tdes[0]);
      mem_dump(log_file,pkt,tot_len);
#endif   
      /* send it on wire */
      netio_send(d->nio,pkt,tot_len);
   }

   /* Clear the OWN flag of the first descriptor */
   txd0.tdes[0] &= ~POS_OC3_TXDESC_OWN;
   physmem_copy_u32_to_vm(d->vm,tx_start,txd0.tdes[0]);

   /* Interrupt on completion */
   pci_dev_trigger_irq(d->vm,d->pci_dev);
   return(TRUE);
}

/*
 * pci_pos_read()
 */
static m_uint32_t pci_pos_read(cpu_gen_t *cpu,struct pci_device *dev,int reg)
{
   struct pos_oc3_data *d = dev->priv_data;

#if DEBUG_ACCESS
   POS_LOG(d,"read PCI register 0x%x\n",reg);
#endif

   switch(reg) {
      case PCI_REG_BAR0:
         return(d->dev.phys_addr);
      default:
         return(0);
   }
}

/*
 * pci_pos_write()
 */
static void pci_pos_write(cpu_gen_t *cpu,struct pci_device *dev,
                          int reg,m_uint32_t value)
{
   struct pos_oc3_data *d = dev->priv_data;

#if DEBUG_ACCESS
   POS_LOG(d,"write 0x%x to PCI register 0x%x\n",value,reg);
#endif

   switch(reg) {
      case PCI_REG_BAR0:
         vm_map_device(cpu->vm,&d->dev,(m_uint64_t)value);
         POS_LOG(d,"registers are mapped at 0x%x\n",value);
         break;
   }
}

/*
 * dev_c7200_pa_pos_init()
 *
 * Add a PA-POS port adapter into specified slot.
 */
int dev_c7200_pa_pos_init(vm_instance_t *vm,struct cisco_card *card)
{
   struct pos_oc3_data *d;
   u_int slot = card->slot_id;

   /* Allocate the private data structure for PA-POS-OC3 chip */
   if (!(d = malloc(sizeof(*d)))) {
      vm_error(vm,"%s: out of memory\n",card->dev_name);
      return(-1);
   }

   memset(d,0,sizeof(*d));
   d->name = card->dev_name;
   d->vm   = vm;

   /* Set the PCI bus */
   card->pci_bus = vm->slots_pci_bus[slot];

   /* Set the EEPROM */
   cisco_card_set_eeprom(vm,card,cisco_eeprom_find_pa("PA-POS-OC3"));
   c7200_set_slot_eeprom(VM_C7200(vm),slot,&card->eeprom);

   /* Initialize RX device */
   d->rx_name = dyn_sprintf("%s_RX",card->dev_name);
   dev_init(&d->rx_dev);
   d->rx_dev.name      = d->rx_name;
   d->rx_dev.priv_data = d;
   d->rx_dev.handler   = dev_pos_rx_access;

   /* Initialize TX device */
   d->tx_name = dyn_sprintf("%s_TX",card->dev_name);
   dev_init(&d->tx_dev);
   d->tx_dev.name      = d->tx_name;
   d->tx_dev.priv_data = d;
   d->tx_dev.handler   = dev_pos_tx_access;

   /* Initialize CS device */
   d->cs_name = dyn_sprintf("%s_CS",card->dev_name);
   dev_init(&d->cs_dev);
   d->cs_dev.name      = d->cs_name;
   d->cs_dev.priv_data = d;
   d->cs_dev.handler   = dev_pos_cs_access;

   /* Initialize PLX9060 for RX part */
   d->rx_obj = dev_plx9060_init(vm,d->rx_name,card->pci_bus,0,&d->rx_dev);

   /* Initialize PLX9060 for TX part */
   d->tx_obj = dev_plx9060_init(vm,d->tx_name,card->pci_bus,1,&d->tx_dev);

   /* Initialize PLX9060 for CS part (CS=card status, chip status, ... ?) */
   d->cs_obj = dev_plx9060_init(vm,d->cs_name,card->pci_bus,2,&d->cs_dev);

   /* Unknown PCI device here (will be mapped at 0x30000) */
   dev_init(&d->dev);
   d->dev.name      = card->dev_name;
   d->dev.priv_data = d;
   d->dev.phys_len  = 0x10000;
   d->dev.handler   = dev_pos_access;

   d->pci_dev = pci_dev_add(card->pci_bus,card->dev_name,0,0,3,0,
                            c7200_net_irq_for_slot_port(slot,0),
                            d,NULL,pci_pos_read,pci_pos_write);

   /* Store device info into the router structure */
   card->drv_info = d;
   return(0);
}

/* Remove a PA-POS-OC3 from the specified slot */
int dev_c7200_pa_pos_shutdown(vm_instance_t *vm,struct cisco_card *card)
{
   struct pos_oc3_data *d = card->drv_info;

   /* Remove the PA EEPROM */
   cisco_card_unset_eeprom(card);
   c7200_set_slot_eeprom(VM_C7200(vm),card->slot_id,NULL);

   /* Remove the PCI device */
   pci_dev_remove(d->pci_dev);

   /* Remove the PLX9060 chips */
   vm_object_remove(vm,d->rx_obj);
   vm_object_remove(vm,d->tx_obj);
   vm_object_remove(vm,d->cs_obj);

   /* Remove the devices from the CPU address space */
   vm_unbind_device(vm,&d->rx_dev);
   vm_unbind_device(vm,&d->tx_dev);
   vm_unbind_device(vm,&d->cs_dev);

   vm_unbind_device(vm,&d->dev);
   cpu_group_rebuild_mts(vm->cpu_group);

   /* Free the device structure itself */
   free(d);
   return(0);
}

/* Bind a Network IO descriptor to a specific port */
int dev_c7200_pa_pos_set_nio(vm_instance_t *vm,struct cisco_card *card,
                             u_int port_id,netio_desc_t *nio)
{
   struct pos_oc3_data *d = card->drv_info;

   if (!d || (port_id > 0))
      return(-1);

   if (d->nio != NULL)
      return(-1);

   d->nio = nio;
   d->tx_tid = ptask_add((ptask_callback)dev_pos_oc3_handle_txring,d,NULL);
   netio_rxl_add(nio,(netio_rx_handler_t)dev_pos_oc3_handle_rxring,d,NULL);
   return(0);
}

/* Bind a Network IO descriptor to a specific port */
int dev_c7200_pa_pos_unset_nio(vm_instance_t *vm,struct cisco_card *card,
                               u_int port_id)
{
   struct pos_oc3_data *d = card->drv_info;

   if (!d || (port_id > 0))
      return(-1);

   if (d->nio) {
      ptask_remove(d->tx_tid);
      netio_rxl_remove(d->nio);
      d->nio = NULL;
   }
   return(0);
}

/* PA-POS-OC3 driver */
struct cisco_card_driver dev_c7200_pa_pos_oc3_driver = {
   "PA-POS-OC3", 1, 0,
   dev_c7200_pa_pos_init,
   dev_c7200_pa_pos_shutdown,
   NULL,
   dev_c7200_pa_pos_set_nio,
   dev_c7200_pa_pos_unset_nio,
   NULL,
};
