/* 
 * Copyright (C) 1995 Advanced RISC Machines Limited. All rights reserved.
 * 
 * This software may be freely used, copied, modified, and distributed
 * provided that the above copyright notice is preserved in all copies of the
 * software.
 */

/* -*-C-*-
 *
 * 1.2
 *     2002/06/08 20:34:41
 *
 *
 * hostchan.c - Semi Synchronous Host side channel interface for Angel.
 */

#include <stdio.h>

#ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
#else
#  include "winsock.h"
#  include "time.h"
#endif
#include "hsys.h"
#include "host.h"
#include "logging.h"
#include "chandefs.h"
#include "chanpriv.h"
#include "devclnt.h"
#include "buffers.h"
#include "drivers.h"
#include "adperr.h"
#include "devsw.h"
#include "hostchan.h"

#ifndef UNUSED
#define UNUSED(x) (x = x)  /* Silence compiler warnings for unused arguments */
#endif

#define HEARTRATE 5000000

/*
 * list of available drivers, declared in drivers.c
 */
extern DeviceDescr *devices[];

static DeviceDescr *deviceToUse = NULL;

static struct Channel {
    ChannelCallback callback;
    void *callback_state;
} channels[CI_NUM_CHANNELS];

static unsigned char HomeSeq;
static unsigned char OppoSeq;

/*
 * Handler for DC_APPL packets
 */
static DC_Appl_Handler dc_appl_handler = NULL;

/*
 * slots for registered asynchronous processing callback procedures
 */
#define MAX_ASYNC_CALLBACKS 8
static unsigned int             num_async_callbacks = 0;
static Adp_Async_Callback       async_callbacks[MAX_ASYNC_CALLBACKS];

/*
 * writeQueueRoot is the queue of write requests pending acknowledgement
 * writeQueueSend is the queue of pending write requests which will
 * be a subset of the list writeQueueRoot
 */
static Packet *writeQueueRoot = NULL;
static Packet *writeQueueSend = NULL;
static Packet *resend_pkt = NULL;
static int resending = FALSE;

/* heartbeat_enabled is a flag used to indicate whether the heartbeat is
 * currently turned on, heartbeat_enabled will be false in situations
 * where even though a heartbeat is being used it is problematical or
 * dis-advantageous to have it turned on, for instance during the 
 * initial stages of boot up
 */
unsigned int heartbeat_enabled = FALSE;
/* heartbeat_configured is set up by the device driver to indicate whether
 * the heartbeat is being used during this debug session.  In contrast to
 * heartbeat_enabled it must not be changed during a session.  The logic for
 * deciding whether to send a heartbeat is: Is heartbeat_configured for this
 * session? if and only if it is then if heartbeat[is currently]_enabled and
 * we are due to send a pulse then send it 
 */
unsigned int heartbeat_configured = TRUE;

void Adp_initSeq( void ) {
  Packet *tmp_pkt = writeQueueSend;

  HomeSeq = 0;
  OppoSeq = 0;
  if ( writeQueueSend != NULL) {
    while (writeQueueSend->pk_next !=NULL) {
      tmp_pkt = writeQueueSend;
      writeQueueSend = tmp_pkt->pk_next;
      DevSW_FreePacket(tmp_pkt);
    }
  }
  tmp_pkt = writeQueueRoot;
  if ( writeQueueRoot == NULL)
    return;

  while (writeQueueRoot->pk_next !=NULL) {
    tmp_pkt = writeQueueRoot;
    writeQueueRoot = tmp_pkt->pk_next;
    DevSW_FreePacket(tmp_pkt);
  }
  return;
}

/**********************************************************************/

/*
 *  Function: DummyCallback
 *   Purpose: Default callback routine to handle unexpected input
 *              on a channel
 *
 *    Params:
 *       Input: packet  The received packet
 *
 *              state   Contains nothing of significance
 *
 *   Returns: Nothing
 */
