/*******************************************************************************
**
**      OS9 - Specific Ethernet Code for RPC
**
**  This code is part of the RPC system from CERN DD/OC and collaborators.
**
**
** FUNCTION:
**
**  This is the machine-dependent part of the code to allow thsystem to
**  communicate over ethernet. This version is for the OS9 operating system.
**
** HISTORY:
**
**       1988   Ethernet driver running over q0driv by H.v.d.Schmitt, CERN/EP
**       1988   Adaption to RPC: Johannes Raab CERN/EP (OPAL)
**     Sep 89   Maintenaince and update Tim Berners-Lee CERN/DD
**
** RESTRICTIONS:
**
**  o   The error codes are not explanatory.
**  o   The trace on error is not complete.
**  o   Only one ethernet socket may be open at a time per process (it seems?)
**      The routines rpc_eth_open and rpc_eth_close are process-wide largely.
*/

/*******************************************************\
*                                                       *
*                      *************                    *
*               ****  *************** ****              * 
*            ******** *****     ***** *****             *
*          ********** ****       **** ****              *
*         *********** *****     ***** ****              *
*        *****   **** *************** ****              *
*        ****     *** *************** ***               *
*        ****     *** ****            ***               *
*        ****     *** **** *********  ***               *
*        *****   **** **** *********  **                *
*         *********** **** ***   ***  ***               *
*          ********** **** *********  *********         *
*            *******  **** *********   *******          *
*                     **** ***   ***                    *
*                     **** ***   ***                    *
*                                                       *
*                                                       *                 
\*******************************************************/

#include <stdio.h>
#include <events.h>

#include "osk_specific.h"

#define TS                      /* get TS interface visibility */    
#define TS_INTERNALS            /* get TS internals visibility */    
#include "rpcrts.h"             /* RPC structures including TS layer */
#include "rpc_code.h"           /* Coding macros */

#define  NBUFF  2               /* number of buffers to queue a read into */
#define  MAX_TRY 10             /* number of allowed retries for write */

#define  MAX_WAIT   50          /* time to wait for EvCreation */
#define  INI_VAL     0          /* EvCreate parameters */
#define  WAIT_INC   -1
#define  SIG_INC     1
#define  SLEEP_INC   1          /* time increment while waiting for event */ 

#define RP_SIZE         1500     /* Size of a reply packet */

#define max(A,B)       ((A) > (B) ? (A) : (B))
#define min(A,B)       ((A) < (B) ? (A) : (B))

typedef unsigned long   ulong;
typedef unsigned char   uchar;

struct  net_request {
        uchar   dest[6],source[6],protocol[2];
        uchar   rq_data[RP_SIZE-14];
        }       ;
        
struct  net_reply {
        uchar   dest[6],source[6],protocol[2];
        uchar   rp_data[RP_SIZE-14];
        }       ;

/*  Module-global data:
*/

    struct net_request request,requack;
    struct net_reply   reply[NBUFF],replack;

    int             len[NBUFF];
    int             seq[NBUFF], gseq, lseq;
    int             epath;          /* Path to network device */
    int             evtid;          /* Event id */
    int             inidone;
    char            evname[12];

    int             eth_usage;      /* Count of current usage of ethernet */

 
    union zsam { courier_word twobytes;   /* to get the protocol right */
             short sword;
            } zsamma;


/*          General Ethernet Initialisation
**          -------------------------------
**
*/
EInit( proto, paddress)
short proto;                    /* protocol */          
rpc_ethernet_address paddress;
{
   int i, code;
   short temp;

/* Initialise node address */
/* The lance driver will write our address in the packet */


/* Open the network path */

    strcpy(evname,"RPC_");
    evname[4] = ( (temp=(proto >> 12)) < 10 ? '0'+temp : '7'+temp);
    evname[5] = ( (temp=((proto >> 8) & 017)) < 10 ? '0'+temp : '7'+temp);
    evname[6] = ( (temp=((proto >> 4) & 017)) < 10 ? '0'+temp : '7'+temp);
    evname[7] = ( (temp=(proto  & 017)) < 10 ? '0'+temp : '7'+temp);
    evname[8] = '_';
    i = getpid();
    evname[9] = ( i/10 +'0');
    evname[10] = ( i%10 + '0');
    evname[11] = '\0';
    epath = open("/elan",S_IREAD | S_IWRITE);
    if( epath==-1 ) {
        CTRACE(tfp, "RPC/TS: Ethernet open() failed\n");
        return -1;      /*  Error: OPEN failed. */
    }

/* Link/Create event : if we fail to create it then delete it */

    evtid = _ev_creat(INI_VAL, WAIT_INC, SIG_INC, evname);
    if ( evtid == -1 ) {
        evtid = _ev_link( evname);
        do
            _ev_unlink(evtid);
        while (_ev_delete(evname) == -1);
        evtid = _ev_creat(INI_VAL, WAIT_INC, SIG_INC, evname);                 
        if( evtid == -1 ) {
             CTRACE(tfp,
                "RPC/TS/ETH: Some one is bogarting the event %d\n",errno);
             return -2;
        }
    }

    if ((evtid = _ev_link(evname)) == -1 ) {
        CTRACE(tfp,"RPC/TS/ETH/Open: Can't link to event \n");
        return -2;
    }


    gseq = lseq = 0;

/* Issue initial read rq's for all buffers
*/
    inidone = 1;
    for( i=0; i<NBUFF; ++i) {
        seq[i] = -1;
        eth_mul_rd(epath, &reply[i],proto,evtid,
                   &len[i],RP_SIZE,&seq[i],&gseq);
    }

    CTRACE(tfp,"RPC/TS/ETH: Open OK, path %d ev_name %s\n", epath,evname);
    return  epath;
}

