#pragma module	PCAPVCM "X-1"
#pragma code_psect "EXEC$NONPAGED_CODE"
#pragma linkage_psect "EXEC$NONPAGED_LINKAGE"

//***************************************************************************
//*                                                                         *
//*  © Copyright 2003 Hewlett-Packard Development Company, L.P.             *
//*								            *
//*  Confidential computer software.  Valid license from HP and/or          *
//*  its subsidiaries required for possession, use, or copying.             *
//*                                                                         *
//*  Consistent with FAR 12.211 and 12.212, Commercial Computer Software,   *
//*  Computer Software Documentation, and Technical Data for Commercial     *
//*  Items are licensed to the U.S. Government under vendor's standard      *
//*  commercial license.                                                    *
//*                                                                         *
//*  Neither HP nor any of its subsidiaries shall be liable for technical   *
//*  or editorial errors or omissions contained herein.  The information    *
//*  in this document is provided "as is" without warranty of any kind and  *
//*  is subject to change without notice.  The warranties for HP products   *
//*  are set forth in the express limited warranty statements accompanying  *
//*  such products.  Nothing herein should be construed as constituting an  *
//*  additional warranty.                                                   *
//*                                                                         *
//***************************************************************************
//
//
// FACILITY:
//
//      Dynamic Loadable Execlet for PCAP
//
//
// ABSTRACT:
//
//	This module implements a VCM for the LAN driver
// 
//
// AUTHOR:
//
//	Ankan
//
//  CREATION DATE:  21-Mar-2003
//
//  DESIGN ISSUES:
//
//      All data passed to this execlet is assumed to be correct.
//	No probes/range checks are made. Any failliure to read/write
//	data passed into the execlet will cause a system crash.
//
// *****************************************************************************
// *
// * Important Note:
// *
// *     This code uses privileged OpenVMS interfaces.
// *     OpenVMS does not guarantee that these interfaces will
// *     be supported indefinitely, and may change these interfaces
// *     without prior notice.
// *
// *****************************************************************************
//
//
// REVISION HISTORY:
//
//      X-1     Ankan			Anders Ahgren		21-Mar-2003
//              Initial version.
//

//
// Imported definitions
//
#define __NEW_STARLET 1
#include <starlet.h>
#include <string.h>
#include <stdlib.h>
#include <lanudef.h>
#include <ldcdef.h>
#include <vcibdef.h>
#include <vcibdlldef.h>
#include <vcrpdef.h>
#include <vcrplandef.h>
#include <lildef.h>
#include <dyndef.h>
#include <ints.h>
#include <ssdef.h>
#include <vms_macros.h>
#include <vms_drivers.h>
#include <builtins.h>
#include <nmadef.h>
#include <boostatedef.h>
#include <chfdef.h>
#include <exe_routines.h>
#include <ldr_routines.h>
#include <ldrimgdef.h>
#include <pdscdef.h>
#include <psldef.h>
#include <inirtndef.h>
#include <gen64def.h>

#include "pcapvcm.h"

//
// Load/unload stuff...
//
int pcap$vcm_init(  LDRIMG *ini_image_block,
		    INIRTN *ini_flags_addr,
		    const char *ini_user_buffer );
int pcap$vcm_unload();

//
// Execlet interface routine declarations
//
int pcap$vcm_get_context(PCAPVCM_64PP vcm);
int pcap$vcm_alloc_port(VCMCTX_64PP vcmctx);
int pcap$vcm_free_port(VCMCTX_64P vcmctx);
int pcap$vcm_getdevice(VCMCTX_64P vcmctx, CHAR_64P devnam);
int pcap$vcm_create_port(VCMCTX_64P vcmctx, CHAR_64P device);
int pcap$vcm_delete_port(VCMCTX_64P vcmctx);
int pcap$vcm_enable_port(VCMCTX_64P vcmctx, int p2len, CHAR_64P p2buf);
int pcap$vcm_disable_port(VCMCTX_64P vcmctx);
int pcap$vcm_get_mgm_error(VCMCTX_64P vcmctx, CHAR_64P error);
int pcap$vcm_get_last_error(VCMCTX_64P vcmctx);
int pcap$vcm_read_packet(VCMCTX_64P vcmctx, int len, CHAR_64P packet);
int pcap$vcm_send_packet(VCMCTX_64P vcmctx, int hdrlen, int len,
    CHAR_64P rawpacket);