static void DummyCallback(Packet *packet, void *state)
{
    ChannelID chan;
    const char fmt[] = "Unexpected read on channel %u, length %d\n";
    char fmtbuf[sizeof(fmt) + 24];

    UNUSED(state);

    chan = *(packet->pk_buffer);
    sprintf(fmtbuf, fmt, chan, packet->pk_length);
    printf(fmtbuf);

    /*
     * junk this packet
     */
    DevSW_FreePacket(packet);
}

/*
 *  Function: BlockingCallback
 *   Purpose: Callback routine used to implement a blocking read call
 *
 *    Params:
 *       Input: packet  The received packet.
 *
 *      Output: state   Address of higher level's pointer to the received
 *                      packet.
 *
 *   Returns: Nothing
 */
static void BlockingCallback(Packet *packet, void *state)
{
    /*
     * Pass the packet back to the caller which requested a packet
     * from this channel.  This also flags the completion of the I/O
     * request to the blocking read call.
     */
    *((Packet **)state) = packet;
}

/*
 *  Function: FireCallback
 *   Purpose: Pass received packet along to the callback routine for
 *              the appropriate channel
 *
 *    Params:
 *       Input: packet  The received packet.
 *
 *   Returns: Nothing
 *
 * Post-conditions: The Target-to-Host sequence number for the channel
 *                      will have been incremented.
 */
static void FireCallback(Packet *packet)
{
    ChannelID chan;
    struct Channel *ch;

    /*
     * is this a sensible channel number?
     */
    chan = *(packet->pk_buffer);
    if (invalidChannelID(chan))
    {
        printf("ERROR: invalid ChannelID received from target\n");

        /*
         * free the packet's resources, 'cause no-one else will
         */
        DevSW_FreePacket(packet);
        return;
    }

    /*
     * looks OK - increment sequence number, and pass packet to callback
     */
    ch = channels + chan;
    (ch->callback)(packet, ch->callback_state);
}

/**********************************************************************/

/*
 * These are the externally visible functions.  They are documented
 * in hostchan.h
 */
void Adp_addToQueue(Packet **head, Packet *newpkt)
{
    /*
     * this is a bit of a hack
     */
    Packet *pk;

    /*
     * make sure that the hack we are about to use will work as expected
     */
    ASSERT(&(((Packet *)0)->pk_next) == 0, "bad struct Packet layout");

#if defined(DEBUG) && 0
    printf("Adp_addToQueue(%p, %p)\n", head, newpkt);
#endif

    /*
     * here's the hack - it relies upon the next
     * pointer being at the start of Packet.
     */
    pk = (Packet *)(head);

    /*
     * skip to the end of the queue
     */
    while (pk->pk_next != NULL)
        pk = pk->pk_next;

    /*
     * now add the new element
     */
    newpkt->pk_next = NULL;
    pk->pk_next = newpkt;
}

Packet *Adp_removeFromQueue(Packet **head)
{
    struct Packet *pk;

    pk = *head;

    if (pk != NULL)
        *head = pk->pk_next;

    return pk;
}

void Adp_SetLogEnable(int logEnableFlag)
{
  DevSW_SetLogEnable(logEnableFlag);
}

void Adp_SetLogfile(const char *filename)
{
  DevSW_SetLogfile(filename);
}

AdpErrs Adp_OpenDevice(const char *name, const char *arg,
                       unsigned int heartbeat_on)
{
    int i;
    AdpErrs retc;
    ChannelID chan;

#ifdef DEBUG
    printf("Adp_OpenDevice(%s, %s)\n", name, arg ? arg : "<NULL>");
#endif

    heartbeat_configured = heartbeat_on;
    if (deviceToUse != NULL)
        return adp_device_already_open;

    for (i = 0; (deviceToUse = devices[i]) != NULL; ++i)
        if (DevSW_Match(deviceToUse, name, arg) == adp_ok)
            break;

    if (deviceToUse == NULL)
        return adp_device_not_found;

    /*
     * we seem to have found a suitable device driver, so try to open it
     */
    if ((retc = DevSW_Open(deviceToUse, name, arg, DC_DBUG)) != adp_ok)
    {
        /* we don't have a device to use */
        deviceToUse = NULL;
        return retc;
    }

    /*
     * there is no explicit open on channels any more, so
     * initialise state for all channels.
     */
    for (chan = 0; chan < CI_NUM_CHANNELS; ++chan)
    {
        struct Channel *ch = channels + chan;

        ch->callback = DummyCallback;
        ch->callback_state = NULL;
        OppoSeq = 0;
        HomeSeq = 0;
    }

    return adp_ok;
}