/*          Read a packet from ethernet
**          ---------------------------
*/
ERead( proto,outbuf,size, timer, paddress)
    short   proto;                    /* protocol type */
    uchar   *outbuf;                  /* ptr to free buffer */
    int     *size;                    /* size of returned packet */
    short   timer;                    /* timeout counter 
                                          0 = sleep 'til event
                                          -i = poll i in 10 millisec units
                                           i = sleep i   "                  */
    rpc_ethernet_address paddress;
{
    register i, n, kount;
    int  code, done;
    unsigned sleep_time;

    CTRACE(tfp,"RPC/TS/ETH: timer %d \n",timer);

    if ((n=_ev_read(evtid)) <= 0 && timer > 0) {
        sleep_time = (unsigned) SLEEP_INC;
        do {
              tsleep( sleep_time );
              if ((n = _ev_read(evtid)) > 0) 
                  goto GET_IT_NOW;
              sleep_time += sleep_time;           /* use a backoff like TIM's */
         } while ( (timer -= sleep_time) > 0);
         CTRACE(tfp,"RPC/TS/ETH: ev ret code %d  time waited %d\n",
                n,timer);
         return -1;
     }

GET_IT_NOW:
    ev_wait(evtid);
    lseq += 1;
    for( i=0; i<NBUFF; ++i) {
        if(lseq == seq[i]) {
            _strass(outbuf,reply[i].dest,len[i]);
            seq[i] = -1;
            *size = len[i];
            if (rpc_trace) {
                printf(" READ: Size %d \n", *size);
                n = min(40,*size);
                for (kount = 0; kount < n; kount++) 
                    printf("%2x",*(outbuf+kount));
                printf("\n");
            }
                             
/*
**          Set up the next read
*/
           eth_mul_rd(epath, &reply[i],proto,evtid,
                      &len[i],RP_SIZE,&seq[i],&gseq);
           return 0;
        }
    }
    return  -1;
}

/*      Write a packet to the ethernet
**      ------------------------------
*/
EWrite( proto, inbuf, size, paddress)
    short   proto;                    /* protocol type */
    uchar    *inbuf;                  /* ptr to free buffer */
    int     size;                     /* size of returned packet */
    rpc_ethernet_address paddress;
{
    register kount;
    int     leng, len1, done, done1, try, try1, i;

    getmyid(epath, (inbuf+6));
    _strass(request.dest,inbuf,size);

    for( try = MAX_TRY , done = 0; !done && try >=0; try--) {
        done = eth_write(epath,&request,size);
        if( !done ) {
            tsleep( (unsigned) 50);
            if (rpc_trace) 
                printf(" write timeout, stat =%d\n", done);
        }   
    }
    if( !done ) return -1; 

    if (rpc_trace) {
                   printf("RPC/TS/ETH/SEND: Size %d \n", size);
                   i = min(40,size);
                   for (kount = 0; kount < i; kount++) 
                      printf("%2x",*(inbuf+kount));
                   printf("\n");
    }

    return  0;
}


/*                      Assembler Routines
**                      ==================
**
**  These routines perform the actual operating system interface
**  necessary to to call the driver in nonstandard ways.
*/

_strass( a,b,leng )
char   *a,*b;
int    leng;
{
#asm
        movem.l d2/a0-a1,-(a7)
        move.l  8(a5),d2
        ble.s   _sas1
        subq.l  #1,d2
        movea.l d0,a0
        movea.l d1,a1
_sas2   move.b  (a1)+,(a0)+
        dbra    d2,_sas2
_sas1   movem.l (a7)+,d2/a0-a1
#endasm
}

