/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
#ifndef lint
static char    *rcsid = "$Header:mc.c 12.1$";
#endif

#include <dos.h>
#include <stdio.h>
#include "pctype.h"
#include <doscalls.h>
#include "pcparam.h"
#include "rb.h"
#include "abios_st.h"
#include "dio.h"
#include "bios.h"
#include "trace.h"
#include "os2data.h"
#include "sem.h"
#include "mc.h"
#include "thread.h"

#define SEM_SHARED 	1	/* shared between processes */

#ifdef __HIGHC__
#define NOT_HC(stmt) /* stmt */
#else
#define NOT_HC(stmt) stmt
#endif

#define dbugptr (&cbcbptr->debug)
/*
 * under OS/2 we use a shared memory segment which we then 
 * store all the shared variables and data structures.
 */
#undef MCFAR
#define MCFAR far	/* must use far pointer for shared memory */
struct mcshare {
	long mc_data_handle;			/* for locking */
	int mc_counter;				/* for counting uses */
	int mc_open;
	int pids[NCPU];				/* pid's for each process */
	int rcvpending[NCPU];			/* pending rcv interrupt */
	int xmitpending[NCPU];			/* pending xmit interrupt */
	struct cbcb far * cbcbptr[NCPU];	/* to find cbcb's */
	struct mcdev mc_dev[NCPU];		/* for sharing information */
} far * mcshare;
struct mcdev far *mcdev;			/* pointer into mcshare */
unsigned mc_segment;				/* segment for mc shared memory */
long mc_sem;					/* semaphore handle */
long mc_sem_poll[NCPU];				/* poll semaphore */
u_int mc_tid;
#define mcopen mcshare->mc_open			/* it must be shared */

static void farmemcpy(short MCFAR *, short MCFAR *, int);

/*
 * pseudo network device between multiple cpu's on a microchannel.
 */

/*
 * initialize the shared memory on OS/2 and do nothing on DOS
 */
mcinit()
{
	struct ProcIDsArea pids;
	int i;
	int rc;
	char mc_sem_name[20];

	if (ncpu < 2)
		return(0);	/* don't need it - only 1 cpu */
	DOSGETPID((struct ProcIDsArea far *) &pids);
	if (DOSALLOCSHRSEG(sizeof (struct mcshare), (char far *) "/SHAREMEM/MC",
			(unsigned far *) &mc_segment) == 0)
	{
		printf("DOSALLOCSHRSEG returned ok\n");
		NOT_HC(FP_SEG(mcshare) = mc_segment);
		farbzero((char far *) mcshare, sizeof (struct mcshare)); /* */
		mcshare->mc_data_handle = pcif_lock((char far *)&mcshare->mc_data_handle);
		mcshare->mc_counter = 1;	/* how many users we have */
		if (rc = DOSCREATESEM(SEM_SHARED, &mc_sem, "/SEM/MC"))
			fatal("couldn't create /SEM/MC",rc);
		for (i=0; i<ncpu; ++i)
		{
			sprintf(mc_sem_name,"/SEM/MC%d", i);
			if (rc = DOSCREATESEM(SEM_SHARED, &mc_sem_poll[i], mc_sem_name))
				fatal("couldn't create /SEM/MC%d",rc);
		}
	}
	else if (rc = DOSGETSHRSEG((char far *) "/SHAREMEM/MC", (unsigned far *) &mc_segment) == 0)
	{
		NOT_HC(FP_SEG(mcshare) = mc_segment);
		printf("DOSGETSHRSEG returned ok\n");
		mcshare->mc_counter++;	/* how many users we have */
		if (DOSOPENSEM(&mc_sem, "/SEM/MC"))
			fatal("couldn't open /SEM/MC",1);
		for (i=0; i<ncpu; ++i)
		{
			sprintf(mc_sem_name,"/SEM/MC%d", i);
			if (rc = DOSOPENSEM(&mc_sem_poll[i], mc_sem_name))
				fatal("couldn't open /SEM/MC%d",rc);
		}
	} else
		fatal("cannot allocate/access shared segment\n",rc);
	mcdev = mcshare->mc_dev;	/* pointer to the first one */
	mcshare->cbcbptr[cpu_number] = (struct cbcb far *) cbcbptr;
	mcshare->pids[cpu_number] = pids.procid_cpid;
	cbcbptr->cbcb_ent[MCENT].pc_cb = exchl(physaddr((char far *) &mcdev[cpu_number]));
	farbzero((char far *) &mcdev[cpu_number], sizeof (struct mcdev)); /* */
	start_mc_thread();	/* start our thread */
	return(0);
}

