/***************************************************************************
*	NAME:  OS.C $Revision: 1.4 $
**	COPYRIGHT:
**	"Copyright (c) 1994, by FORTE
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: os.c $
* Revision 1.4  1995/04/16 19:45:06  mleibow
* Fixed os_ins() bug.
* Revision 1.3  1995/04/10 11:27:23  mleibow
* Fixed os_setvect
* Revision 1.2  1995/03/22 13:14:42  mleibow
* Revision 1.1  1995/02/23 11:06:52  unknown
* Initial revision
* Revision 1.2  1994/09/13 09:38:30  unknown
* Added os_file_gets
* Revision 1.1  1994/09/12 15:17:42  unknown
***************************************************************************/

/**************************************************************************
 *  This module provides an OS indepent API for OS services.  The functions
 *  in this module provide DMA, Interrupt handling, memory allocation,
 *  and semaphores.
 **************************************************************************/

#include <dos.h>
#include <stdlib.h>
#include "dx.h"
#include "iw.h"
#include "iwl.h"
#include "globals.h"

#define UCHAR unsigned char
#define USHORT unsigned short
#define ULONG unsigned long

/* local data structures */
#define DMA0_PAGE	0x87		/* chan 0 page register (refresh)*/
#define DMA1_PAGE	0x83		/* chan 1 page register */
#define DMA2_PAGE	0x81		/* chan 2 page register */
#define DMA3_PAGE	0x82		/* chan 3 page register */
#define DMA4_PAGE	0x8F		/* chan 4 page register (unuseable)*/
#define DMA5_PAGE	0x8B		/* chan 5 page register */
#define DMA6_PAGE	0x89		/* chan 6 page register */
#define DMA7_PAGE	0x8A		/* chan 7 page register */

#define DMA1_SNGL	0x0A		/* write single bit register */
#define DMA1_MODE	0x0B		/* write mode register */
#define DMA1_CLRFF	0x0C		/* clear byte ptr flip/flop */
#define DMA2_SNGL	0xD4		/* write single bit register */
#define DMA2_MODE	0xD6		/* write mode register */
#define DMA2_CLRFF	0xD8		/* clear byte ptr flip/flop */

#define OCR1	0x20			/* 8259-1 Operation control register */
#define IMR1	0x21			/* 8259-1 Mask register */
#define OCR2	0xA0			/* 8259-2 Operation control register */
#define IMR2	0xA1			/* 8259-2 Mask register */
#define EOI	0x20

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

LOCAL DEFINITION:
dma_page - DMA page register map for DMA physical channel numbers

*/
static USHORT dma_page[] = {
	DMA0_PAGE,
	DMA1_PAGE,
	DMA2_PAGE,
	DMA3_PAGE,
	DMA4_PAGE,
	DMA5_PAGE,
	DMA6_PAGE,
	DMA7_PAGE
};

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

LOCAL DEFINITION:
os_irq_table - array used to track the interrupt vectors for IRQ's

DESCRIPTION:
The array is composed of structures with the following fields:
  vector:       interrupt number corresponding to IRQ.
  old_handler:  pointer to origional interrupt function

The array is indexed with an IRQ number.
*/
struct IRQ_ENTRY {
	unsigned short vector;
	OS_PINTERRUPT old_handler;
} os_irq_table[] = {
	{0x08, 0L},	/* 0 */
	{0x09, 0L},	/* 1 */
	{0x0a, 0L},	/* 2 */
	{0x0b, 0L},	/* 3 */
	{0x0c, 0L},	/* 4 */
	{0x0d, 0L},	/* 5 */
	{0x0e, 0L},	/* 6 */
	{0x0f, 0L},	/* 7 */
	{0x70, 0L},	/* 8 */
	{0x71, 0L},	/* 9 */
	{0x72, 0L},	/* 10 */
	{0x73, 0L},	/* 11 */
	{0x74, 0L},	/* 12 */
	{0x75, 0L},	/* 13 */
	{0x76, 0L},	/* 14 */
	{0x77, 0L},	/* 15 */
};

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

LOCAL DEFINITION:
DMA_ENTRY - structure used to track status of a virtual DMA channel