/*      Assembler routine to call driver to Write a packet
*/
PRIVATE eth_write( pth,buf,leng )
int pth,leng;
unsigned char   *buf;
{
#asm
        movem.l d1-d2/a0,-(a7)
        move.l  8(a5),d2
        movea.l d1,a0
        move.l  #1001,d1
        os9     I$GetStt
        bcc.s   wr_ok
        clr.l   d0
        bra.s   wr_ex
wr_ok   moveq.l #1,d0
wr_ex
        movem.l (a7)+,d1-d2/a0
#endasm
}
 


/*      Assembler routine to call driver to perform a multiple read
*/
eth_mul_rd( pth,buf,pro,code,lln,maxl,seq,gseq )
int pth,code,*lln,maxl,*seq,*gseq;
unsigned short pro;
unsigned char *buf;
{
#asm
        movem.l d1-d4/a0-a3,-(a7)
        clr.l   d2
        move.w  10(a5),d2
        move.l  12(a5),d3
        movea.l 16(a5),a1
        move.l  20(a5),d4
        movea.l 24(a5),a2
        movea.l 28(a5),a3
        movea.l d1,a0
        move.l  #1005,d1
        os9     I$GetStt
        bcc.s   mu_ok
        clr.l   d0
        bra.s   mu_ex
mu_ok   moveq.l #1,d0
mu_ex
        movem.l (a7)+,d1-d4/a0-a3
#endasm
}



/*      Assembler routine to call driver to Read a packet
*/
eth_mul_rd_from_src( pth,buf,pro,code,lln,maxl,addr,seq,gseq )
int pth,code,*lln,maxl,*seq,*gseq,*addr;
unsigned short pro;
unsigned char *buf;
{
#asm
        movem.l d1-d4/a0-a3,-(a7)
        clr.l   d2
        move.w  10(a5),d2
        move.l  12(a5),d3
        movea.l 16(a5),a1
        move.l  20(a5),d4       
        movea.l 24(a5),a2
        movea.l 28(a5),a3
        movea.l 32(a5),a4
        movea.l d1,a0
        move.l  #1011,d1
        os9     I$GetStt
        bcc.s   mus_ok
        clr.l   d0
        bra.s   mus_ex
mus_ok  moveq.l #1,d0
mus_ex
        movem.l (a7)+,d1-d4/a0-a3
#endasm
}
 

/*      Assembler routine to call driver to return our ethernet address
*/
getmyid( pth, myaddr )
int pth;
uchar *myaddr;
{
#asm
        movem.l d1/a0,-(a7)
        movea.l d1,a0
        move.l  #1010,d1
        os9     I$GetStt
        movem.l (a7)+,d1/a0
#endasm
}


/*      Assembler routine to wait for an event
*/

ev_wait( evtid )
int evtid;
{
#asm
        movem.l d1-d3,-(a7)
        moveq.l #Ev$Wait,d1
        moveq.l #1,d2
        move.l  #2000000000,d3
        os9     F$Event
        movem.l (a7)+,d1-d3
#endasm
}


/*              INTERFACE ROUTINES CONFORMING TO RPC REQUIREMENTS
*/

 
/*      Procedure:      Initialise the ethernet i/o
**      ---------       ---------------------------
**
**  This routine is called once only for this process, to initialise
**  any process-wide things.
**
** On exit,
**      return      gives the return status
*/   
PUBLIC rpc_status rpc_eth_init()
{
    eth_usage = 0;
    return RPC_S_NORMAL;
}

/*      Procedure:      Initialise the ethernet i/o for one channel
**      ---------       -------------------------------------------
**
** On entry,
**      psocket     points to a socket descriptor which has been set up
**                  with the correct ethernet addreess, type fields and masks.
** On exit,
**      return      gives the return status
*/   
PUBLIC rpc_status rpc_eth_open(psocket)
        socket_type     psocket;
{
        register socket_type    soc = psocket;
        int eth_status;

        eth_usage++;
        if (eth_usage == 1) {
	    rpc_os9_init();	/* Connect intercept handler */
#ifdef AST
	    eth_rxq = NULL;
#endif
	}
        zsamma.twobytes = soc->mdp.soc_ether.soc_type_filter;

        eth_status = EInit(
                zsamma.sword,
                soc->mdp.soc_ether.soc_addr_filter,
                rpc_trace);  
        if (eth_status<3) return RPC_S_TS_INTERNAL_ERROR;    /* @@@@ */
        else return RPC_S_NORMAL;
}

/*	Return the Address of this socket		    rpc_eth_my_address()
**	---------------------------------
**
**  On entry,
**	socket	is a valid socket pointer, open over ethernet.
**	buf	points to a suitable buffer for the result.
**
**  On exit,
**	buf[]	contains the address as 6 bytes in binary.
*/

PUBLIC rpc_status rpc_eth_my_address(buf)
    rpc_byte *  buf;

{
    getmyid(epath, buf);
    return RPC_S_NORMAL;
}
 