int pcap$vcm_build_header(VCMCTX_64P vcmctx, int len, CHAR_64P header);
int pcap$vcm_get_statistics(VCMCTX_64P vcmctx, CHAR_64P stats);

//
// Special linkage
//
#define	INIT001_ROUTINE pcap$vcm_init
#include		init_rtn_setup

//
// VCI callback routines. Not to funny, thse are needed because these routines
// are called via JSBs from the LAN driver (LAN.MAR).
//
/* Transmit Complete */
#pragma linkage pcap_txLnkg = (parameters(r4,r3), preserved(r2,r4,r5), nopreserve(r0,r1))
#pragma use_linkage pcap_txLnkg (pcap_txCompl)
void pcap_txCompl( VCIBDLLDEF *vcib, VCRPDEF *request );

/* PortMgmt Complete */
#pragma linkage pcap_mgmLnkg = (parameters(r4,r3), preserved(r2,r4,r5), nopreserve(r0,r1))
#pragma use_linkage pcap_mgmLnkg (pcap_mgmCompl)
void pcap_mgmCompl( VCIBDLLDEF *vcib, VCRPDEF *request );

/* Receive */
#pragma linkage pcap_rxLnkg = (parameters(r4,r3), preserved(r2,r4,r5), nopreserve(r0,r1))
#pragma use_linkage pcap_rxLnkg (pcap_rxCompl)
void pcap_rxCompl( VCIBDEF *vcib, VCRPDEF *request );

/* Events */
#pragma linkage pcap_evtLnkg = (parameters(r4,r1, r2), preserved(r5), nopreserve(r0,r1))
#pragma use_linkage pcap_evtLnkg (pcap_event)
void pcap_event( VCIBDLLDEF *vcib, int event, int reason );


//
// External variables
//
extern uint64 exe$gq_systime;

//
// Global variables
//
#pragma extern_model save
#pragma extern_model strict_refdef "EXEC$NONPAGED_DATA"
static int unlvec[4];
#pragma __required_pointer_size __save
#pragma __required_pointer_size __long  
static	PCAPVCM	*pcapvcm = 0;
#pragma __required_pointer_size __restore  
#pragma extern_model restore


//
// Note: we use the VCIB as a queue for VCRBs and to keep track of the
// size of the queue we use the size field to hold the number of elements
// in the queue.
//
int init_vcib(VCIBDLLDEF *vcib, LILDEF *lil)
{
    int status = SS$_NORMAL;
    VCIBDEF *vcib_base;

    vcib_base = (VCIBDEF *) vcib;
    memset(vcib, 0, sizeof(VCIBDLLDEF));

    vcib_base->vcib$a_portmgmt_complete = (void *) pcap_mgmCompl;
    vcib_base->vcib$a_receive_complete = (void *) pcap_rxCompl;
    vcib_base->vcib$a_report_event = (void *) pcap_event;
    vcib_base->vcib$a_transmit_complete = (void *) pcap_txCompl;
    vcib_base->vcib$b_type = DYN$C_DECNET;
    vcib_base->vcib$b_sub_type = DYN$C_NET_VCI_VCIB;
    vcib_base->vcib$l_vci_id = 0x0101;
    vcib_base->vcib$w_version_upper = 1;
    vcib->vcib$a_dll_input_list = lil;
    vcib->vcib$w_dll_hdr_size = LAN$C_MAX_HDR_SIZE; // Max out...
    vcib->vcib$v_lan_ftc = 1;	// Always call completion routine
    return status;
}