AdpErrs Adp_CloseDevice(void)
{
    AdpErrs retc;

#ifdef DEBUG
    printf("Adp_CloseDevice\n");
#endif

    if (deviceToUse == NULL)
        return adp_device_not_open;

    heartbeat_enabled = FALSE;

    retc = DevSW_Close(deviceToUse, DC_DBUG);

    /*
     * we have to clear deviceToUse, even when the lower layers
     * faulted the close, otherwise the condition will never clear
     */
    if (retc != adp_ok)
        WARN("DevSW_Close faulted the call");

    deviceToUse = NULL;
    return retc;
}

AdpErrs Adp_Ioctl(int opcode, void *args)
{
#ifdef DEBUG
    printf("Adp_Ioctl\n");
#endif

    if (deviceToUse == NULL)
        return adp_device_not_open;

    return DevSW_Ioctl(deviceToUse, opcode, args);
}

AdpErrs Adp_ChannelRegisterRead(const ChannelID chan,
                                const ChannelCallback cbfunc,
                                void *cbstate)
{
#ifdef DEBUG
    printf("Adp_ChannelRegisterRead(%d, %p, %x)\n", chan, cbfunc, cbstate);
#endif

    if (deviceToUse == NULL)
        return adp_device_not_open;

    if (invalidChannelID(chan))
        return adp_bad_channel_id;

    if (cbfunc == NULL)
    {
        channels[chan].callback = DummyCallback;
        channels[chan].callback_state = NULL;
    }
    else
    {
        channels[chan].callback = cbfunc;
        channels[chan].callback_state = cbstate;
    }

    return adp_ok;
}

AdpErrs Adp_ChannelRead(const ChannelID chan, Packet **packet)
{
    struct Channel *ch;

#ifdef DEBUG
    printf("Adp_ChannelRead(%d, %x)\n", chan, *packet);
#endif

    if (deviceToUse == NULL)
        return adp_device_not_open;

    if (invalidChannelID(chan))
        return adp_bad_channel_id;

    /*
     * if a callback has already been registered for this
     * channel, then we do not allow this blocking read.
     */
    ch = channels + chan;
    if (ch->callback != DummyCallback)
        return adp_callback_already_registered;

    /*
     * OK, use our own callback to wait for a packet to arrive
     * on this channel
     */
    ch->callback = BlockingCallback;
    ch->callback_state = packet;
    *packet = NULL;

    /*
     * keep polling until a packet appears for this channel
     */
    while (((volatile Packet *)(*packet)) == NULL)
        /*
         * this call will block until a packet is read on any channel
         */
        Adp_AsynchronousProcessing(async_block_on_read);

    /*
     * OK, the packet has arrived: clear the callback
     */
    ch->callback = DummyCallback;
    ch->callback_state = NULL;

    return adp_ok;
}