DESCRIPTION:
  dma_disable:   disable mask for DMA channel
  dma_enable:    enable mask for DMA channel
  dma_page:      page register of DMA controller
  dma_page_reg:  unused
  dma_addr:      physical address of DMA buffer for transfer
  dma_count:     count of bytes to transfer
  dma_single:    bit mask for single byte DMA transfer
  dma_mode:      mode of DMA transfer
  dma_clrff:     clear DMA controller flip-flop
  dma_channel:   physical DMA channel number to be used for transfer
*/
typedef volatile struct {
	UCHAR	dma_disable;
	UCHAR	dma_enable;
	UCHAR	dma_page;
	USHORT	dma_page_reg;
	USHORT	dma_addr;
	USHORT	dma_count;
	USHORT	dma_single;
	USHORT	dma_mode;
	USHORT	dma_clrff;
	USHORT	dma_channel;
} DMA_ENTRY;

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

LOCAL DEFINITION:
dma_parms - os layer view of virtual DMA channels

DESCRIPTION:
dma_parms is an array used to track the current state of the virtual
DMA channels used in the kernel.  A virtual DMA channel will be one
of:
  IW_DMA_CHAN_1
  IW_DMA_CHAN_2
  IW_DMA_CHAN_3
*/
static DMA_ENTRY dma_parms[NUM_CHANNELS];

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

FUNCTION DEFINITION:
os_init_channel - Initialize OS layer data about virtual DMA channel

os_init_channel sets up the information kept by the OS layer about a 
vitual DMA channel.  The OS layer mainly tracks the port addresses and
register values needed to prgram the DMA controller for a given virtual
DMA channel.

NOTE: a virutal DMA channel will be one of:
  IW_DMA_CHAN_1
  IW_DMA_CHAN_2
  IW_DMA_CHAN_3

RETURNS: void
*/
void os_init_channel(
  int channel,      /* virtual DMA channel number */
  int dma_channel)  /* physical DMA channel number */
{
	DMA_ENTRY *dparms = &dma_parms[channel];
	USHORT iochan;

	dparms->dma_channel = dma_channel;
	iochan = dma_channel & 3;
	if (dma_channel <= 3) {
	    dparms->dma_single = DMA1_SNGL;
	    dparms->dma_mode   = DMA1_MODE;
	    dparms->dma_clrff  = DMA1_CLRFF;
	    dparms->dma_addr   = iochan << 1;
	    dparms->dma_count   = dparms->dma_addr | 1;
	} else {
	    dparms->dma_single = DMA2_SNGL;
	    dparms->dma_mode   = DMA2_MODE;
	    dparms->dma_clrff  = DMA2_CLRFF;
	    dparms->dma_addr   = (iochan << 2) | 0xC0;
	    dparms->dma_count   = dparms->dma_addr | 0x02;
	}
	dparms->dma_enable = iochan;
	dparms->dma_disable = iochan | 0x04;
	dparms->dma_page = dma_page[dma_channel];
}

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

FUNCTION DEFINITION:
os_pgm_dma - program the DMA controller

os_pgm_dma programs the DMA controller for a DMA transfer on a specified
virtual DMA channel.