int init_mgmt_vcrp(VCRPLANDEF *vcrplan, int func, int p2len, char **p2buf)
{
    int status = 1;
    VCRPDEF *vcrp;

    vcrp = (VCRPDEF *) vcrplan;
    vcrp->vcrp$b_type = DYN$C_VCRP;
    vcrp->vcrp$v_cmn_mgmt = 1;
    vcrp->vcrp$l_function = func;
    vcrplan->vcrp$a_lan_p2buff = p2buf;
    vcrplan->vcrp$l_lan_p2buff_size = p2len;

    return status;
}


int init_transmit_vcrp(VCRPLANDEF *vcrplan)
{
    int status = 1;
    VCRPDEF *vcrp;
    
    vcrp = (VCRPDEF *) vcrplan;
    vcrp->vcrp$b_type = DYN$C_VCRP;
    vcrp->vcrp$v_cmn_mgmt = 0;
    vcrp->vcrp$l_function = VCRP$K_FC_TRANSMIT;

    return status;
}


//
//	This is the execlet initialization routine, which is called upon
//	loading of this image.
//
int pcap$vcm_init (LDRIMG *ini_image_block,
		    INIRTN *ini_flags_addr,
		    const char *ini_user_buffer )
{
    int	status;
    uint64	alloc_size;

    //
    // Make sure we are not called again
    //
    ini_flags_addr->inirtn$v_no_recall = 1;

    //
    // We need to do some cleanup if we ever get unloaded, so declare an
    // unload vector and pass our unload routine
    //
    unlvec[0] = (int) pcap$vcm_unload;
    unlvec[1] = 0;
    unlvec[2] = 0;
    unlvec[3] = 0;
    ini_image_block->ldrimg$l_unlvec = unlvec;

    //
    // Allocate a 2 pages for our shared data structure
    // with our companion, the PCAP library.
    //
    status = mmg_std$alloc_system_va_map (
		    PTE$C_UW | PTE$M_ASM,		// User mode RW
		    2,					// number of pages
		    1,					// nonpaged
		    1,					// S1 space
		    (VOID_PPQ) &pcapvcm );
    if (!$VMS_STATUS_SUCCESS(status))
      bug_check (CUSTOMER, FATAL, COLD);

    //
    // Initialize the data structure...
    //
    memset (pcapvcm, 0, mmg$gl_page_size);
    pcapvcm->mbo = 1;
    pcapvcm->type = DYN$C_MISC;
    pcapvcm->subtype = DYN$C_MISC;
    pcapvcm->size = sizeof(PCAPVCM);
    pcapvcm->revision = PCAPVCM$K_REVISION;
    pcapvcm->recv_queue_size = PCAPVCM$K_RECV_QUEUE_SIZE;
    pcapvcm->retry_count = PCAPVCM$K_RECV_QUEUE_RETRY;
    pcapvcm->get_context = pcap$vcm_get_context;
    pcapvcm->alloc_port = pcap$vcm_alloc_port;
    pcapvcm->free_port = pcap$vcm_free_port;
    pcapvcm->get_device = pcap$vcm_getdevice;
    pcapvcm->create_port = pcap$vcm_create_port;
    pcapvcm->delete_port = pcap$vcm_delete_port;
    pcapvcm->enable_port = pcap$vcm_enable_port;
    pcapvcm->disable_port = pcap$vcm_disable_port;
    pcapvcm->get_mgm_error = pcap$vcm_get_mgm_error;
    pcapvcm->get_last_error = pcap$vcm_get_last_error;
    pcapvcm->read_packet = pcap$vcm_read_packet;
    pcapvcm->send_packet = pcap$vcm_send_packet;
    pcapvcm->build_header = pcap$vcm_build_header;
    pcapvcm->get_statistics = pcap$vcm_get_statistics;

    return SS$_NORMAL;
}



//
//	This unload routine will be automagically called during execlet
//	unloading to perform the cleanup steps.
// NOTE - since I'm useless at building execlets this currently will
// cause the system to crash, because I have pageable psects.
int pcap$vcm_unload ()
{
    int status;
    int pages;
    int cpuidx;
    uint64 delta;
    GENERIC_64 delta_time;

    //
    // Is the pointer good to the shared data?
    //
    if ( pcapvcm != 0 ) {

	//
	// Finally get rid of the shared data block
	//
	mmg_std$dealloc_sva ( 2, pcapvcm );
	pcapvcm = 0;
    }

    return SS$_NORMAL;
}