static AdpErrs ChannelWrite(
    const ChannelID chan, Packet *packet, AsyncMode mode)
{
    struct Channel *ch;
    unsigned char *cptr;

#ifdef DEBUG
    printf( "Adp_ChannelWrite(%d, %x)\n", chan, packet );
#endif

    if (deviceToUse == NULL)
        return adp_device_not_open;

    if (invalidChannelID(chan))
        return adp_bad_channel_id;

    /*
     * fill in the channels header at the start of this buffer
     */
    ch = channels + chan;
    cptr = packet->pk_buffer;
    *cptr++ = chan;
    *cptr = 0;
    packet->pk_length += CHAN_HEADER_SIZE;

    /*
     * OK, add this packet to the write queue, and try to flush it out
     */

    Adp_addToQueue(&writeQueueSend, packet);
    Adp_AsynchronousProcessing(mode);

    return adp_ok;
}

AdpErrs Adp_ChannelWrite(const ChannelID chan, Packet *packet) {
  return ChannelWrite(chan, packet, async_block_on_write);
}

AdpErrs Adp_ChannelWriteAsync(const ChannelID chan, Packet *packet) {
  return ChannelWrite(chan, packet, async_block_on_nothing);
}

static AdpErrs send_resend_msg(DeviceID devid) {

  /*
   * Send a resend message, usually in response to a bad packet or
   * a resend request */
  Packet * packet;
  packet = DevSW_AllocatePacket(CF_DATA_BYTE_POS);
  packet->pk_buffer[CF_CHANNEL_BYTE_POS] = CI_PRIVATE;
  packet->pk_buffer[CF_HOME_SEQ_BYTE_POS] = HomeSeq;
  packet->pk_buffer[CF_OPPO_SEQ_BYTE_POS] = OppoSeq;
  packet->pk_buffer[CF_FLAGS_BYTE_POS] = CF_RELIABLE | CF_RESEND;
  packet->pk_length = CF_DATA_BYTE_POS;
  return DevSW_Write(deviceToUse, packet, devid);
}

static AdpErrs check_seq(unsigned char msg_home, unsigned char msg_oppo) {
  Packet *tmp_pkt;

  UNUSED(msg_oppo);
  /* 
   * check if we have got an ack for anything and if so remove it from the
   * queue
   */
  if (msg_home == (unsigned char)(OppoSeq+1)) {
    /*
     * arrived in sequence can increment our opposing seq number and remove
     * the relevant packet from our queue
     * check that the packet we're going to remove really is the right one
     */
    tmp_pkt = writeQueueRoot;
    while ((tmp_pkt->pk_next != NULL) &&
           (tmp_pkt->pk_next->pk_buffer[CF_HOME_SEQ_BYTE_POS]
            != OppoSeq)){
      tmp_pkt = tmp_pkt->pk_next;
    }
    OppoSeq++;
    if (tmp_pkt->pk_next == NULL) {
#ifdef DEBUG
      printf("trying to remove a non existant packet\n");
#endif
      return adp_bad_packet;
    }
    else {
      Packet *tmp = tmp_pkt->pk_next;
#ifdef RET_DEBUG
      printf("removing a packet from the root queue\n");
#endif
      tmp_pkt->pk_next = tmp_pkt->pk_next->pk_next;
      /* remove the appropriate packet */
      DevSW_FreePacket(tmp);
    return adp_ok;
    }
  }
  else if (msg_home < (unsigned char) (OppoSeq+1)){
    /* already received this message */
#ifdef RET_DEBUG
    printf("sequence numbers low\n");
#endif   
    return adp_seq_low;
  }
  else {  /* we've missed something */
#ifdef RET_DEBUG
    printf("sequence numbers high\n");
#endif   
    return adp_seq_high;
  }
}

static unsigned long tv_diff(const struct timeval *time_now, 
                             const struct timeval *time_was)
{
    return (  ((time_now->tv_sec * 1000000) + time_now->tv_usec)
            - ((time_was->tv_sec * 1000000) + time_was->tv_usec) );
}

#if !defined(__unix) && !defined(__CYGWIN__)
static void gettimeofday( struct timeval *time_now, void *dummy )
{
    time_t t = clock();
    UNUSED(dummy);
    time_now->tv_sec = t/CLOCKS_PER_SEC;
    time_now->tv_usec = (t%CLOCKS_PER_SEC)*(1000000/CLOCKS_PER_SEC);
}
#endif