RETURNS: void 
*/
void os_pgm_dma(
  int chan,     /* virtual DMA channel to program */
  ULONG count,  /* number of bytes to transfer */
  USHORT mode,  /* DMA transfer mode */
  USHORT page,  /* pjysical page number (highest 4 bits of phys. address */
  USHORT addr)  /* physical address of DMA buffer */
{
	DMA_ENTRY *dparms = &dma_parms[chan];
	UCHAR transfer_lsb, transfer_msb;
	UCHAR transfer_addr_lsb, transfer_addr_msb;

	mode |= dparms->dma_channel & 3;
	if (dparms->dma_channel >= 4) {
	    if (count & 1) count++;
	    transfer_lsb = (UCHAR)((count-2)>>1);
	    transfer_msb = (UCHAR)((count-2)>>9);
	    transfer_addr_lsb = (UCHAR)(addr >> 1);
	    transfer_addr_msb = (UCHAR)(addr >> 9);
	    if (page & 1) {
		transfer_addr_msb |= 0x80;
	    }
	} else {
	    transfer_lsb = (UCHAR)(count-1);
	    transfer_msb = (UCHAR)((count-1)>>8);
	    transfer_addr_lsb = (UCHAR)(addr);
	    transfer_addr_msb = (UCHAR)(addr >> 8);
	}

	OS_PUSH_DISABLE();
	OS_OUTPORTB(dparms->dma_single, dparms->dma_disable);	/* disable */
	OS_OUTPORTB(dparms->dma_mode, mode);			/* set mode */
	OS_OUTPORTB(dparms->dma_clrff, 0);			/* clear f/f */
	OS_OUTPORTB(dparms->dma_addr, transfer_addr_lsb);	/* LSB */
	OS_OUTPORTB(dparms->dma_addr, transfer_addr_msb);	/* MSB */
	OS_OUTPORTB(dparms->dma_page, page);			/* page # */
	OS_OUTPORTB(dparms->dma_clrff, 0);			/* clear f/f */
	OS_OUTPORTB(dparms->dma_count, transfer_lsb);		/* LSB count */
	OS_OUTPORTB(dparms->dma_count, transfer_msb);		/* MSB count */
	OS_OUTPORTB(dparms->dma_single, dparms->dma_enable);	/* enable */
	OS_POP_FLAGS();
}

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

FUNCTION DEFINITION:
os_stop_dma - shutdown DMA transfer activity at the DMA controller

DESCRIPTION:
os_stop_dma stops DMA transfer activity at the DMA controller for a 
specified virtual DMA channel.

RETURNS: void 
*/
void os_stop_dma(
  int channel) /* virtual DMA channel to stop transfer */
{
	DMA_ENTRY *dparms = &dma_parms[channel];

	OS_OUTPORTB(dparms->dma_single, dparms->dma_disable);
}

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

FUNCTION DEFINITION:
os_dma_count - get number of bytes left to be transferred

DESCRIPTION:
os_dma_count reads the DMA controllers current byte count.  Since the 
controller counts backwards to 0xffff, the number returned is the
number of bytes left to be transferred.

RETURNS: ULONG - number of bytes left to transfer
*/
ULONG os_dma_count(
  int channel) /* virtual DMA channel */
{
	DMA_ENTRY *dparms = &dma_parms[channel];
        ULONG xfer_left;
	UCHAR transfer_lsb;
	UCHAR transfer_msb;

	OS_PUSH_DISABLE();
	OS_OUTPORTB(dparms->dma_clrff, 0);		/* clear f/f */
	transfer_lsb = OS_INPORTB(dparms->dma_count);	/* LSB count */
	transfer_msb = OS_INPORTB(dparms->dma_count);	/* MSB count */
	OS_POP_FLAGS();
        xfer_left = ((USHORT)transfer_lsb + ((USHORT)transfer_msb<<8) + 1) & 0xffff;
	if (dparms->dma_channel >= 4) {	/* 16-bit channel */
	    xfer_left <<= 1;
	}
	return(xfer_left);
}

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

FUNCTION DEFINITION:
os_file_read - read a number of bytes from a file

os_file_read calls the C runtime library function: _dos_read.  This
routine reads up to 64K bytes from a file into a buffer specified by
the caller.

RETURNS: USHORT - number of bytes actually read
                  0 if at EOF before call
*/                  
ULONG os_file_read(
  int handle,           /* file handle to read from */
  void *io_buffer,  /* buffer to hold data from file */
  USHORT size)          /* number of bytes to read */
{
	unsigned long rsize;

	iwl_status=_dx_read(handle, io_buffer, size, &rsize);
	return(rsize);
}

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

FUNCTION DEFINITION:
os_file_close - close a file

os_file_close calls the C runtime function: _dos_close

RETURNS: USHORT - 0 on success
                  EOF if any errors were detected
*/
USHORT os_file_close(
  int handle) /* file handle to close */
{
	if ((iwl_status=_dx_close(handle)) == 0) {
	    return(IW_OK);
	} else {
	    return(IW_DOS_ERROR);
	}
}

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

FUNCTION DEFINITION:
os_file_seek - reposition a file pointer