//
// VCI callback routies
//

//
// Transmit done. We only allow one transmit at the time, this
// routine simply clears the trasmit in progress flag.
//
void pcap_txCompl( VCIBDLLDEF *vcib, VCRPDEF *request )
{
    VCMCTX *vcmctx;
    VCRPLANDEF *vcrp;

    // Get context
    vcmctx = request->vcrp$a_creator;
    vcmctx->transmit_pending = 0;
    vcmctx->last_error = request->vcrp$q_request_status;
    vcmctx->stat.tr_packets++;
}


//
// Do nothing. We own the management VCRP an have a method for getting
// the status below.
//
void pcap_mgmCompl( VCIBDLLDEF *vcib, VCRPDEF *request )
{
    uint64 status;
    status = request->vcrp$q_request_status;
}


//
// Receive complete routine. Notice that the VCIB contains our
// context, so that we can fiddle... we assume we're going to
// ignore this packet.
//
void pcap_rxCompl( VCIBDEF *vcib, VCRPDEF *request )
{
    int retry_count = 1;
    int status = SS$_NORMAL;
    int trash_it = 0;
    PCAPVCIB *pcapvcib;
    VCRPDEF *vcrpout;
    VCMCTX *vcmctx;
    

    //
    // Get our port context
    //
    pcapvcib = (PCAPVCIB *) vcib;
    vcmctx = (VCMCTX *) pcapvcib->vcmctx;
    
    //
    // Put into global receive queue, so no copy.
    // Notice, at this time we're supposed to hold
    // IOLOCK 8, so be as fast as possible
    //
    retry_count += pcapvcm->retry_count;
    do {
	status = __PAL_INSQHIL(vcib, request);
    } while (status < 0 && retry_count-- > 0);

    //
    // If we failed to insert this item, drop it
    //
    if (status < 0) {
	status = vci_delete_vcrp(request);
	vcmctx->stat.recv_packets_dropped++;
	return;
    }

    //
    // Increase counter
    //
    vcmctx->stat.recv_packets++;

    //
    // Is the queue full?
    //
    vcib->vcib$w_size++;
    if (vcib->vcib$w_size > vcmctx->recv_queue_size) {
	status = __PAL_REMQTIL(vcib, (void *)&vcrpout);
	vcib->vcib$w_size--;
	if (status != 0) {
	    status = vci_delete_vcrp(vcrpout);
	    vcmctx->stat.recv_packets_dropped++;
	}
    }

    //
    // Update statistics
    //
    pcapvcm->curr_recv_queue_size = vcib->vcib$w_size;
    vcmctx->stat.recv_queue_size = vcib->vcib$w_size;    
}


//
// We reveived an event, just save it for now...
//
void pcap_event( VCIBDLLDEF *vcib, int event, int reason )
{

    pcapvcm->last_mgm_event = event;
}


//
// Get the PCAP context
//
int pcap$vcm_get_context(PCAPVCM_64PP vcm)
{
    int status;

    if (pcapvcm != NULL) {
	*vcm = pcapvcm;
	status = SS$_NORMAL;
    } else {
	status = SS$_ACCVIO;
    }

    return status;
}


//
// Allocate a VCM port context. This must be done as the first thing
//
int pcap$vcm_alloc_port(VCMCTX_64PP vcmctx)
{
    int status = SS$_NORMAL;
    int pages;
    VCMCTX_64P _align(QUADWORD) tmpctx;
    uint64 real_size;

    //
    // Number of pages required for CPU context
    //
    pages = (sizeof(VCMCTX) + (mmg$gl_page_size - 1))/mmg$gl_page_size;

    //
    // Allocate a VCM context from NNP...
    //
    status = exe$allocate_pool(
		sizeof(VCMCTX),
		MMG$K_POOLTYPE_NPP,
		6,
		&real_size,
		(VOID_PPQ) &tmpctx);
    if ( !$VMS_STATUS_SUCCESS(status) ) {
	return status;
    }

    memset(tmpctx, 0, sizeof(VCMCTX));
    tmpctx->size = real_size;
    tmpctx->lil = (LILDEF *)tmpctx->lilbuf;
    tmpctx->recv_queue_size = pcapvcm->recv_queue_size;
    INIT_LIL(tmpctx->lil, PCAP_LIL_SIZE);
    *vcmctx = tmpctx;

    return status;
}