static AdpErrs pacemaker(void)
{
  Packet *packet;

  packet = DevSW_AllocatePacket(CF_DATA_BYTE_POS);
  if (packet == NULL) {
    printf("ERROR: could not allocate a packet in pacemaker()\n");
    return adp_malloc_failure;
  }
  packet->pk_buffer[CF_CHANNEL_BYTE_POS] = CI_PRIVATE;
  packet->pk_buffer[CF_HOME_SEQ_BYTE_POS] = HomeSeq;
  packet->pk_buffer[CF_OPPO_SEQ_BYTE_POS] = OppoSeq;
  packet->pk_buffer[CF_FLAGS_BYTE_POS] = CF_RELIABLE | CF_HEARTBEAT;
  packet->pk_length = CF_DATA_BYTE_POS;
  return DevSW_Write(deviceToUse, packet, DC_DBUG);
}  

#ifdef FAKE_BAD_LINE_RX
static AdpErrs fake_bad_line_rx( const Packet *const packet, AdpErrs adp_err )
{
    static unsigned int bl_num = 0;

    if (     (packet != NULL)
          && (bl_num++ >= 20 )
          && ((bl_num % FAKE_BAD_LINE_RX) == 0))
    {
        printf("DEBUG: faking a bad packet\n");
        return adp_bad_packet;
    }
    return adp_err;
}
#endif /* def FAKE_BAD_LINE_RX */

#ifdef FAKE_BAD_LINE_TX
static unsigned char tmp_ch;

static void fake_bad_line_tx( void )
{
    static unsigned int bl_num = 0;

    /* give the thing a chance to boot then try corrupting stuff */
    if ( (bl_num++ >= 20) && ((bl_num % FAKE_BAD_LINE_TX) == 0)) 
    {
        printf("DEBUG: faking a bad packet for tx\n");
        tmp_ch = writeQueueSend->pk_buffer[CF_FLAGS_BYTE_POS];
        writeQueueSend->pk_buffer[CF_FLAGS_BYTE_POS] = 77;
    }
}

static void unfake_bad_line_tx( void )
{
    static unsigned int bl_num = 0;

    /*
     * must reset the packet so that its not corrupted when we
     *  resend it 
     */   
    if ( (bl_num >= 20) && ((bl_num % FAKE_BAD_LINE_TX) != 0))
    {
        writeQueueSend->pk_buffer[CF_FLAGS_BYTE_POS] = tmp_ch;
    }
}
#endif /* def FAKE_BAD_LINE_TX */

/*
 * NOTE: we are assuming that a resolution of microseconds will
 * be good enough for the purporses of the heartbeat.  If this proves
 * not to be the case then we may need a rethink, possibly using
 * [get,set]itimer
 */
static struct timeval time_now;
static struct timeval time_lastalive;