os_file_seek calls the C runtime function: lseek.
Positions the file pointer a number of bytes from the file location given
by the method paremeter.

The method parameter can be one of:
  DOS_SEEK_START   - offset from beginning of file
  DOS_SEEK_END     - offset from the end of the file
  DOS_SEEK_CURRENT - offset from the current position of the file

RETURNS: USHORT - 0 on success
                  non-zero on error
*/
USHORT os_file_seek(
  int handle,    /* file to reposition pointer */
  ULONG offset, /* number of bytes to change pointer position */
  int method)    /* file position to apply offset to */
{
    unsigned short rval;
    unsigned long roffset;

    if ((rval=_dx_seek(handle, offset, method, &roffset)) == 0) {
	return(IW_OK);
    } else {
	return(IW_DOS_ERROR);
    }
}

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

FUNCTION DEFINITION:
os_file_open - open a file for reading and writing

os_file_open calls the C runtime function: _dos_open.
This routine opens the file specified for both reading and writing.

RETURNS: int - pointer to opened file
               NULL on error
*/
int os_file_open(
  char *name) /* path including file name to be opened */
{
    unsigned short handle;

    if ((iwl_status=_dx_open(name, O_RDONLY, &handle)) == 0) {
	return(handle);
    } else {
	return(IW_DOS_ERROR);
    }
}

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

FUNCTION DEFINITION:
os_file_gets - get a string from a file

os_file_gets is a substitute for the standard C runtime fgets function.
This routine reads a string of characters from a file a places it into
a buffer specified by the caller.  The caller the maximum number of
bytes to be read.  This function stops when it reads either the maximum
number - 1 or a newline.

RETURNS: char RFAR * - pointer to the string read in
                      NULL on EOF or error
*/
char RFAR *os_file_gets(
  int handle,           /* file to read data from */
  char RFAR *io_buffer,  /* buffer for data from file */
  unsigned short size)  /* maximum number of bytes to be read */
{
        ULONG actual;
        long offset;

        /* Read the maximum possible buffer size */
        actual = os_file_read(handle, io_buffer, size-1);
        if (actual == 0) return NULL;
        /* Find the newline (in relation to the end of the string) */
        io_buffer[actual] = '\0';
        for (offset = 0; offset < (long)actual; offset++) {
            if (io_buffer[offset] == '\n') {
                io_buffer[++offset] = '\0';
                break;
            }
	    if (io_buffer[offset] == '\r')
	     io_buffer[offset] = '\n';
        }
        offset -= (long)actual;
        /* Backtrack the file pointer to point to the newline */
        os_file_seek(handle, offset, DOS_SEEK_CURRENT);

        return(io_buffer);
}

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

FUNCTION DEFINITION:
os_setvect - sets an interrupt vector entry

os_setvect calls the C runtime function: _dos_setvect
Sets the value for the interrupt vector specified by the int_number
parameter to a far pointer to a new interrupt function.

RETURNS: void
*/
void os_setvect(
  int int_number,    /* interrupt number to set vector */
  OS_PINTERRUPT isr) /* far pointer to interrupt function */
{
    if (FP_SEG(isr) != 0) {
	dx_setvect_pm(int_number, (void *)isr);
    } else {
	dx_setvect_real(int_number, FP_OFF(isr));
    }
}

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

FUNCTION DEFINITION:
os_getvect - gets an interrupt vector entry

os_getvect calls the C runtime function: _dos_getvect
Read the value for the interrupt vector specified by the int_number
parameter.  The value will be a far pointer to an interrupt function
and is returned to the caller.

RETURNS: OS_PINTERRUPT - far pointer to interupt function
*/
OS_PINTERRUPT os_getvect(
  int int_number) /* interrupt number to get interrupt function */
{
	return((OS_PINTERRUPT)MK_FP(0, dx_getvect_real(int_number)));
}

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

FUNCTION DEFINITION:
os_set_irq - set the interrupt vector for an IRQ number 

os_set_irq - sets the interrupt vector for an IRQ number as opposed to 
an interrupt number.  In normal PC usage the IRQ number is mapped to the
interrupt vector table by adding 0x08 to the IRQ number for IRQ's between
0 - 7, and 0x70 for IRQ's from 8 - 15.

