/* VA device send handler subroutine called through netJobAdd(). */
static void vxworks_va_send_sub(END_OBJ *end, M_BLK_ID m)
{
  VxWorksVa *va = (void *)end;
  SshInterceptorInternalPacket ipp;

  vxworks_va_sends_processed++;

  if (!va->attached)
    {
      SSH_TRACE(SSH_D_ERROR, ("%s: not attached, dropping packet", va->name));
      m_freem(m);
      return;
    }

  if (!va->packet_cb)
    {
      SSH_TRACE(SSH_D_ERROR, ("%s: no packet_cb, dropping packet", va->name));
      m_freem(m);
      return;
    }

  if (!(ipp =
        ssh_interceptor_packet_alloc_header(
          va->interceptor,
          SSH_PACKET_FROMPROTOCOL,
          SSH_PROTOCOL_ETHERNET,
          va->ifnum,
          SSH_INTERCEPTOR_INVALID_IFNUM)))
    {
      SSH_TRACE(
        SSH_D_ERROR,
        ("%s: out of interceptor headers, dropping packet", va->name));
      m_freem(m);
      return;
    }

  ipp->head = m;

  va->packet_cb(
    va->interceptor, (void *)ipp, va->adapter_context);
}
/* Netdevice low level transmit callback function. */
static int
ssh_virtual_adapter_xmit(struct sk_buff *skbp, 
			 struct net_device *dev)
{
  struct net_device_stats *stats;
  SshInterceptorInternalPacket ipp;
  SshInterceptor interceptor;
  SshVirtualAdapter adapter;
  SshInterceptorIfnum ifnum_in;
  SshVirtualAdapterPacketCB packet_cb;
  void *adapter_context;

  SSH_ASSERT(skbp != NULL && dev != NULL);

  interceptor = ssh_interceptor_context;
  
  ssh_kernel_mutex_lock(interceptor->interceptor_lock);
  adapter = (SshVirtualAdapter) SSH_LINUX_NET_DEVICE_PRIV(dev);
  if (adapter == NULL)
    {
      /* Virtual adapter is not attached. */
      ssh_kernel_mutex_unlock(interceptor->interceptor_lock);
      SSH_DEBUG(SSH_D_NICETOKNOW, 
		("Device %d [%s] is not attached to a SshVirtualAdapter",
		 dev->ifindex, dev->name));
    discard:
      /* Silently discard the packet. */
      dev_kfree_skb_any(skbp);
      return NET_XMIT_SUCCESS;
    }

  /* Update statistics */
  stats = ssh_virtual_adapter_get_stats(dev);
  SSH_ASSERT(stats != NULL);
  stats->tx_packets++;

  if (!adapter->initialized || !adapter->packet_cb)
    {
      /* This is not very uncommon. Packets end up here if the virtual
	 adapter is set up when policymanager is not running. We discard
	 the packets silently, as otherwise the stack will attempt to 
	 transmit IPv6 IGMP messages indefinitely. */
      ssh_kernel_mutex_unlock(interceptor->interceptor_lock);
      SSH_DEBUG(SSH_D_LOWOK,
		("Virtual adapter %d [%s] not initialized / no cb.",
		 adapter->dev->ifindex, adapter->dev->name));
      goto discard;
    }

  ifnum_in = (SshInterceptorIfnum) dev->ifindex;
  SSH_LINUX_ASSERT_VALID_IFNUM(ifnum_in);

  /* Pass the packet to the packet callback. */
  ipp = ssh_interceptor_packet_alloc_header(interceptor,
				 SSH_PACKET_FROMPROTOCOL,
				 SSH_PROTOCOL_ETHERNET, 
				 ifnum_in,
				 SSH_INTERCEPTOR_INVALID_IFNUM,
				 skbp,
                                 FALSE, FALSE, TRUE);
  if (ipp == NULL)
    {
      SSH_DEBUG(SSH_D_NICETOKNOW, 
		("Could not allocate packet header, virtual adapter %d [%s]",
		 adapter->dev->ifindex, adapter->dev->name));
      stats->tx_errors++;
      ssh_kernel_mutex_unlock(interceptor->interceptor_lock);
      return NET_XMIT_DROP;
    }

  stats->tx_bytes += ipp->skb->len;

  packet_cb = adapter->packet_cb;
  adapter_context = adapter->adapter_context;

  ssh_kernel_mutex_unlock(interceptor->interceptor_lock);

  SSH_DEBUG(SSH_D_NICETOKNOW, 
	    ("Passing skb %p from virtual adapter %d [%s] to engine", 
	     ipp->skb, (int)ifnum_in, dev->name));
  
  /* Call the callback.  This will eventually free `pp'. */
  (*packet_cb)(interceptor, (SshInterceptorPacket) ipp, adapter_context);
  
  return NET_XMIT_SUCCESS;
}