static void async_process_dbug_read( const AsyncMode mode,
                                     bool *const finished  )
{
    Packet *packet;
    unsigned int msg_home, msg_oppo;
    AdpErrs adp_err;

    adp_err = DevSW_Read(deviceToUse, DC_DBUG, &packet,
                         mode == async_block_on_read    );

#ifdef FAKE_BAD_LINE_RX
    adp_err = fake_bad_line_rx( packet, adp_err );
#endif

    if (adp_err == adp_bad_packet) {
        /* We got a bad packet, ask for a resend, send a resend message */
#ifdef DEBUG
        printf("received a bad packet\n");
#endif
        send_resend_msg(DC_DBUG);
    }
    else if (packet != NULL)
    {
        /* update the heartbeat clock */
        gettimeofday(&time_lastalive, NULL);

            /*
             * we got a live one here - were we waiting for it?
             */
        if (mode == async_block_on_read)
           /* not any more */
           *finished = TRUE;
#ifdef RETRANS

        if (packet->pk_length < CF_DATA_BYTE_POS) {
            /* we've got a packet with no header information! */
            printf("ERROR: packet with no transport header\n");
            send_resend_msg(DC_DBUG);
        }
        else {
#ifdef RET_DEBUG
            unsigned int c;
#endif
            /*
             * TODO: Check to see if its acknowledgeing anything, remove
             * those packets it is from the queue.  If its a retrans add the
             * packets to the queue
             */
            msg_home = packet->pk_buffer[CF_HOME_SEQ_BYTE_POS];
            msg_oppo = packet->pk_buffer[CF_OPPO_SEQ_BYTE_POS];
#ifdef RET_DEBUG
            printf("msg seq numbers are hseq 0x%x oseq 0x%x\n",
                   msg_home, msg_oppo);
            for (c=0;c<packet->pk_length;c++)
               printf("%02.2x", packet->pk_buffer[c]);
            printf("\n");
#endif
            /* now was it a resend request? */
            if ((packet->pk_buffer[CF_FLAGS_BYTE_POS]) 
                & CF_RESEND) {
                /* we've been asked for a resend so we had better resend */
                /*
                 * I don't think we can use a resend as acknowledgement for
                 * anything so lets not do this for the moment
                 * check_seq(msg_home, msg_oppo);
                 */
#ifdef RET_DEBUG
                printf("received a resend request\n");
#endif
                if (HomeSeq != msg_oppo) {
                    int found = FALSE;
                    /* need to resend from msg_oppo +1 upwards */
                    DevSW_FreePacket(packet);
                    resending = TRUE;
                    /* find the correct packet to resend from */
                    packet = writeQueueRoot;
                    while (((packet->pk_next) != NULL) && !found) {
                        if ((packet->pk_buffer[CF_OPPO_SEQ_BYTE_POS])
                            != msg_oppo+1) {
                            resend_pkt = packet;
                            found = TRUE;
                        }
                        packet = packet->pk_next;
                    }
                    if (!found) {
                        panic("trying to resend non-existent packets\n");
                    }
                }
                else if (OppoSeq != msg_home) {
                    /* 
                     * send a resend request telling the target where we think
                     * the world is at 
                     */
                    DevSW_FreePacket(packet);
                    send_resend_msg(DC_DBUG);
                }
            }
            else {
                /* not a resend request, lets check the sequence numbers */
                
                if ((packet->pk_buffer[CF_CHANNEL_BYTE_POS] != CI_HBOOT) &&
                    (packet->pk_buffer[CF_CHANNEL_BYTE_POS] != CI_TBOOT)) {
                    adp_err = check_seq(msg_home, msg_oppo);
                    if (adp_err == adp_seq_low) {
                        /* we have already received this packet so discard */
                        DevSW_FreePacket(packet);
                    }
                    else if (adp_err == adp_seq_high) {
                        /*
                         * we must have missed a packet somewhere, discard this 
                         * packet and tell the target where we are
                         */
                        DevSW_FreePacket(packet);
                        send_resend_msg(DC_DBUG);
                    }
                    else
                       /*
                        * now pass the packet to whoever is waiting for it
                        */
                       FireCallback(packet);
                }
                else
                   FireCallback(packet);
            }
        }
#else
        /*
             * now pass the packet to whoever is waiting for it
             */
        FireCallback(packet);
#endif
    }
}

static void async_process_appl_read(void)
{
    Packet *packet;
    AdpErrs adp_err;

    /* see if there is anything for the DC_APPL channel */
    adp_err = DevSW_Read(deviceToUse, DC_APPL, &packet, FALSE);

    if (adp_err == adp_ok && packet != NULL)
    {
        /* got an application packet on a shared device */

#ifdef DEBUG
        printf("GOT DC_APPL PACKET: len %d\nData: ", packet->pk_length);
        {
            unsigned int c;
            for ( c = 0; c < packet->pk_length; ++c )
               printf( "%02X ", packet->pk_buffer[c] );
        }
        printf("\n");
#endif

        if (dc_appl_handler != NULL)
        {
            dc_appl_handler( deviceToUse, packet );
        }
        else
        {
            /* for now, just free it!! */
#ifdef DEBUG
            printf("no handler - dropping DC_APPL packet\n");
#endif
            DevSW_FreePacket( packet );
        }
    }
}