//
// Deallocate a port block
//
int pcap$vcm_free_port(VCMCTX_64P vcmctx)
{
    int status = SS$_NORMAL;

    //
    // TBD - Check state, to ensure we can do this...
    //

    //
    // Dellocate our context...
    //
    exe$deallocate_pool(vcmctx, MMG$K_POOLTYPE_NPP, vcmctx->size);

    return status;
}

//
// Get devices. We must copy, since LAN return a kernel only readable address
// Notice that this routine can be called multiple times until no more
// devices are found.
//
int pcap$vcm_getdevice(VCMCTX_64P vcmctx, CHAR_64P devnam)
{
    int status = SS$_NORMAL;
    uint32 id;
    LDCDEF *ldc;
    unsigned char *tmpname;
    int len = 0;

    id = vcmctx->ldcid;
    status = vci_get_device(&id, &ldc);
    if ($VMS_STATUS_SUCCESS(status)) {
	vcmctx->ldcid = id;
	tmpname = (unsigned char *) ldc->ldc$a_name;
	len = (int) tmpname[0];
	vcmctx->ldc.ldc$a_name = (void *) &vcmctx->devbuf[0];
	memcpy(vcmctx->ldc.ldc$a_name, ldc->ldc$a_name, len+1);
	memcpy(devnam, ldc->ldc$a_name, len+1);
	devnam[len+1] = (char) 0;
	vcmctx->ldc.ldc$l_type = ldc->ldc$l_type;
	vcmctx->ldc.ldc$l_rcvsize = ldc->ldc$l_rcvsize;
	vcmctx->ldc.ldc$l_devtype = ldc->ldc$l_devtype;
    } else {
	vcmctx->ldcid = 0;
    }
    return status;
}

//
// Create a port...
//
int pcap$vcm_create_port(VCMCTX_64P vcmctx, CHAR_64P device)
{
    int status;
    char tmpdev[128];
    int len;
    PCAPVCIB *pcapvcib;

    len = (int) device[0];
    memcpy(tmpdev, device, len+1);
        
    //
    // Add device to LIL
    //
    if (device) {
	ADD_LIL_ADDR_VAL((LILDEF *)vcmctx->lil, DLL$K_LAN_DEVICE,
	    len+1, tmpdev);
    }
    
    //
    // Initialize VCIB
    //
    status = init_vcib((VCIBDLLDEF *)&vcmctx->vcib, (LILDEF *) vcmctx->lil);

    //
    // Save context in our VCIB
    //
    pcapvcib = (PCAPVCIB *) &vcmctx->vcib;
    pcapvcib->vcmctx = (VCMCTX *) vcmctx;

    if $VMS_STATUS_SUCCESS(status) {
	status = vci_create_port((VCIBDLLDEF *)&vcmctx->vcib);
    }

    return status;
}

//
// Delete a port
//
int pcap$vcm_delete_port(VCMCTX_64P vcmctx)
{
    int status;

    status = vci_delete_port((VCIBDLLDEF *)&vcmctx->vcib);

    return status;
}