/*
 * if the mc device is open then we decrement the counter
 * and if we're the last user we unlock the segment
 */
mcclose()
{

	int i;
	if (mc_segment)
	{
		if(--mcshare->mc_counter == 0)
			pcif_unlock(mcshare->mc_data_handle);
		DOSFREESEG(mc_segment);
	}
	if (mc_sem)
		DOSCLOSESEM(mc_sem);
	for (i=0; i<ncpu; ++i)
		if (mc_sem_poll[i])
			DOSCLOSESEM(mc_sem_poll[i]);
}

#ifdef DEBUG
static char *mcname[] = 
{ "unknown", "open", "close", "xmit", "recv", "canrcv", "nop" };

static char *mccmdname(cmd)
	int cmd;
{
	if ((unsigned) cmd > MC_CMD_MAX)
		cmd = 0;
	return(mcname[cmd]);
}
#endif

#define TO_ADDR 5		/* where to find the host address */
#define BROADCAST	0xff

mcreq(unix_cb)
	long unix_cb;
{

	int unit = cpu_number;		/* our unit number */
	struct mcdev MCFAR *mc = &mcdev[unit];
	int rc = 0;				/* 0 = nothing done */
	int tohost;


	if (mcdev == 0)
		return;			/* nothing can be done yet */

	if (mc->cmd == 0)
		mc->cmd = unix_cb;	/* pass cmd in unix_cb */
	DEBUGF(*dbugptr & MCDEBUG, printf("mc%d: cmd=%s (%d)\n", unit, mccmdname(mc->cmd),mc->cmd));

	switch(mc->cmd)
	{
	case MC_CMD_OPEN:
		mc->state = MC_OPEN_STATE | MC_XMIT_ENABLE;	/* mark as open */
		mc->unit = unit;
		mc->addr[0] = 0x80;		/* fake an address */
		mc->addr[1] = 0x80;		/* fake an address */
		mc->addr[2] = 0x80;		/* fake an address */
		mc->addr[3] = 0x80;		/* fake an address */
		mc->addr[4] = 0x80;		/* fake an address */
		mc->addr[5] = unit+1;		/* fake an address */
		mc->rstatus = 0;
		mc->xstatus = 0;
		rc = 1;
		break;
	case MC_CMD_NOP:
		rc = 1;
		break;
	case MC_CMD_CLOSE:
		mcopen &= ~(1<<unit);		/* can't send to it */
		mc->state = 0;			/* we're now closed */
		rc = 1;
		break;
	case MC_CMD_XMIT:
		mc->xlen = exchw(mc->xlength);	/* get it byte swapped */
		if (mc->xlen < 12 || mc->xlen > MAXMCLEN)
			printf("mc%d: xlen=%d\n", unit, mc->xlen);
		tohost = mc->xbuff[TO_ADDR];	/* get host address */
		if (tohost == BROADCAST)
			mc->xmask = mcopen;	/* to everybody */
		else
			mc->xmask = mcopen & (1<< (tohost-1));
		mc->xstatus |= MC_XMIT_BUSY;	/* we are now busy */
		break;
	case MC_CMD_RCVEN:
		mc->state |= MC_RECV_ENABLE;	/* can now recieve */
		mc->rstatus = 0;		/* allow new recieve */
		mcopen |= (1<<unit);
		break;
	case MC_CMD_CANRCV:
		mc->state &= ~MC_RECV_ENABLE;	/* can not now recieve */
		mcopen &= ~(1<<unit);		/* prevent waiting for it */
		rc = 1;
		break;
	default:
		printf("mc: invalid command %d\n", mc->cmd);
		break;
	}
			
	mc_scan();		/* scan to see if anything changed */

	if (rc)
	{
		mc->result = rc;
		if ((mc->state&MC_OPEN_STATE) || (mc->cmd==MC_CMD_NOP))	/* */
			rompint4(FAKEPOLL(MCIRQ));	/* always our CPU */
	}
	mc->cmd = 0;
}


/*
 * go thru and see what we can transmit by copying into a free 
 * receive buffer.
 * we send a receive interrupt if that is requested, and send a
 * transmit interrupt when everybody's gotten the packet.
 * we also notice when a transmit is sending to somebody that
 * isn't open and can't receive the packet.
 */