static void async_process_write( const AsyncMode mode,
                                 bool *const finished  )
{
    Packet *packet;

#ifdef DEBUG
    static unsigned int num_written = 0;
#endif

    /*
     * NOTE: here we rely in the fact that any packet in the writeQueueSend
     * section of the queue will need its sequence number setting up while
     * and packet in the writeQueueRoot section will have its sequence
     * numbers set up from when it was first sent so we can easily look
     * up the packet numbers when(if) we want to resend the packet.
     */

#ifdef DEBUG
    if (writeQueueSend!=NULL)
       printf("written 0x%x\n",num_written += writeQueueSend->pk_length);
#endif
    /*
     * give the switcher a chance to complete any partial writes
     */
    if (DevSW_FlushPendingWrite(deviceToUse) == adp_write_busy)
    {
        /* no point trying a new write */
        return;
    }
      
    /*
     * now see whether there is anything to write
     */
    packet = NULL;
    if (resending) {
        packet = resend_pkt;
#ifdef RET_DEBUG
        printf("resending hseq 0x%x oseq 0x%x\n", 
               packet->pk_buffer[CF_HOME_SEQ_BYTE_POS],
               packet->pk_buffer[CF_OPPO_SEQ_BYTE_POS]);
#endif
    }
    else if (writeQueueSend != NULL) {
#ifdef RETRANS
        /* set up the sequence number on the packet */
        packet = writeQueueSend;
        HomeSeq++;
        (writeQueueSend->pk_buffer[CF_OPPO_SEQ_BYTE_POS])
            = OppoSeq;
        (writeQueueSend->pk_buffer[CF_HOME_SEQ_BYTE_POS])
            = HomeSeq;
        (writeQueueSend->pk_buffer[CF_FLAGS_BYTE_POS])
            = CF_RELIABLE;
# ifdef RET_DEBUG
        printf("sending packet with hseq 0x%x oseq 0x%x\n",
               writeQueueSend->pk_buffer[CF_HOME_SEQ_BYTE_POS],
               writeQueueSend->pk_buffer[CF_OPPO_SEQ_BYTE_POS]);
# endif
#endif /* RETRANS */
    }

    if (packet != NULL) {
        AdpErrs dev_err;

#ifdef FAKE_BAD_LINE_TX
        fake_bad_line_tx();
#endif

        dev_err = DevSW_Write(deviceToUse, packet, DC_DBUG);
        if (dev_err == adp_ok) {
#ifdef RETRANS
            if (resending) {
                /* check to see if we've recovered yet */
                if ((packet->pk_next) == NULL){
# ifdef RET_DEBUG
                    printf("we have recovered\n");
# endif
                    resending = FALSE;
                }
                else {
                    resend_pkt = resend_pkt->pk_next;
                }
            }
            else {
                /* 
                 * move the packet we just sent from the send queue to the root
                 */
                Packet *tmp_pkt, *tmp;

# ifdef FAKE_BAD_LINE_TX
                unfake_bad_line_tx();
# endif

                tmp_pkt = writeQueueSend;
                writeQueueSend = writeQueueSend->pk_next;
                tmp_pkt->pk_next = NULL;
                if (writeQueueRoot == NULL)
                   writeQueueRoot = tmp_pkt;
                else {
                    tmp = writeQueueRoot;
                    while (tmp->pk_next != NULL) {
                        tmp = tmp->pk_next;
                    }
                    tmp->pk_next = tmp_pkt;
                }
            }
#else  /* not RETRANS */
            /*
             * switcher has taken the write, so remove it from the
             * queue, and free its resources
             */
            DevSW_FreePacket(Adp_removeFromQueue(&writeQueueSend));
#endif /* if RETRANS ... else ... */

            if (mode == async_block_on_write)
               *finished = DevSW_WriteFinished(deviceToUse);

        } /* endif write ok */
    }
    else /* packet == NULL */
    {
        if (mode == async_block_on_write)
           *finished = DevSW_WriteFinished(deviceToUse);
    }
}