//
// Enable a port
//
int pcap$vcm_enable_port(VCMCTX_64P vcmctx, int p2len, CHAR_64P p2buf)
{
    int status;

    if (p2len > 0 && p2buf != NULL) {
	memcpy(&vcmctx->p2_buf[0], p2buf, p2len);
	vcmctx->p2ptr = (char *)&vcmctx->p2_buf[0];
	vcmctx->p2len = p2len;
	status = init_mgmt_vcrp((VCRPLANDEF *) &vcmctx->vcrp, 
	    VCRP$K_FC_ENABLE_PORT, vcmctx->p2len, (char **)&vcmctx->p2ptr);
    } else {
	status = init_mgmt_vcrp((VCRPLANDEF *)&vcmctx->vcrp, 
	    VCRP$K_FC_ENABLE_PORT, 0, 0);
    }

    if $VMS_STATUS_SUCCESS(status) {
	status = vci_mgmt_port((VCRPLANDEF *)&vcmctx->vcrp,
	    (VCIBDLLDEF *)&vcmctx->vcib);
    }

    return status;
}

//
// Disable a port
//
int pcap$vcm_disable_port(VCMCTX_64P vcmctx)
{
    int status = SS$_NORMAL;

    status = init_mgmt_vcrp((VCRPLANDEF *)&vcmctx->vcrp, 
		VCRP$K_FC_DISABLE_PORT, 0, 0);

    if $VMS_STATUS_SUCCESS(status) {
	status = vci_mgmt_port((VCRPLANDEF *)&vcmctx->vcrp, 
		    (VCIBDLLDEF *)&vcmctx->vcib);
    }

    return status;
}


//
// Get last management error.
//
int pcap$vcm_get_mgm_error(VCMCTX_64P vcmctx, CHAR_64P error)
{
    int status = SS$_NORMAL;
    VCRPDEF_64P vcrpptr;

    vcrpptr = (VCRPDEF_64P) &vcmctx->vcrp;
    memcpy(error, &vcrpptr->vcrp$q_request_status, 8);

    return status;
}    


//
// Get last error.
//
int pcap$vcm_get_last_error(VCMCTX_64P vcmctx)
{
    int status;

    status = (int) vcmctx->last_error;

    return status;
}    


//
// Read a packet. This is as simple as removing a VCRP from the
// queue in the VCIB. 
//
int pcap$vcm_read_packet(VCMCTX_64P vcmctx, int len, CHAR_64P packet)
{
    int retry_count = 10;
    int status = SS$_NORMAL;
    int status2;
    int saved_ipl;
    int vcrpsize;
    VCRPDEF *vcrp;
    VCIBDEF *vcib;

    retry_count += pcapvcm->retry_count; 
    fork_lock(SPL$C_IOLOCK8, &saved_ipl);
    //
    // Remove from tail (FIFO style)
    //
    vcib = (VCIBDEF *)&vcmctx->vcib;
    do {
	status = __PAL_REMQTIL(vcib, (void *)&vcrp);
    } while (status <= 0 && retry_count-- > 0);

    //
    // If we couldn't remove entry from queue, say so
    //
    if (status < 0) {
	fork_unlock(SPL$C_IOLOCK8, saved_ipl, SMP_RESTORE);
    	return SS$_NOTQUEUED;
    }

    //
    // If queue is empty, give up
    //
    if (status <= 0) {
	return SS$_NOSUCHOBJECT;
    }

    //
    // If this is the last entry in the queue, then indicate success
    //
    if (status == 2) {
	status = SS$_NORMAL;
    }

    if (vcib->vcib$w_size > 0) {
	vcib->vcib$w_size--;
    }

    vcrpsize = vcrp->vcrp$w_size;
    if (vcrpsize > len) {
	status = vci_delete_vcrp(vcrp);
	fork_unlock(SPL$C_IOLOCK8, saved_ipl, SMP_RESTORE);
	return SS$_INSFMEM;
    }

    //
    // Copy the entire VCRP
    //
    memcpy(packet, vcrp, vcrpsize);

    //
    // Get rid of VCRP
    //
    status2 = vci_delete_vcrp(vcrp);

    fork_unlock(SPL$C_IOLOCK8, saved_ipl, SMP_RESTORE);

    return status;
}