mc_scan()
{
    struct mcdev MCFAR *mc;
    int i;

    for (mc=mcdev; mc < mcdev+ncpu; ++mc)
    {
	DOSSEMREQUEST(mc_sem, (long) -1);
	if ((mc->state&MC_OPEN_STATE) && (mc->xstatus&MC_XMIT_BUSY))
	{
	    for (i=0; i<ncpu; ++i)
		if (mc->xmask & (1<<i))
		{
		    struct mcdev MCFAR *rmc = &mcdev[i];

		    if ((rmc->state&MC_OPEN_STATE) == 0)
			mc->xmask &= ~(1<<i);		/* not open now */
		    else if ((rmc->rstatus&MC_RECV_BUSY) == 0)
		    {
			DEBUGF(*dbugptr & MCDEBUG, printf("mc%d: sending %d bytes to %d\n", mc->unit, mc->xlen, rmc->unit));
			/* we can send from mc to rmc */
			rmc->rstatus |= MC_RECV_BUSY;
			if (mc->xlen)
			{
			    farmemcpy((short MCFAR *) mc->xbuff, (short MCFAR *) rmc->rbuff, mc->xlen);
			}
			rmc->rlen = mc->xlen;
			rmc->rlength = mc->xlength;	/* byte-swapped */
			rmc->rstatus |= MC_RECV_DONE;
			if (rmc->state & MC_RECV_ENABLE)
			{
			    mcint((MC_RCV_INTR), rmc->unit);
			    DEBUGF(*dbugptr & MCDEBUG, printf("mc%d: rcv done intr\n", rmc->unit));
			}
				
			mc->xmask &= ~(1<<i);
		    }
		}

	    if (mc->xmask == 0)
	    {
		mc->xstatus &= ~MC_XMIT_BUSY;
		mc->xstatus |= MC_XMIT_DONE;
		if (mc->state & MC_XMIT_ENABLE)
		{
		    mcint((MC_XMIT_INTR), mc->unit);
		    DEBUGF(*dbugptr & MCDEBUG, printf("mc%d: xmit done intr\n", mc->unit));
		}
	    }
	}
	DOSSEMCLEAR(mc_sem);
    }
}

/*
 * send interrupt to relevent cpu.
 * if we're the cpu then just call rompint4, otherwise we'll arrange 
 * it so that a mcpoll will generate the interrupt.
 */
mcint(type,unit)
{
    int rc;
    if (cpu_number == unit)
	rompint4(FAKEPOLL(MCIRQ)|IRQ_PUT_INFO(type));
    else
    {
	if (type == MC_RCV_INTR)
	    mcshare->rcvpending[unit] = 1;
	else if(type == MC_XMIT_INTR)
	    mcshare->xmitpending[unit] = 1;
	else
	    printf("mcint: unknown type=%x\n",type);
	if (rc = DOSSEMCLEAR(mc_sem_poll[unit]))
		printf("DOSSEMCLEAR %d rc=%d\n", unit, rc);
    }
}

/*
 * test to see if we've got any microchannel interrupts to be delivered
 * to us.
 */
mcpoll()
{
	int rc = 0;
	if (mc_segment == 0)
		return(0);
	if (mcshare->xmitpending[cpu_number])
	{
		mcshare->xmitpending[cpu_number] = 0;
		rompint4(FAKEPOLL(MCIRQ)|IRQ_PUT_INFO(MC_XMIT_INTR));
		++rc;
	}
	if (mcshare->rcvpending[cpu_number])
	{
		mcshare->rcvpending[cpu_number] = 0;
		rompint4(FAKEPOLL(MCIRQ)|IRQ_PUT_INFO(MC_RCV_INTR));
		++rc;
	}
	return(rc);
}


static void farmemcpy(from, to, length)
short MCFAR *to;
short MCFAR *from;
int length;
{
	for (;length > 0; length -= sizeof (short))
		*to++ = *from++;
}

/*
 * zero a chunk of memory
 */
farbzero(cp1,length)
	char	   far *cp1;
	int		length;
{
	while (--length >= 0)
		*cp1++ = 0;
}

/*
 * wait for someone else to clear our cpu's mc semaphore
 */
static void far
mc_thread()
{
	SET_THREAD_PRIORITY(PRI_MC_THREAD);
	for (;;) 
	{
		DOSSEMSETWAIT(mc_sem_poll[cpu_number], (long) 500);
		mcpoll();	/* send interrupt */
	}
}

/*
 * start the mc thread
 */
int
start_mc_thread()

{
	char	*stack;
	char	far *thstk;
	int	rc;
	char	*malloc();

	if ((stack = malloc(512)) == 0)
		fatal("No memory for mc thread\n");
	stack += 512;
	thstk = (char far *)stack;
	if (rc = DOSCREATETHREAD(mc_thread,(u_int far *)&mc_tid,thstk)) {
		printf("Unable to create mc thread: %d\n",rc);
		exit(0);
	}
}