static void async_process_heartbeat( void )
{
    /* check to see whether we need to send a heartbeat */
    gettimeofday(&time_now, NULL);

    if (tv_diff(&time_now, &time_lastalive) >= HEARTRATE)
    {
        /*
         * if we've not booted then don't do send a heartrate the link
         * must be reliable enough for us to boot without any clever stuff,
         * if we can't do this then theres little chance of the link staying
         * together even with the resends etc
         */
        if (heartbeat_enabled) {
            gettimeofday(&time_lastalive, NULL);
            pacemaker();
        }
    }
}

static void async_process_callbacks( void )
{
    /* call any registered asynchronous callbacks */
    unsigned int i;
    for ( i = 0; i < num_async_callbacks; ++i )
       async_callbacks[i]( deviceToUse, &time_now );
}

void Adp_AsynchronousProcessing(const AsyncMode mode)
{
    bool finished = FALSE;
#ifdef DEBUG
    unsigned int wc = 0, dc = 0, ac = 0, hc = 0;
# define INC_COUNT(x) ((x)++)
#else
# define INC_COUNT(x)
#endif

    if ((time_lastalive.tv_sec == 0) && (time_lastalive.tv_usec == 0)) {
      /* first time through, needs initing */
      gettimeofday(&time_lastalive, NULL);
    }

    /* main loop */
    do
    {
        async_process_write( mode, &finished );
        INC_COUNT(wc);

        if ( ! finished && mode != async_block_on_write )
        {
            async_process_dbug_read( mode, &finished );
            INC_COUNT(dc);
        }

        if ( ! finished && mode != async_block_on_write )
        {
           async_process_appl_read();
           INC_COUNT(ac);
        }

        if ( ! finished )
        {
          if (heartbeat_configured)
            async_process_heartbeat();
          async_process_callbacks();
          INC_COUNT(hc);
        }

    } while (!finished && mode != async_block_on_nothing);

#ifdef DEBUG
    if ( mode != async_block_on_nothing )
       printf( "Async: %s - w %d, d %d, a %d, h %d\n",
               mode == async_block_on_write ? "blk_write" : "blk_read",
               wc, dc, ac, hc );
#endif
}

/*
 * install a handler for DC_APPL packets (can be NULL), returning old one.
 */
DC_Appl_Handler Adp_Install_DC_Appl_Handler(const DC_Appl_Handler handler)
{
    DC_Appl_Handler old_handler = dc_appl_handler;

#ifdef DEBUG
    printf( "Installing DC_APPL handler %x (old %x)\n", handler, old_handler );
#endif

    dc_appl_handler = handler;
    return old_handler;
}


/*
 * add an asynchronous processing callback to the list
 * TRUE == okay, FALSE == no more async processing slots
 */
bool Adp_Install_Async_Callback( const Adp_Async_Callback callback_proc )
{
    if ( num_async_callbacks < MAX_ASYNC_CALLBACKS && callback_proc != NULL )
    {
        async_callbacks[num_async_callbacks] = callback_proc;
        ++num_async_callbacks;
        return TRUE;
    }
    else
       return FALSE;
}


/*
 * delay for a given period (in microseconds)
 */
void Adp_delay(unsigned int period)
{
    struct timeval tv;

#ifdef DEBUG
    printf("delaying for %d microseconds\n", period);
#endif
    tv.tv_sec = (period / 1000000);
    tv.tv_usec = (period % 1000000);

    (void)select(0, NULL, NULL, NULL, &tv);
}

/* EOF hostchan.c */