//
// Send of a packet. The packet is assumed to be formatted correctly, so we'll
// just put it in a VCRP and send it on its way...
//
int pcap$vcm_send_packet(VCMCTX_64P vcmctx, int hdrlen, int len,
    CHAR_64P rawpacket)
{
    int status;
    int saved_ipl;
    int vcrpsize;
    char *packptr;
    VCRPDEF *base;
    VCRPLANDEF *vcrp;
    char *reshdr;
    int built_hdrlen;
    int pdulen;

    //
    // If we have an outstanding transmit, give up.
    //
    if (vcmctx->transmit_pending) {
	return SS$_NOTHINGDONE;
    }
    vcmctx->transmit_pending = 1;
    vcmctx->transmit_vcrp = (VCRPDEF *) &vcmctx->vcrpbuf[0];
    vcrp = (VCRPLANDEF *) vcmctx->transmit_vcrp;
    memset(vcrp, 0, 4096);
    status = init_transmit_vcrp(vcrp);
    base = (VCRPDEF *) vcrp;

    //
    // Point to where we're going to put the packet
    //
    packptr = (char *) vcrp + VCRP$T_LAN_DATA + LAN$C_MAX_HDR_SIZE + 8;
    built_hdrlen = vci_build_header(packptr, &reshdr, 0, 0, (VCRPLANDEF *)&vcmctx->vcib);

    //
    // Save the port context address
    //
    base->vcrp$a_creator = (VCMCTX *)vcmctx;

    //
    // Build a frame
    //
    pdulen = (len-hdrlen) + built_hdrlen;
    base->vcrp$l_boff = (char *) reshdr - (char *) base;
    base->vcrp$l_bcnt = pdulen;
    base->vcrp$l_total_pdu_size = pdulen; 
    base->vcrp$w_size = VCRP$T_LAN_DATA + len;
    memcpy(&vcrp->vcrp$q_lan_t_dest, rawpacket, 6);
    //
    // Fiddle header
    //
    memcpy(reshdr, rawpacket, 6);	    // DA
    memcpy(&reshdr[6], &rawpacket[6], 6);   // SA
    memcpy(&reshdr[12], &rawpacket[12], 2); // PTY
    packptr = (char *) reshdr + built_hdrlen;
    memcpy(packptr, (rawpacket+hdrlen), len-hdrlen); 

    //
    // Send the frame
    //
    fork_lock(SPL$C_IOLOCK8, &saved_ipl);
    status = vci_transmit_frame((VCRPLANDEF *)vcmctx->transmit_vcrp, 
	(VCIBDLLDEF *)&vcmctx->vcib);
    fork_unlock(SPL$C_IOLOCK8, saved_ipl, SMP_RESTORE);
    
    //
    // If, by any chance, the transmit is complete, return the status
    //
    if (vcmctx->transmit_pending == 0) {
	status = (int) base->vcrp$q_request_status;
    } else {
	status = SS$_OPINPROG;
    }
    
    return status;
}


//
// Build a header and return it to the user
//
int pcap$vcm_build_header(VCMCTX_64P vcmctx, int len, CHAR_64P header)
{
    int status = SS$_NORMAL;
    char *reshdr;
    char *hdrptr;
    int hdrlen;

    //
    // Set up pointer to (the middle of) header
    //
    hdrptr = (char *)&vcmctx->hdr[64];

    //
    // Build the header
    //
    hdrlen = vci_build_header(hdrptr, &reshdr, 0, 0, (VCRPLANDEF *)&vcmctx->vcib);

    //
    // Copy header just built
    //
    if (hdrlen > len) {
	hdrlen = len;
    }

    memcpy(header, reshdr, hdrlen);

    return hdrlen;
}

//
// Retreive statistics for this VCI port
//
int pcap$vcm_get_statistics(VCMCTX_64P vcmctx, CHAR_64P stats)
{
    int status = SS$_NORMAL;
    PCAPSTAT *statptr;

    statptr = (PCAPSTAT *) stats;

    statptr->recv_packets = vcmctx->stat.recv_packets;
    statptr->recv_packets_dropped = vcmctx->stat.recv_packets_dropped;
    statptr->recv_queue_size = vcmctx->stat.recv_queue_size;
    statptr->tr_packets = vcmctx->stat.tr_packets;
    statptr->tr_failed = vcmctx->stat.tr_failed;

    return status;
}

