/***************************************************************************
*	NAME:  OS.C $Revision: 1.9 $
**	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.9  1995/11/07 14:56:13  teckert
* Changes os_getenv to return "IW.INI" if there is no INTERWAVE environment
* variable and "" for IWDIR.
* Revision 1.8  1995/09/21 16:51:09  sdsmith
* Added code to get DMA count from Windows Vxd if it is present
* Revision 1.7  1995/09/13 10:00:36  mleibow
* Revision 1.6  1995/09/13 09:58:20  mleibow
* Removed bogus INPORTB.  Oops.
* Revision 1.5  1995/09/12 16:28:22  teckert
* Changed get_vxd_config to return 0 if USE_VXD is undefined
* Revision 1.4  1995/09/12 10:14:05  mleibow
* win95 changes
* Revision 1.3  1995/09/08 16:37:07  sdsmith
* Added calls to the VxD if it is available when doing DMA count
* Revision 1.2  1995/07/13 18:35:04  mleibow
* os_disable and os_enable interrupts only do sti, or cli, if changing
* Revision 1.1  1995/02/23 11:06:54  unknown
* Initial revision
* Revision 1.1  1994/09/12 15:17:46  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 <stdlib.h>
#include <dos.h>
#include <fcntl.h>
#include <time.h>
#include <windows.h>
#include "iw.h"
#include "iwl.h"
#include "globals.h"
#ifdef IW_USE_VXD
static unsigned long Virtual_API;
#include "viwd.h"
#endif

#include "os.h"

#if defined(_MSC_VER) && _MSC_VER == 600
#include <memory.h>
#endif
#pragma warning (disable : 4704)

#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 = (UCHAR)iochan;
	dparms->dma_disable = (UCHAR)(iochan | 0x04);
	dparms->dma_page = (UCHAR)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
*/
extern unsigned long Virtual_API;
ULONG os_dma_count(
  int channel) /* virtual DMA channel */
{
	DMA_ENTRY *dparms = &dma_parms[channel];
        ULONG xfer_left;
	UCHAR transfer_lsb;
	UCHAR transfer_msb;
	UINT port;

	OS_PUSH_DISABLE();
	port = dparms->dma_clrff;
#ifdef IW_USE_VXD
	if (Virtual_API) {
#if 0
	    _asm {
		mov dx, IWAPI_WRITEPORTBYTE
		mov bx, port
		xor ax,ax
		call dword ptr[Virtual_API]
	    }
	    port = dparms->dma_count;
	    _asm {
		mov dx, IWAPI_READPORTBYTE
		mov bx, port
		xor ax,ax
		call dword ptr[Virtual_API]
		mov transfer_lsb,  al
		mov dx, IWAPI_READPORTBYTE
		mov bx, port
		xor ax,ax
		call dword ptr[Virtual_API]
		mov transfer_msb,  al
	    }
//             OS_INPORTB(port, 0);          /* clear f/f */
#endif
	   port = dparms->dma_clrff;
	   _asm mov ax, port
	   _asm push ax
	   port = dparms->dma_count;
	   _asm {
	       mov bx, port
	       mov dx, IWAPI_READDMACOUNT
	       pop ax
	       call dword ptr[Virtual_API]
	       mov transfer_lsb, cl
	       mov transfer_msb, ch
	   }
	}
	else {
#endif
	    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 */
#ifdef IW_USE_VXD
	}
#endif
	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 is a substitue for the C runtime library function "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
*/                  
USHORT os_file_read(
  int handle,           /* file handle to read from */
  void RFAR *io_buffer,  /* buffer to hold data from file */
  USHORT bsize)          /* number of bytes to read */
{
	USHORT retval;

	ASM {
	    mov  cx, bsize
	    mov  bx, handle
	    push ds
	    lds  dx, io_buffer
	    mov  ax, DOS_READ
	    int  21h
	    pop  ds
	    mov  retval, ax
	}
	return(retval);
}

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

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 */
{
        USHORT 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_file_close - close a file

os_file_close is a substitute for the standard C runtime function: fclose.

RETURNS: USHORT - 0 on success
                  EOF if any errors were detected
*/
USHORT os_file_close(
  int handle) /* file handle to close */
{
        USHORT rc;

	ASM {
	    mov bx, handle
	    mov ax, DOS_CLOSE
	    int 21h
	    jc error
	    mov ax, 0
	    jmp done
	error:
	    mov iwl_status, ax
	    mov ax, IW_DOS_ERROR
	done:
	    mov rc,ax
	}
	return(rc);
}

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

FUNCTION DEFINITION:
os_file_seek - reposition a file pointer

os_file_seek is a substitute for the standard C runtime function: fseek.
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 foffset, /* number of bytes to change pointer position */
  int method)    /* file position to apply offset to */
{
        USHORT rc;
	unsigned char cmethod = method;

	ASM {
	    mov cx, word ptr foffset + 2
	    mov dx, word ptr foffset
	    mov bx, handle
	    mov ah, DOS_SEEK
	    mov al, cmethod
	    int 21h
	    jc error
	    mov ax, 0
	    jmp done
	error:
	    mov iwl_status, ax
	    mov ax, IW_DOS_ERROR
	done:
	    mov rc,ax
	}
	return(rc);
}

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

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

os_file_open is a substitute for the standard C runtime function: fopen.
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 RFAR *name) /* path including file name to be opened */
{
        USHORT rc;

	ASM {
	    push ds
	    lds dx, name
	    mov ax, DOS_OPEN_R
	    int 21h
	    pop	ds
	    jc	error
	    jmp done
	error:
	    neg	ax
	done:
	    mov rc,ax
	}
	return(rc);
}

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

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 */
{
    ASM {
	mov             ax, int_number
	mov     ah, 25h
	push    ds
	lds     dx, isr
//      assume  ds, nothing
	int     21h                     ; set the new vector
	pop     ds
    }
}

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

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 */
{          
  WORD wSelector;
  WORD wOffset;   

    ASM    {
	mov     ax,int_number
	mov     ah, 35h
	int     21h                     ; get the old vector in es:bx
	mov     wSelector,es
	mov     wOffset,bx  
    }
	
    return MAKELP(wSelector,wOffset);
}

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

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 far *os_getenv(
  const char far *varname) /* environment variable to retrieve value */
{
	LPSTR lpszEnv;
	LPSTR result = 0L;
	
	lpszEnv = GetDOSEnvironment();
	while (*lpszEnv != '\0')
		{
		if (iwu_strncmp(lpszEnv, varname, lstrlen(varname)) == 0)
			{
			result = lpszEnv + lstrlen(varname) + 1;
			break;
			}
		lpszEnv += lstrlen(lpszEnv) + 1;
		}
        if ( result == 0 )
            {
            if ( iwu_strncmp((LPSTR)"INTERWAVE", varname, lstrlen(varname)) == 0 )
                {
                result = (char far *)"IW.INI";
                }
            else if ( iwu_strncmp((LPSTR)"IWDIR", varname, lstrlen(varname)) == 0 )
                {
                result = (char far *)"";
                }
            }
	return result;                
}