/*
**      Procedure:      DeInitialise the ethernet i/o for one channel
**      ---------       ---------------------------------------------
**
** This procedure in fact checks whether the very last close is being done,
** in which case it performs any global deinitialisation which may be required.
**
** On entry,
**      psocket     points to a socket descriptor.
** On exit,
**      return      gives the return status
*/   
PUBLIC rpc_eth_close(soc)
    socket_type soc;
{
    eth_usage = eth_usage - 1;
    if (eth_usage == 0) {

/*   close path, and get rid of the events in the system
*/
    if (_ev_unlink(evtid) != -1 ) {
        do
            _ev_unlink(evtid);
            while (_ev_delete(evname) == -1);
        }
        _ev_delete(evname);
        CTRACE(tfp, "RPC/TS/Eth: Closing neatly \n");
        close(epath);
    }
    return RPC_S_NORMAL;
}

/*      Procedure:      Send ethernet packet
**      ---------       --------------------    
**
**  On entry,
**      socket      points to an initialised socket.
**
**      buffer      points to a packet. The destination field and protocol type
**                  in the packet header are correct.
**          m_socket    is valid
**          m_index     is the number of bytes (excluding the header)
**                  The source address is not yet filled in.
**      bufsize     gives the number of bytes to be transmitted, EXCLUDING the
**                  ethernet header.
**  On exit,
**      return      Gives the status: if good, the packet has been sent.
**
*/
PUBLIC rpc_status rpc_eth_send(mes)
        rpc_message_pointer mes;
{
    int                     bufsize = (int)mes->m_index;
    register socket_type    soc = mes->m_socket;

    zsamma.twobytes = soc->mdp.soc_ether.soc_type_filter;
    if (EWrite( 
                      zsamma.sword,            /* protocol */
                      mes->header.ether.dest,  /* Data address */
                      bufsize+14,              /* Size inc. Eth Hdr */
                      soc->mdp.soc_ether.soc_addr_filter))
        return RPC_S_TS_INTERNAL_ERROR;
    return RPC_S_NORMAL;      
}
 
 
/*      Wait for ethernet message                             rpc_eth_receive()
**      -------------------------
**
** This procedure receives one ethernet message on the given socket.
** The packets are filtered by source address and protocol type according
** to the way the socket has been set up (wildcard or not).
**
** On entry,
**
**      ppmessage   points to a pointer to a suitable message buffer with
**                  the following fields set up:
**
**                  m_socket    Pointer to socket descriptor
**
**      timeout     may be -1 for infinite timeout, or 0 for poll.
**                  else units of microseconds.
**
** On return,
**
**      return      returns error code or normal or timeout.
**      *pmessage   may have been changed, ie the message swapped for another.
**      **pmessage  has the header and data fields both filled in.
**          m_index     gives the number of data bytes in the message.
*/

PUBLIC rpc_status rpc_eth_receive(ppmessage, timeout)
        rpc_message_pointer     *ppmessage;
        int                     timeout;
{

    register rpc_message *mes = *ppmessage;
    register socket_type soc =  mes->m_socket;
    int                  istat;

    short ticker =              (short) timeout;
    zsamma.twobytes = soc->mdp.soc_ether.soc_type_filter;
    istat = ERead (
                  zsamma.sword,                 /* Reqd prot.type         */
                  mes->header.ether.dest,       /* buffer adr             */
                  &mes->m_index,                /* returned buf length    */
                  ticker,                       /* timeout in milliseconds */
                  soc->mdp.soc_ether.soc_addr_filter);
    if (istat != 0) {
        if (istat == -1) 
            return RPC_S_TIMEOUT;
        else 
            return RPC_S_TS_INTERNAL_ERROR;
    } 

    mes->m_index = mes->m_index - 14;  
    return (RPC_S_NORMAL);

}

#ifdef AST
/*      Set up handler for next message to arrive         rpc_eth_when_receive()
**      -----------------------------------------
**
**                    (Only if ASTs supported)
**
** On entry,
**      pmessage    points to an available message buffer, fields as follows:
**          m_index     maximum length to be read
**          m_status    (undefined)
**          m_next      (undefined)
**          m_socket    points to valid socket descriptor, includes fields
**              soc_user_ast    address of AST completion routine
*/
PUBLIC rpc_status eth_when_receive(pmessage)
        rpc_message_pointer     pmessage;
{
        register struct socket_struct *soc = pmessage->m_socket;
 
        if (!lance_enabled) {
                bbopen();
                lance_enabled = true;
        }

        soc->soc_astadr =   &(eth_ast_service);
        soc->soc_astprm =   pmessage;
        queue_add_tail(&eth_rxq, soc);
        return RPC_S_NORMAL;
}
#endif /* AST */