This routine converts the IRQ to an interrupt number, sets the vector to
the interrupt fuction specified by the caller, saving the old interrupt
vector in the process, and unmasks the IRQ specified.

RETURNS: void
*/
void os_set_irq(
  int irq,               /* IRQ number to set voector */
  OS_PINTERRUPT handler) /* far pointer to new interrupt function */
{
	int vector;
	unsigned char al, cl;

	OS_PUSH_DISABLE();
	vector = os_irq_table[irq].vector;
	os_irq_table[irq].old_handler = os_getvect(vector);
	os_setvect(vector, handler);

	/* set up gf1 and then midi -- reset in opposite order */
	cl = ~(1 << (irq&7));
	if (irq <= 7) {
		/* Lower controller */
		al = OS_INPORTB(IMR1);
		OS_OUTPORTB(IMR1, al & cl);
	} else {
		/* Upper controller */
		al = OS_INPORTB(IMR2);
		OS_OUTPORTB(IMR2, al & cl);
	}
	OS_POP_FLAGS();
}

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

FUNCTION DEFINITION:
os_unset_irq - reset the interrupt vector for an IRQ number 

os_unset_irq resets the interrupt vector for an IRQ number to the orginal
value.

NOTE: this routine should only be called after calling os_set_irq

RETURNS: void

SEE ALSO:
os_set_irq
*/
void os_unset_irq(
  int irq) /* IRQ number to reset interrupt vector */
{
	int vector;
	unsigned char al, cl;

	OS_PUSH_DISABLE();
	if (irq != 2) {
	    cl = 1 << (irq&7);
	    if (irq <= 7) {
		    /* Lower controller */
		    al = OS_INPORTB(IMR1);
		    OS_OUTPORTB(IMR1, al | cl);
	    } else {
		    /* Upper controller */
		    al = OS_INPORTB(IMR2);
		    OS_OUTPORTB(IMR2, al | cl);
	    }
	}
	vector = os_irq_table[irq].vector;
	os_setvect(vector, os_irq_table[irq].old_handler);
	OS_POP_FLAGS();
}

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

FUNCTION DEFINITION:
os_eoi - send the end-of-interrupt signal to the PIC

RETURNS: void
*/
void os_eoi(
  unsigned short irq) /* IRQ number to send EOI signal */
{
	if (irq > 7) {
	    OS_OUTPORTB(OCR2, EOI);
	}
	OS_OUTPORTB(OCR1, EOI);
}

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

FUNCTION DEFINITION:
os_getenv - get the value of a current envrionment variable

RETURNS: char RFAR * - pointer to value assiciated with environment variable
                       NULL if variable is not currently defined
*/
char RFAR *os_getenv(
  char *name) /* environment variable to retrieve value */
{
  char RFAR *cp;

  cp = dx_getenv(name);
  return(cp);
}

void os_outset(USHORT port, UCHAR data, USHORT length)
{
    int i;

    for (i=0; i<length; i++) OS_OUTPORTB(port, data);
}


void os_outs(void RFAR *data, USHORT length)
{
#pragma aux OUTS = 		\
    "movzx ecx, length",	\
    "mov esi, data",		\
    "shr ecx,1",		\
    "pushf",			\
    "jz skip",			\
    "mov dx, iwl_register_select", 	\
    "mov al, 0x51",		\
    "out dx, al",		\
    "mov dx, iwl_data_low",	\
    "cld",			\
    "rep outsw",		\
"skip:",			\
    "popf",			\
    "jnc done",			\
    "mov dx, iwl_dram_io",	\
    "outsb",			\
"done:"	 			\
    modify [dx ecx esi];

    OUTS();
}

void os_ins(USHORT port, void RFAR *data, USHORT length)
{
#pragma aux INS = 	\
    "push es",		\
    "push ds",		\
    "pop es",		\
    "mov dx, port",	\
    "movzx ecx, length",\
    "mov edi,data",	\
    "cld",		\
    "rep insb",		\
    "pop es",		\
    modify [dx ecx edi];
    INS();
}