#define INT_FLAG 512
// #define INT_STACK_SIZE       120
// unsigned char os_int_stack[INT_STACK_SIZE];
// unsigned char *os_int_p = os_int_stack;
static unsigned short sav_flags;
static unsigned short disable_count=0;

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

FUNCTION DEFINITION:
os_push_disable - push the flags register on the stack and disable interrupts

RETURNS: void
*/
void os_push_disable(void)
{
    ASM {
	pushf
	pop ax
	test ah,2
	jz no_cli
	cli
no_cli:
	inc [disable_count]
	cmp [disable_count],1
	jne not_first
	mov [sav_flags],ax
not_first:
    }
}

void os_disable_interrupts(void)
{
    ASM {
	pushf
	pop ax
	test ah,2
	jz no_cli
	cli
no_cli:
    }
}

void os_enable_interrupts(void)
{
    ASM {
	pushf
	pop ax
	test ah,2
	jnz no_sti
	sti
no_sti:
    }
}

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

FUNCTION DEFINITION:
os_popf - pop the flags register off the stack

NOTE: if interrupts were enabled when the flags register was pushed on the
      stack, they will automatically be reenabled.

RETURNS: void
*/
void os_popf(void)
{
    ASM {
	dec [disable_count]
	jnz no_sti
	mov ax,[sav_flags]
	test ah,2
	jz no_sti
	sti
no_sti:
    }
}

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

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

void os_outs(void RFAR *data, USHORT nbytes)
{
    ASM {
	push ds
    	mov dx, iwl_register_select
    	mov cx, nbytes
    	lds si, data
    	shr cx,1
    	pushf
    	jz skip
    	mov al, 0x51
    	out dx, al
    	inc dx
    	cld
    	rep outsw
    	dec dx
    skip:
    	popf
    	jnc done
    	add dx, 4
    	outsb
    done:
    	pop ds
    }
}

void os_ins(USHORT port, void RFAR *data, USHORT len)
{
    ASM {
        mov dx, port
        mov cx, len
        les di,data
        cld
        rep insb
    }
}

#ifdef IW_USE_VXD
unsigned char os_get_pnp_resource(
  UCHAR res,
  USHORT RFAR *val)
{
    unsigned char rc = 1;

    _asm {
	mov dx, IWAPI_GETRESOURCECONFIG
        xor ax,ax
        mov al, res
	call dword ptr[Virtual_API]
        les bx,val
        mov word ptr es:[bx],ax
        jnc Success
    }
    rc = 0;
Success:
    return(rc);
}
#endif

int os_get_vxd_info(void)
{
    unsigned short tmp;
#ifdef IW_USE_VXD
    _asm {
	// if we are under windows enhanced
	mov     ax, 1600h               
	int     2Fh                     
	test    al, 7Fh                 
        jnz     running_enhanced        
        jmp     not_running_enhanced    
    }
running_enhanced:
    _asm {
	// if VxD available
	mov     ax, 1684h               
	mov     bx, INTERWAV_DEV_ID
	mov	di,0
	mov	es,di
	int     2Fh                     
	mov     word ptr Virtual_API, di
	mov     word ptr Virtual_API + 2, es
    }

    if (Virtual_API) {
	// if VxD supports get config
	if (os_get_pnp_resource(IWGRC_SYNTH_BASE_2X, &iwl_base_port)) {
            os_get_pnp_resource(IWGRC_SYNTH_BASE_3X, &iwl_synth_base);
            os_get_pnp_resource(IWGRC_IRQ_1, &tmp);
            iwl_irq1 = (char)tmp;
            os_get_pnp_resource(IWGRC_IRQ_2, &tmp);
            iwl_irq2 = (char)tmp;
            os_get_pnp_resource(IWGRC_DMA_1, &tmp);
            iwl_channel_out = (unsigned char)tmp;
            os_get_pnp_resource(IWGRC_DMA_2, &tmp);
            iwl_channel_in = (unsigned char)tmp;
	    os_get_pnp_resource(IWGRC_CODEC_BASE, &iwl_codec_base);
	    os_get_pnp_resource(IWGRC_MPU_BASE, &iwl_mpu_base);
            os_get_pnp_resource(IWGRC_MPU_IRQ, &tmp);
            iwl_mpu_irq = (char)tmp;
	    os_get_pnp_resource(IWGRC_ADLIB_BASE, &iwl_adlib_base);
            os_get_pnp_resource(IWGRC_SB_IRQ, &tmp);
            iwl_adlib_irq = (char)tmp;
		return(1);
	}
    }
not_running_enhanced:
#endif
    return(0);
}
