/***************************************************************************
*	NAME:  INIT.C $Revision: 1.38 $
**	COPYRIGHT:
**	"Copyright (c) 1995 by e-Tek Labs"
**
**       "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: init.c $
* Revision 1.38  1996/01/25 14:50:12  mleibow
* better error checking for config manager failures
* Revision 1.37  1995/11/21 10:39:26  sdsmith
* Fixes for init under Windows 95 single task mode
* Revision 1.36  1995/11/14 17:05:45  sdsmith
* Removal of copyrights
* Revision 1.35  1995/10/26 11:38:53  MTRAVERS
* Changed ping to account for overlaps.
* Revision 1.34  1995/10/25 10:11:44  sdsmith
* Added bbios option
* Revision 1.33  1995/10/18 14:31:16  MTRAVERS
* Fixed legacy mode init
* Revision 1.32  1995/10/16 15:57:18  unknown
* Fixed copyright
* Revision 1.31  1995/10/16 09:45:42  sdsmith
* Changes for new style init
****************************************************************************/

#include <dos.h>
#include "debug.h"
#include "iw.h"
#include "os.h"

/* Constants */
#define MAX_IW_CARDS 16
#define OFF 0
#define ON  1
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#define ERROR_STR_LEN 4096
#define ERROR_BASE 100
#define WAIT_FOR_KEY 0x02
#define PNP_DATA_RDY 1
#define IW_PSRPAI 0
#define IW_PISOCI 1
#define IW_PCCCI 2
#define IW_PWAKEI 3
#define IW_PRESDI 4
#define IW_PRESSI 5
#define IW_PCSNI 6
#define IW_PLDNI 7
#define IW_PUACTI 0x30
#define IW_LMCFI 0x52
#define AUDIO 0
#define EXT 1
#define GAME_PORT 2
#define ADLIB 3
#define MPU401 4
#define MAX_MEM_REGISTERS   9
#define MAX_IO_PORTS        20
#define MAX_IRQS            7
#define MAX_DMA_CHANNELS    7
#define LEGACY_CSN_OFFSET   0x10
#define SYS_DRIVER 0
#define EXECUTABLE 1

/* Message Codes */
#define USAGE_MESSAGE 1
#define COPYRIGHT_MESSAGE 2
#define PNP_BIOS_FOUND 3
#define PNP_CM_FOUND 4
#define BAD_PNP_BIOS_HEADER 5
#define PNP_ISA_CONFIG_ERROR 6
#define ERROR_WRITING_PROFILE 7
#define IW_INITED 8
#define FOUND_DRAM 9
#define FOUND_ROM 10
#define NO_IW_CARDS_FOUND 11
#define NO_CM_NO_IW_PNP 12
#define NO_LEGACY_TO_CONFIG 13
#define INSUFFICIENT_LEGACY_DATA 14
#define INSUFFICIENT_LEGACY_DATA_0 15
#define INSUFFICIENT_LEGACY_DATA_1 16
#define INSUFFICIENT_LEGACY_DATA_2 17
#define INSUFFICIENT_LEGACY_DATA_3 18
#define INSUFFICIENT_LEGACY_DATA_4 19
#define IW_CARD_OFF 20

/* Return Codes */
#define OK 0
#define NO_INTERWAVE_ENVIRONMENT 1
#define NO_CFG_LANGUAGE 2
#define CANT_LOAD_PROFILE 3

/* get_iw_cfg_error codes */
#define NO_ERROR 0
#define NO_DEV_0 1
#define NO_DEV_1 2
#define NO_DEV_2 3
#define NO_DEV_3 4
#define NO_DEV_4 5

/* Externals */
extern USHORT iwl_register_select,iwl_data_low;

/* Macros */
#define IWL_OUT_W(reg, val) \
    { OS_OUTPORTB(iwl_register_select, reg); OS_OUTPORTW(iwl_data_low, val); }

/* Local types */
typedef int (far *pnp_bios_entry)(int,...);
typedef void (far *pnp_cm_entry)(void);

/* local functions */
static void print_error(int e);
static void print_message(int e);
static char *get_message(int e);

/* GLOBALS */
unsigned short pidxr = 0x279, pnpwrp = 0xA79, pcsnbr = 0x201;
UCHAR get_iw_cfg_error;
char cards = 0;
char quiet = 0, verbose = 0, do_help = 0;
char broken_bios = 0,broken_ethernet = 0, turn_card_off = 0;
char fatal_error = 0;
char iw_vendor_id[8];
char ini_file[128];
char error_string[ERROR_STR_LEN];
char buf[128];
char env_buf[128];
char far profile_buffer[32768];
char read_only;
char config_changed = 0;
int mode;

struct pnp_bios_struct {
    unsigned char signature[4];
    unsigned char version;
    unsigned char length;
    unsigned short control_field;
    unsigned char csum;
    unsigned long event_notification_flag_address;
    unsigned short rm_api_off;
    unsigned short rm_api_seg;
    unsigned short pm_api_sel;
    unsigned long pm_api_off;
    unsigned long oem_device_identifier;
    unsigned short rm_ds;
    unsigned long pm_ds;
} far *pnp_bios_struct;

struct bios_cfg_struct {
    unsigned char revision;
    unsigned char num_csns;
    unsigned short rdp;
    unsigned short reserved;
} pnp_bios_cfg, pnp_cm_cfg;

pnp_bios_entry bios_api;
pnp_cm_entry cm_api;

struct pnp_config {
    unsigned char vendor[8];
    unsigned char pnp_csn;
    unsigned short pnp_read_port;
    unsigned short baseport;
    unsigned short synthbase;
    unsigned short codecbase;
    unsigned short mpubase;
    unsigned short adlibbase;
    unsigned short cdbase;
    unsigned short atapibase;
    unsigned short gameport;
    unsigned char dma1;
    unsigned char dma2;
    unsigned char irq1;
    unsigned char irq2;
    unsigned char cddma;
    unsigned char cdirq;
    unsigned char adlibirq;
    unsigned char mpuirq;
    unsigned char emulation;
    unsigned char config;
};

struct Device_ID {
    unsigned long dBusID;       // Bus type 0 undefined
    unsigned long dDevID;       // Physical device ID, 0xFFFFFFFF is undefined
    unsigned long dSerialNum;   // Serial/Instance number, 0 is undefined
    unsigned long dLogicalID;   // Logical device ID(PnP), Class code(PCI)
    unsigned long dFlags;
};

union Bus_Access {
    struct PCIAccess {
	unsigned char bBusNumber;
	unsigned char bDevFuncNumber;
	unsigned short wPCIReserved;
    } sPCIAccess;
    struct EISAAccess {
	unsigned char bSlotNumber;
	unsigned char bFunctionNumber;
	unsigned short wEisaReserved;
    } sEISAAccess;
    struct PnPAccess {
	unsigned char bCSN;
	unsigned char bLogicalDevNumber;
	unsigned short wReadDataPort;
    } sPnPAccess;
    struct PCMCIAAccess {
	unsigned short wLogicalSocket;
	unsigned short wPCMCIAReserved1;
    } sPCMCIAAccess;
};

struct Config_Info {
    struct Device_ID sDeviceId;
    union Bus_Access uBusAccess;
    unsigned short  wNumMemWindows;
    unsigned long dMemBase[MAX_MEM_REGISTERS];
    unsigned long dMemLength[MAX_MEM_REGISTERS];
    unsigned short  wMemAttrib[MAX_MEM_REGISTERS];
    unsigned short  wNumIOPorts;
    unsigned short  wIOPort_Base[MAX_IO_PORTS];
    unsigned short  wIOPort_Length[MAX_IO_PORTS];
    unsigned short  wNumIRQs;
    unsigned char  bIRQRegisters[MAX_IRQS];
    unsigned char  bIRQAttrib[MAX_IRQS];
    unsigned short  wNumDMAs;
    unsigned char  bDMALst[MAX_DMA_CHANNELS];
    unsigned short  wDMAAttrib[MAX_DMA_CHANNELS];
    unsigned char  bReserved1[3];
};

/* rom header */
struct rom_hdr {
/* 000 */
    char iwave[8];
//              "INTRWAVE"
/* 008 */    unsigned char rom_hdr_revision;
//              rom header revision
/* 009 */    unsigned char series_number;
//              series number (for multi chip rom sets or 0)
/* 010 */    char series_name[16];
//              series name for identifying a chip as part of a collection.
/* 026 */    char date[10];
//              rom creation date. i.e. 03/13/1995
/* 036 */    unsigned short vendor_revision_major;
//              revision number (used by manufacturer)
/* 038 */    unsigned short vendor_revision_minor;
//              revision number (used by manufacturer)
/* 040 */    unsigned long rom_size;
//              size of this rom chip in bytes
/* 044 */    char copyright[128];
//              vendors copyright message.  NUL terminated.
/* 172 */    char vendor_name[64];
//              vendors name or information.  NUL terminated.
/* 236 */    char rom_description[128];
//              text description of ROM.  NUL terminated.
/* 364 */    char pad[147];
//              padding - must be zero filled
/* 511 */    unsigned char csum;
//              checksum of header
};

extern struct req_hdr {
    unsigned char len;
    unsigned char dum1;
    unsigned char command;
    unsigned char dum2[15];
    unsigned char far *cmd_line;
} far * far ReqHdr;

/*=========================================================================*/
char far *getenv(const char *env)
{
    char far *cp, far *rc = 0;

    if (iwu_strnicmp(env, "INTERWAVE=", 10) == 0) {
	return(ini_file);
    }
    if (mode == EXECUTABLE) {
	cp = MK_FP(*((unsigned short far *)MK_FP(_psp, 0x2c)), 0);
	while (*cp) {
	    if (iwu_strnicmp(cp, env, iwu_strlen(env)) == 0) {
		rc = cp + iwu_strlen(env) + 1;
		iwu_strcpy(env_buf, rc);
		break;
	    }
	    while (*cp) cp++;
	    cp++;
	}
    }
    else {
	iwu_strcpy(env_buf, ini_file);
    }
    return(env_buf);
}

#if 0
// Delay function
void delay(unsigned i)
{
    int j;

    while (i--) {
	int j = 100;

	while (j--) {
	    _asm mov dx, 279h
	    _asm in al, dx
	}
    }
}
#endif

/**************************************************************************/
/* Plug and Play Functions                                                */
/**************************************************************************/
void IwavePnpKey(void)
{
    UCHAR code=0x6A;
    UCHAR msb;
    UCHAR i;
	
    /* Reset Linear Feedback Shift Reg. */

    OS_OUTPORTB(pidxr,0x00);
    OS_OUTPORTB(pidxr,0x00);

    OS_OUTPORTB(pidxr,code);          /* Initial value */

    for (i=1; i<32; i++) {
	msb = ((code&0x01)^((code&0x02)>>1))<<7;
	code = (code>>1)|msb;
	OS_OUTPORTB(pidxr,code);
    }
}
void IwavePnpRevokeKey(void)
{
    OS_OUTPORTB(pidxr, IW_PCCCI);
    OS_OUTPORTB(pnpwrp, WAIT_FOR_KEY);
}
void IwavePnpWake(UCHAR csn)
{
    OS_OUTPORTB(pidxr,IW_PWAKEI);     /* select PWAKEI */
    OS_OUTPORTB(pnpwrp,csn);          /* write csn */
}
void IwavePnpSelectDev(UCHAR dev)
{
    OS_OUTPORTB(pidxr,IW_PLDNI);      /* select PLDNI */
    OS_OUTPORTB(pnpwrp,dev);          /* write PLDNI */
}
void IwavePnpActivateDev(UCHAR dev, UCHAR bool)
{
    IwavePnpSelectDev(dev);         /* select audio device */

    OS_OUTPORTB(pidxr,IW_PUACTI);   /* select Activate Register */
    OS_OUTPORTB(pnpwrp,bool);       /* write register */
}
void IwavePnpPeek(
	USHORT pnprdp,
	USHORT bytes,
	UCHAR *data)
{
    USHORT i;
    UCHAR datum;

    for (i=1; i<=bytes; i++) {
	OS_OUTPORTB(pidxr,IW_PRESSI);          /* select PRESSI */
	while(TRUE) {                  /* wait til new data byte is ready */
            datum = OS_INPORTB(pnprdp);
            if (datum) {
		if(datum&PNP_DATA_RDY) {
		    OS_OUTPORTB(pidxr,IW_PRESDI); /* select PRESDI */
		    datum = OS_INPORTB(pnprdp);   /* read resource byte */
		    if(data!=0)
			*(data++)=datum;          /* store it */
		}
                break;
	    }
	}
    }
}

void IwavePnpSerial(
	USHORT pnprdp,
	UCHAR csn,
	UCHAR  *vendor,
	ULONG *serial)
{
    UCHAR presdi, digit, i;

    *serial=0L;

    IwavePnpKey();
    IwavePnpWake(csn);             /* Wake card up, reset EEPROM logic */

    for (i=1; i<=4; i++) {
	IwavePnpPeek(pnprdp,1,&presdi);

	switch(i) {
	    case 1:
		*(vendor++)=((presdi&0x7C)>>2)|0x40; /* 1st char */
		*vendor=(presdi&0x03)<<3;            /* isolate bits[4:3] of 2nd char */
		break;
	    case 2:
		*vendor=((presdi&0xE0)>>5)|(*vendor);
		*(vendor++)=(*vendor)|0x40;               /* 2nd char */
		*vendor=(presdi&0x1F)|0x40;               /* 3rd char */
		break;
	    case 3:
	    case 4:
		digit=(presdi&0xF0)>>4;
		if (digit<=0x09)
		    *(++vendor)=digit+0x30;               /* ASCII of digit */
		else
		    *(++vendor)=(digit&0x07)+0x3F;
		digit=presdi&0x0F;
		if (digit<=0x09)
		    *(++vendor)=digit+0x30;
		else
		    *(++vendor)=(digit&0x07) + 0x3F;
		break;
	}
    }
    *(++vendor)='\0';
    IwavePnpPeek(pnprdp,4,(UCHAR *)serial);
    IwavePnpPeek(pnprdp,1,0);            /* discard checksum */
    IwavePnpRevokeKey();
}

void IwavePnpSetCfg(UCHAR dev, struct pnp_config *cfg)
{
    UCHAR csn;

    csn = cfg->pnp_csn;
    IwavePnpKey();
    IwavePnpWake(csn);
    OS_OUTPORTB(pidxr, IW_PCSNI);
    OS_INPORTB(cfg->pnp_read_port);
    IwavePnpActivateDev(dev,ON);
    switch (dev) {
	case AUDIO:
	    OS_OUTPORTB(pidxr,0x60);                     /* select P2X0HI */
	    OS_OUTPORTB(pnpwrp,cfg->baseport>>8);        /* set P2XR[9:8] */
	    OS_OUTPORTB(pidxr,0x61);                     /* select P2XRLI */
	    OS_OUTPORTB(pnpwrp,cfg->baseport);           /* set P2XR[7:4] */
							 /* P2XR[3:0]=0   */
	    OS_OUTPORTB(pidxr,0x62);                     /* select P3X0HI */
	    OS_OUTPORTB(pnpwrp,cfg->synthbase>>8);/* set P3XR[9:8] */
	    OS_OUTPORTB(pidxr,0x63);                     /* select P3X0LI */
	    OS_OUTPORTB(pnpwrp,cfg->synthbase);   /* set P3XR[7:3] */
						     /* P3XR[2:0]=0   */
	    OS_OUTPORTB(pidxr,0x64);                     /* select PHCAI */
	    OS_OUTPORTB(pnpwrp,cfg->codecbase>>8);        /* set PCODAR[9:8] */
	    OS_OUTPORTB(pidxr,0x65);                     /* select PLCAI */
	    OS_OUTPORTB(pnpwrp,cfg->codecbase);           /* set PCODAR[7:2] */

	    OS_OUTPORTB(pidxr,0x70);                     /* select PUI1SI */
	    OS_OUTPORTB(pnpwrp,cfg->irq1&0x0F);           /* IRQ 1 number */
	    OS_OUTPORTB(pidxr,0x72);                     /* select PUI2SI */
	    OS_OUTPORTB(pnpwrp,cfg->irq2&0x0F);           /* IRQ 2 number */
	    OS_OUTPORTB(pidxr,0x74);                     /* select PUD1SI */
	    OS_OUTPORTB(pnpwrp,cfg->dma1&0x07);           /* DMA channel 1 */
	    OS_OUTPORTB(pidxr,0x75);                     /* select PUD2SI */
	    OS_OUTPORTB(pnpwrp,cfg->dma2&0x07);           /* DMA channel 2 */
	    break;
	case EXT:
	    OS_OUTPORTB(pidxr,0x60);                 /* select PRAHI */
	    OS_OUTPORTB(pnpwrp,cfg->cdbase>>8);       /* get PCDRAR[9:8] */
	    OS_OUTPORTB(pidxr,0x61);                 /* select PRALI */
	    OS_OUTPORTB(pnpwrp,cfg->cdbase);          /* set PCDRAR[7:4] */

	    OS_OUTPORTB(pidxr,0x62);                 /* select PRAHI */
	    OS_OUTPORTB(pnpwrp,cfg->atapibase>>8);    /* get PATAAR[9:8] */
	    OS_OUTPORTB(pidxr,0x63);                 /* select PRALI */
	    OS_OUTPORTB(pnpwrp,cfg->atapibase);       /* set PATAAR[7:4] */

	    OS_OUTPORTB(pidxr,0x70);                 /* select PRISI */
	    OS_OUTPORTB(pnpwrp,cfg->cdirq&0x0F);      /* CD IRQ number */
	    OS_OUTPORTB(pidxr,0x74);                 /* select PRDSI */
	    OS_OUTPORTB(pnpwrp,cfg->cddma&0x07);      /* CD DMA number */
	    break;
	case GAME_PORT:
	    OS_OUTPORTB(pidxr, 0x60);                /* select P201HI */
	    OS_OUTPORTB(pnpwrp,cfg->gameport>>8);     /* get P201AR[9:8] */
	    OS_OUTPORTB(pidxr,0x61);                 /* select P201LI */
	    OS_OUTPORTB(pnpwrp,cfg->gameport);        /* set P201AR[7:4] */
	    break;
	case ADLIB:
	    OS_OUTPORTB(pidxr,0x60);                 /* select P388HI */
	    OS_OUTPORTB(pnpwrp,cfg->adlibbase>>8);    /* get P388AR[9:8] */
	    OS_OUTPORTB(pidxr,0x61);                 /* select P388LI */
	    OS_OUTPORTB(pnpwrp,cfg->adlibbase);       /* set P388AR[7:4] */
	    OS_OUTPORTB(pidxr,0x70);                 /* select PSBISI */
	    OS_OUTPORTB(pnpwrp,cfg->adlibirq&0x0F);   /* set AdLib/SB IRQ */
	    break;
	case MPU401:
	    OS_OUTPORTB(pidxr,0x60);                 /* select P401HI */
	    OS_OUTPORTB(pnpwrp,cfg->mpubase>>8);    /* get P401AR[9:8] */
	    OS_OUTPORTB(pidxr,0x61);                 /* select P401LI */
	    OS_OUTPORTB(pnpwrp,cfg->mpubase);       /* set P401AR[7:4] */
	    OS_OUTPORTB(pidxr,0x70);                 /* select PMISI */
	    OS_OUTPORTB(pnpwrp,cfg->mpuirq&0x0F);   /* set AdLib/SB IRQ */
	    break;
	default:
	    break;
    }
    IwavePnpRevokeKey();
}

void print_iw_cfg(struct pnp_config *cfg)
{
    dprintf("Audio Device: \n");
    dprintf("  P2XR    = %x\n", cfg->baseport);
    dprintf("  P3XR    = %x\n", cfg->synthbase);
    dprintf("  PCODAR  = %x\n", cfg->codecbase);
    dprintf("  IRQ1    = %d\n", cfg->irq1);
    dprintf("  IRQ2    = %d\n", cfg->irq2);
    dprintf("  DMA1    = %d\n", cfg->dma1);
    dprintf("  DMA2    = %d\n", cfg->dma2);
    dprintf("External Device: PCDRAR=%x  PATAAR=%x  EXTIRQ=%d  EXTDMA=%d\n", cfg->cdbase,cfg->atapibase,cfg->cdirq,cfg->cddma);
    dprintf("Game Port: P201AR=%x\n", cfg->gameport);
    dprintf("AdLib/SB Device: P388AR=%x A/SBIRQ=%d\n", cfg->adlibbase,cfg->adlibirq);
    dprintf("MPU-401 Device: P401AR=%x MPUIRQ=%d\n", cfg->mpubase,cfg->mpuirq);
}
void IwavePnpGetCfg(struct pnp_config *cfg)
{
    USHORT val;
    UCHAR  csn;
    USHORT pnprdp;

    csn = cfg->pnp_csn;
    pnprdp = cfg->pnp_read_port;

    IwavePnpKey();
    IwavePnpWake(csn);
    IwavePnpSelectDev(AUDIO);
    OS_OUTPORTB(pidxr,0x60);                    /* select P2X0HI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;        /* get P2XR[9:8] */
    OS_OUTPORTB(pidxr,0x61);                    /* select P2XRLI */
    val += (USHORT)OS_INPORTB(pnprdp);          /* get P2XR[7:4] */
    cfg->baseport = val;

    OS_OUTPORTB(pidxr,0x62);                    /* select P3X0HI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;        /* get P3XR[9:8] */
    OS_OUTPORTB(pidxr,0x63);                    /* select P3X0LI */
    val += (USHORT)OS_INPORTB(pnprdp);            /* get P3XR[7:3] */
    cfg->synthbase = val;

    OS_OUTPORTB(pidxr,0x64);                    /* select PHCAI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;        /* get PCODAR[9:8] */
    OS_OUTPORTB(pidxr,0x65);                    /* select PLCAI */
    val += (USHORT)OS_INPORTB(pnprdp);          /* get PCODAR[7:2] */
    cfg->codecbase = val;

    OS_OUTPORTB(pidxr,0x70);                    /* select PUI1SI */
    val=(USHORT)(OS_INPORTB(pnprdp)&0x0F);
    cfg->irq1 = val;

    OS_OUTPORTB(pidxr,0x72);                    /* select PUI2SI */
    val = (USHORT)(OS_INPORTB(pnprdp)&0x0F);
    cfg->irq2 = val;

    OS_OUTPORTB(pidxr,0x74);                    /* select PUD1SI */
    val = (USHORT)OS_INPORTB(pnprdp)&0x07;
    cfg->dma1 = val;

    OS_OUTPORTB(pidxr,0x75);                    /* select PUD2SI */
    val = (USHORT)OS_INPORTB(pnprdp)&0x07;
    cfg->dma2 = val;

    /* Get External Device Configuration */
    IwavePnpSelectDev(EXT);

    OS_OUTPORTB(pidxr,0x60);                    /* select PRAHI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;      /* get PCDRAR[9:8] */
    OS_OUTPORTB(pidxr,0x61);                    /* select PRALI */
    val += (USHORT)OS_INPORTB(pnprdp);          /* get PCDRAR[7:4] */
    cfg->cdbase = val;

    OS_OUTPORTB(pidxr,0x62);                    /* select PATAHI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;      /* get PCDRAR[9:8] */
    OS_OUTPORTB(pidxr,0x63);                    /* select PATALI */
    val += (USHORT)OS_INPORTB(pnprdp);          /* get PCDRAR[7:4] */
    cfg->atapibase = val;

    OS_OUTPORTB(pidxr,0x70);                    /* select PRISI */
    val = (USHORT)(OS_INPORTB(pnprdp)&0x0F);    /* Ext Dev IRQ number */
    cfg->cdirq = val;

    OS_OUTPORTB(pidxr,0x74);                    /* select PRDSI */
    val = (USHORT)OS_INPORTB(pnprdp)&0x07;      /* Ext Dev DMA channel */
    cfg->cddma = val;

    /* Get Game Port Device Configuration */
    IwavePnpSelectDev(GAME_PORT);

    OS_OUTPORTB(pidxr,0x60);                    /* select PRAHI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;      /* get PCDRAR[9:8] */
    OS_OUTPORTB(pidxr,0x61);                    /* select PRALI */
    val += (USHORT)OS_INPORTB(pnprdp);          /* get PCDRAR[7:4] */
    cfg->gameport = val;

    /* Get AdLib / Sound Blaster Device Configuration */
    IwavePnpSelectDev(ADLIB);

    OS_OUTPORTB(pidxr,0x60);                    /* select P388HI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;      /* get P388AR[9:8] */
    OS_OUTPORTB(pidxr,0x61);                    /* select P388LI */
    val += (USHORT)OS_INPORTB(pnprdp);          /* get P388AR[7:4] */
    cfg->adlibbase = val;

    OS_OUTPORTB(pidxr,0x70);                    /* select PSBISI */
    val = (USHORT)(OS_INPORTB(pnprdp)&0x0F);    /* AdLib/SB IRQ number */
    cfg->adlibirq = val;

    /* Get MPU-401 Device Configuration */
    IwavePnpSelectDev(MPU401);

    OS_OUTPORTB(pidxr,0x60);                    /* select P401HI */
    val = ((USHORT)OS_INPORTB(pnprdp))<<8;      /* get P401AR[9:8] */
    OS_OUTPORTB(pidxr,0x61);                    /* select P401LI */
    val += (USHORT)OS_INPORTB(pnprdp);          /* get P401AR[7:4] */
    cfg->mpubase = val;

    OS_OUTPORTB(pidxr,0x70);                    /* select PMISI */
    val = (USHORT)(OS_INPORTB(pnprdp)&0x0F);    /* MPU-401 IRQ number */
    cfg->mpuirq = val;

    IwavePnpSelectDev(AUDIO);                   /* Back to Audio Dev */
    IwavePnpRevokeKey();
}
UCHAR checksum_it(UCHAR *vendor)
{
    int i,j;
    unsigned char seed = 0x6A;
    unsigned char rc = 0, new = 0, bit0, bit1;

    for (i=0; i<8; i++) {
	new = vendor[i];
	for (j=0; j<8; j++) {
	    bit0 = seed & 0x01;
	    bit1 = (seed & 0x02) >> 1;
	    seed = (seed >> 1) | ((bit0 ^ bit1 ^ (new & 0x01)) << 7);
	    new >>= 1;
	}
    }
    if (seed == vendor[8]) rc = 1;
    return(rc);
}

UCHAR IwavePnpPing(USHORT *read_port)
{
    UCHAR csn,cnt,found_rdp;
    USHORT pnprdp = 0x203;
    UCHAR vendor[9];

    IwavePnpKey();
    found_rdp = 0;
    *read_port = 0;
    cnt = 0;
    while (pnprdp<=0x23F) {
	for(csn=1;csn<=10;csn++) {
	    IwavePnpWake(csn);
	    IwavePnpPeek(pnprdp,9,(UCHAR *)&vendor);
	    if (checksum_it(vendor)) {
		found_rdp = 1;
		cnt++;
	    }
	}
	if (found_rdp) {
	    *read_port = pnprdp;
	    break;
	}
	else pnprdp+=0x04;
    }
    IwavePnpRevokeKey();
    return(cnt);
}
UCHAR IwavePnpIsol(USHORT *pnpread)
{
    UCHAR checksum=0x6A;
    UCHAR chksum=0x00;
    UCHAR bit=0x00;
    USHORT pnprdp=0x203;
    USHORT data;
    UCHAR csn=0x00;
    UCHAR i;
    UCHAR iteration=1;

//############################################################
//  The procedure to set the address of PNPRDP is :
//  1. Issue the Initiation Key.
//  2. Send Command Wake[0] to enter isolation.
//  3. Send command Set RD_DATA Port.
//############################################################

    IwavePnpKey();                    /* Issue Initiation Key */
    IwavePnpWake(0x00);               /* send command Wake[0] */
    OS_OUTPORTB(0x279,IW_PSRPAI);                /* select PSRPAI to set addr of PNPRDP */
    OS_OUTPORTB(0xA79,(UCHAR)(pnprdp>>2));   /* set A[9:2] of PNPRDP address */
    OS_OUTPORTB(0x279,IW_PISOCI);                /* select PISOCI (Serial Isol Reg) */
    delay(1);                           /* wait 1 millisecond */

    while(TRUE) {
	for (i=1; i<=64; i++) {                                 /* Vendor Id and Serial Number */
	    data=((USHORT)OS_INPORTB(pnprdp))<<8;   /* first read (0x55) */
	    delay(1);
	    data=data|OS_INPORTB(pnprdp);         /* second read (0xAA) */
	    delay(1);                            /* wait 250useconds at least*/
	    if (data==0x55AA)
		bit=0x01;
	    checksum=((((checksum^(checksum>>1))&0x01)^bit)<<7)|(checksum>>1);
	    bit=0x00;
	}
	for (i=65; i<=72; i++) {                                  /* checksum from card */
	    data=((USHORT)OS_INPORTB(pnprdp))<<8;    /* first read (0x55) */
	    delay(1);
	    data=data|(OS_INPORTB(pnprdp)&0x00FF); /* second read (0xAA) */
	    delay(1);
	    if (data==0x55AA)
		chksum=chksum|(1<<(i-65));
	}
	if ((checksum!=0x00)&&(checksum==chksum)) {
	    csn++;
	    OS_OUTPORTB(0x279,IW_PCSNI);            /* select PCSNI */
	    OS_OUTPORTB(0xA79,csn);               /* set CSN */
	    iteration++;
	    IwavePnpWake(0x00);             /* send command Wake[0] */
	    OS_OUTPORTB(0x279,IW_PISOCI);           /* select PISOCI (Serial Isol Reg) */
	}

	if(((checksum==0x00)||(chksum!=checksum))&&(iteration==1)) {
	    if (pnprdp>0x23F)
		return(0);         /* No PNP cards in system */

	    pnprdp+=0x04;                   /* Next PNPRDP to try */
	    checksum=0x6A;
	    chksum=0x00;
	    IwavePnpWake(0x00);               /* send command Wake[0] */
	    OS_OUTPORTB(0x279,0x00);                /* set new addr of PNPRDP */
	    OS_OUTPORTB(0xA79,(UCHAR)(pnprdp>>2));   /* set A[9:2] of PNPRDP address */
	    OS_OUTPORTB(0x279,IW_PISOCI);             /* select PISOCI (Serial Isol Reg) */
	}

	if(((checksum==0x00)||(chksum!=checksum))&&(iteration>1))
	    break;

	checksum=0x6A;                     /* reset for next card */
	chksum = 0x00;                     /* reset for next card */
	bit=0x00;
    }
    IwavePnpRevokeKey();
    *pnpread=pnprdp;
    return(csn);
}

unsigned char pnp_cm_present(void)
{
    pnp_cm_entry api;
    unsigned short cm_version;
    unsigned short num_devs,i;
    struct Config_Info config;
    unsigned char last_csn = 0, tmp_csn, rc = 0;

    _ES = 0;
    _DI = 0;
    _AX = 0x1684;
    _BX = 0x34;
    geninterrupt(0x2F);
    api = (pnp_cm_entry)MK_FP(_ES,_DI);
    if (api) {
	// check version to see if config manager is operational.
	_AX = 0;
	asm call DWORD PTR api
	cm_version = _AX;
	num_devs = _BX;
	if (cm_version != 0 && cm_version != 0xFFFF) {
	    if (verbose > 1) {
		get_message(PNP_CM_FOUND);
		dprintf("%s\n", (char RFAR *)error_string);
	    }
	    cm_api = api;
	    pnp_cm_cfg.rdp = 0;
	    pnp_cm_cfg.num_csns = 0;
	    for (i=0; i<num_devs; i++) {
		_ES = FP_SEG(&config);
		_DI = FP_OFF(&config);
		_AX = 1;
		_BX = i;
		asm call DWORD PTR api
		if (!_AX) {
		    if (config.sDeviceId.dBusID == 0x10) {
			tmp_csn = config.uBusAccess.sPnPAccess.bCSN;
			pnp_cm_cfg.rdp = config.uBusAccess.sPnPAccess.wReadDataPort;
			if (tmp_csn != last_csn) {
			    last_csn = tmp_csn;
			    pnp_cm_cfg.num_csns++;
			}
		    }
		}
	    }
	    rc = 1;
	}
    }
    return(rc);
}

unsigned char pnp_bios_present(void)
{
    unsigned char far *cp = MK_FP(0xF000, 0), far *cp1;
    unsigned short i, j, rval;
    unsigned char csum, rc = 0;

    for (i=0; i < 0x1000; i++, FP_SEG(cp) += 1) {
	if (iwu_strnicmp(cp, "$PnP", 4) == 0) {
	    pnp_bios_struct = (struct pnp_bios_struct far *)cp;
	    cp1 = cp;
	    csum = 0;
	    for (j=0; j < pnp_bios_struct->length; j++) {
		csum = *cp1++;
	    }
	    if (csum == 0) {
		if (verbose > 1) {
		    dprintf("%s\n",(char RFAR *)get_message(PNP_BIOS_FOUND));
		}
		rc = 1;
		bios_api = (pnp_bios_entry)MK_FP(pnp_bios_struct->rm_api_seg, pnp_bios_struct->rm_api_off);
		rval = (*bios_api)(0x40, (char far *)&pnp_bios_cfg, pnp_bios_struct->rm_ds);
		if (rval != 0) {
		    bios_api = 0;
                    pnp_bios_cfg.num_csns = 0;
                    pnp_bios_cfg.rdp = 0;
		    if (verbose > 1) dprintf(get_message(PNP_ISA_CONFIG_ERROR), rval);
		}
		else {
		    if (verbose > 2) {
			dprintf("PnPB ISA Revision %d\n",pnp_bios_cfg.revision);
			dprintf("PnPB ISA Number of CSNs %d\n",pnp_bios_cfg.num_csns);
			dprintf("PnPB ISA Read Data Port %x\n",pnp_bios_cfg.rdp);
		    }
		}
	    }
	    else {
		if (verbose > 1) {
		    dprintf("%s\n",get_message(BAD_PNP_BIOS_HEADER));
		}
	    }
	}
    }
    return(rc);
}

static void print_error(int e)
{
    char *str;

    if (quiet < 2) {
	if (e == NO_CFG_LANGUAGE) {
	    dprintf("InterWave Initialization\n");
	    str = "Can't find default language in [languages] section\n"
	    "of InterWave configuration file.";
	} else if (e == NO_INTERWAVE_ENVIRONMENT) {
	    dprintf("InterWave Initialization\n");
	    str = "Can't find INTERWAVE environment variable.";
	} else if (e == CANT_LOAD_PROFILE) {
	    dprintf("InterWave Initialization\n");
	    str = error_string;
	    error_string[0] = 0;
	    iwu_strcat(error_string,"Problem loading INTERWAVE configuration file: ");
	    iwu_strcat(error_string, ini_file);
	} else {
	    str = error_string;
	    error_string[0] = 0;
	    iw_mls_getstring("iwinit.mls", e, error_string, ERROR_STR_LEN);
	}
	dprintf("Error: %s\n", (char RFAR *)str);
    }
    fatal_error = 1;
}

static void print_message(int e)
{
    if (quiet < 1 || do_help) {
	iw_mls_getstring("iwinit.mls", e, error_string, ERROR_STR_LEN);
	dprintf("%s\n", (char RFAR *)error_string);
    }
}

static char *get_message(int e)
{
    iw_mls_getstring("iwinit.mls", e, error_string, ERROR_STR_LEN);
    return(error_string);
}


static int is_a_digit(char c)
{
    if (c>='0' && c <='9') return 1;
    else return 0;
}

static void parse_command_line(char far *cmd, char cmd_len)
{
    char far *tmp,far *tmp2;

    for (tmp = cmd; (tmp - cmd) < cmd_len;) {
	if (!iwu_isspace(*tmp)) {
	    if (iwu_strncmp(tmp,"ID=",3) == 0) {
		tmp += 3;
		iwu_strncpy(iw_vendor_id, tmp, 7);
		iw_vendor_id[7] = 0;
		tmp += 8;
	    }
	    if (iwu_strncmp(tmp,"INTERWAVE=",10) == 0) {
		tmp += 10;
		tmp2 = tmp;
		while (*tmp2 && !iwu_isspace(*tmp2)) tmp2++;
		iwu_strncpy(ini_file, tmp, (tmp2 - tmp));
		ini_file[(tmp2-tmp)] = 0;
		tmp = ++tmp2;
	    }
	    switch (*tmp) {
		case '-':
		case '/':
		    tmp++;
		    switch (*tmp) {
			case 'q':
			    tmp++;
			    if (is_a_digit(*tmp)) {
				quiet = (*tmp) - '0';
			    }
			    else quiet = 1;
			    break;
			case 'v':
			    tmp++;
			    if (is_a_digit(*tmp)) {
				verbose = (*tmp) - '0';
			    }
			    else verbose = 1;
			    break;
			case 'h':
			case '?':
			    do_help = 1;
			    break;
			default:
			    if (iwu_strncmp(tmp,"broken_ethernet",15) == 0) {
				tmp += 15;
				broken_ethernet = 1;
			    }
			    else if (iwu_strncmp(tmp,"off",3) == 0) {
				tmp += 3;
				turn_card_off = 1;
			    }
			    else if (iwu_strncmp(tmp,"bbios",5) == 0) {
				tmp += 5;
				broken_bios = 1;
			    }
			    break;
		    }
		    break;
		default:
		    break;
	    }
	    tmp++;
	}
	else tmp++;
    }
}

int set_best_memory_cfg(void)
{
#define MAX_BANKS 4
#define BANK_SIZE_BITS  22
    struct rom_hdr rh;
    unsigned long bank_sizes[MAX_BANKS];
    unsigned long max_avail, avail;
    unsigned long rom_pos;
    int i,j,bank;
    int best_mode;
    unsigned char rom_name[20];
    unsigned char csum, RFAR *cp, *cp1;

    for (bank=0; bank < MAX_BANKS; bank++) {
	bank_sizes[bank] = 0;
	for (j=0; j < 16; j++) {
	    if (iw_good_dram(((ULONG)bank<<BANK_SIZE_BITS)+(j*0x40000L))) {
		bank_sizes[bank] += 0x40000L;
	    }
	    else
		break;
	}
    }
    best_mode=-1;
    max_avail=0;
    for (i=0; i < 12; i++) {
	avail = 0;
	if (i >= 0 && i <= 6 && bank_sizes[0] >= 0x40000)
	    avail += 0x40000;
	if (i >= 7 && i <= 9 && bank_sizes[0] >= 0x100000)
	    avail += 0x100000;
	if (i >= 10 && i <= 12 && bank_sizes[0] >= 0x400000)
	    avail += 0x400000;
	if (((i >= 1 && i <= 2) ||
		 (i >= 5 && i <= 6)) && bank_sizes[1] >= 0x40000)
	    avail += 0x40000;
	if (((i >= 3 && i <= 4) ||
		 (i >= 8 && i <= 9)) && bank_sizes[1] >= 0x100000)
	    avail += 0x100000;
	if (i >= 11 && i <= 12 && bank_sizes[1] >= 0x400000)
	    avail += 0x400000;
	if (i == 2 && bank_sizes[2] >= 0x40000)
	    avail += 0x40000;
	if (((i >= 4 && i <= 6) || i == 9) && bank_sizes[2] >= 0x100000)
	    avail += 0x100000;
	if (i == 2 && bank_sizes[3] >= 0x40000)
	    avail += 0x40000;
	if (avail > max_avail) {
	    best_mode = i;
	    max_avail = avail;
	}
    }
    if (verbose >= 1) {
	avail = 0;
	for (bank=0; bank < MAX_BANKS; bank++) {
	    avail += bank_sizes[bank];
	}
	dprintf(get_message(FOUND_DRAM), avail);
	dprintf("\n");
	/* check roms */
	for (rom_pos=0, bank=0; bank < MAX_BANKS; bank++, rom_pos += (4UL*1024UL*1024UL)) {
	    iw_peek_rom((unsigned char RFAR *)&rh, rom_pos, sizeof(struct rom_hdr));
	    if (iwu_strnicmp(rh.iwave, "INTRWAVE", 8) != 0) continue;
		/* test rom header checksum */
	    csum = 0;
	    cp = (unsigned char RFAR *)&rh;
	    for (i=0; i < 511; i++) csum += *cp++;
	    csum = 256 - csum;
	    if (csum != *cp) continue;
	    cp = rom_name;
	    *cp++ = (unsigned char)('0' + (char)rh.series_number + 1);
	    *cp++ = ' ';
	    cp1 = rh.series_name;
	    for (i=0; i < 16 && *cp1; i++) *cp++ = *cp1++;
	    *cp = '\0';
	    dprintf(get_message(FOUND_ROM), (char RFAR *)rom_name);
	    dprintf("\n");
	}
    }
    return(best_mode);
}

static void init_audio_device(void)
{
    struct load_kernel_cfg kcfg;
    int ret,best_mode;

    iwu_get_kernel_cfg(&kcfg, cards);
    kcfg.FullInit = 1;
    kcfg.emulation = 0;
    ret = iw_load_kernel(&kcfg);
    if (ret == IW_OK) {
	best_mode = set_best_memory_cfg();
	iw_unload_kernel();
	if (best_mode != -1) {
	    IWL_OUT_W(IW_LMCFI, best_mode);
	}
	if (!quiet) {
	    dprintf(get_message(IW_INITED), (cards+1));
	    dprintf("\n");
	}
    } else {
	if (!quiet) {
	    dprintf("%s\n", (char RFAR *)iw_error_str(ret));
	}
    }
}

static UCHAR get_iw_cfg(struct pnp_config *cfg)
{
    char setup[16];

    get_iw_cfg_error = NO_ERROR;
    iwu_strcpy(setup, "setup 0");
    setup[6] += cards;
    iwu_get_profile_string(setup,"config","0",buf,16,ini_file);
    cfg->config = ((unsigned char)iwu_strtol(buf, 0, 16) >> 3);

    if (!read_only || (cfg->config & 0x10)) {
	iwu_get_profile_string(setup, "synthbase", "error", buf, 128, ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_0;
	    return(0);
	}
	cfg->baseport = (USHORT)iwu_strtol(buf,0,16);
	cfg->synthbase = cfg->baseport + 0x100;
	iwu_get_profile_string(setup,"CodecBase","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_0;
	    return(0);
	}
	cfg->codecbase = (unsigned short)iwu_strtol(buf, 0, 16);
	iwu_get_profile_string(setup,"DMA1","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_0;
	    return(0);
	}
	cfg->dma1 = (unsigned char)iwu_strtol(buf, 0, 10);
	iwu_get_profile_string(setup,"DMA2","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_0;
	    return(0);
	}
	cfg->dma2 = (unsigned char)iwu_strtol(buf, 0, 10);
	iwu_get_profile_string(setup,"IRQ1","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_0;
	    return(0);
	}
	cfg->irq1 = (unsigned char)iwu_strtol(buf, 0, 10);
	iwu_get_profile_string(setup,"IRQ2","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_0;
	    return(0);
	}
	cfg->irq2 = (unsigned char)iwu_strtol(buf, 0, 10);
    }

    if (!read_only || (cfg->config & 0x08)) {
	iwu_get_profile_string(setup,"CDBase","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_1;
	    return(0);
	}
	cfg->cdbase = (unsigned short)iwu_strtol(buf, 0, 16);
	iwu_get_profile_string(setup,"ATAPIBase","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_1;
	    return(0);
	}
	cfg->atapibase = (unsigned short)iwu_strtol(buf, 0, 16);
	iwu_get_profile_string(setup,"CDIRQ","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_1;
	    return(0);
	}
	cfg->cdirq = (unsigned char)iwu_strtol(buf, 0, 10);
	iwu_get_profile_string(setup,"CDDMA","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_1;
	    return(0);
	}
	cfg->cddma = (unsigned char)iwu_strtol(buf, 0, 10);
    }

    if (!read_only || (cfg->config & 0x04)) {
	iwu_get_profile_string(setup,"GamePort","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_2;
	    return(0);
	}
	cfg->gameport = (unsigned short)iwu_strtol(buf, 0, 16);
    }

    if (!read_only || (cfg->config & 0x02)) {
	iwu_get_profile_string(setup,"AdLibBase","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_3;
	    return(0);
	}
	cfg->adlibbase = (unsigned short)iwu_strtol(buf, 0, 16);
	iwu_get_profile_string(setup,"SBIRQ","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_3;
	    return(0);
	}
	cfg->adlibirq = (unsigned char)iwu_strtol(buf, 0, 10);
    }

    if (!read_only || (cfg->config & 0x01)) {
	iwu_get_profile_string(setup,"MpuBase","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_4;
	    return(0);
	}
	cfg->mpubase = (unsigned short)iwu_strtol(buf, 0, 16);
	iwu_get_profile_string(setup,"MPUIRQ","error",buf,16,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    get_iw_cfg_error = NO_DEV_4;
	    return(0);
	}
	cfg->mpuirq = (unsigned char)iwu_strtol(buf, 0, 10);
    }
    return(1);
}

static void set_iw_pnp_cfg(struct pnp_config *cfg)
{
    if (!read_only || cfg->config & 0x10) {
	IwavePnpSetCfg(AUDIO, cfg);
    }
    if (!read_only || cfg->config & 0x08) {
	IwavePnpSetCfg(EXT, cfg);
    }
    if (!read_only || cfg->config & 0x04) {
	IwavePnpSetCfg(GAME_PORT, cfg);
    }
    if (!read_only || cfg->config & 0x02) {
	IwavePnpSetCfg(ADLIB, cfg);
    }
    if (!read_only || cfg->config & 0x01) {
	IwavePnpSetCfg(MPU401, cfg);
    }
}

int write_ini_settings(struct pnp_config RFAR *cfg, int card_num)
{
    char setup[10];
    char buf[16];

    iwu_strcpy(setup, "setup 0");
    setup[6] += (char)card_num;

    iwu_itoa(cfg->pnp_csn,buf,16);
    if (iwu_write_profile_string(setup,"csn",buf,ini_file) != 0) {
	return(ERROR_WRITING_PROFILE);
    }
    iwu_itoa(cfg->pnp_read_port,buf,16);
    if (iwu_write_profile_string(setup,"pnprdp",buf,ini_file) != 0) {
	return(ERROR_WRITING_PROFILE);
    }
    config_changed = 1;
    return(0);
}

static void activate_card(UCHAR csn, char off)
{
    char on = 1;

    if (off) on = 0;
    IwavePnpKey();
    IwavePnpWake(csn);
    IwavePnpActivateDev(AUDIO, on);
    IwavePnpActivateDev(EXT, on);
    IwavePnpActivateDev(GAME_PORT, on);
    IwavePnpActivateDev(ADLIB, on);
    IwavePnpActivateDev(MPU401, on);
    IwavePnpRevokeKey();
    if (off) {
	if (!quiet) {
	    dprintf(get_message(IW_CARD_OFF), (cards+1));
	    dprintf("\n");
	}
    }
}

static void find_pnp_cards(unsigned short read_port, unsigned char num_csns)
{
    unsigned char match[MAX_IW_CARDS]; /* support 16 InterWave cards */
    unsigned char i,do_all = 0;
    char card,vendor[8],section[16],not_again = 0;
    unsigned long serial;
    struct pnp_config pnpcfg;

    for (i=0; i<MAX_IW_CARDS; i++) match[i] = 0;
    card = cards;
    while (1) {
	iwu_strcpy(section, "setup 0");
	section[6] += (char)(card);
	iwu_get_profile_string(section, "vendor_id", "error", buf, 128, ini_file);
	if (iwu_strcmp(buf,"error") != 0) {
	    for (i=1; i<=num_csns; i++) {
		IwavePnpSerial(read_port, i, vendor, &serial);
		if (!iwu_stricmp(buf, vendor)) {
		    if (!match[i]) {
			match[i] = 1;
			iwu_strcpy(pnpcfg.vendor, vendor);
			pnpcfg.pnp_read_port = read_port;
			pnpcfg.pnp_csn = i;
			if (read_only) {
			    IwavePnpGetCfg(&pnpcfg);
			    if (bios_api && !cm_api) {
				if (pnpcfg.baseport == 0) {
				    do_all = 1;
				}
			    }
			    if (get_iw_cfg(&pnpcfg)) {
				if (do_all) pnpcfg.config = 0x1F;
				if (pnpcfg.config) {
				    set_iw_pnp_cfg(&pnpcfg);
				    IwavePnpGetCfg(&pnpcfg);
				}
				if (verbose) print_iw_cfg(&pnpcfg);
				write_ini_settings(&pnpcfg, cards);
				init_audio_device();
				activate_card(i, turn_card_off);
			    }
			}
			else {
			    if (get_iw_cfg(&pnpcfg)) {
				set_iw_pnp_cfg(&pnpcfg);
				IwavePnpGetCfg(&pnpcfg);
				if (verbose) print_iw_cfg(&pnpcfg);
				write_ini_settings(&pnpcfg, cards);
				init_audio_device();
				activate_card(i, turn_card_off);
			    }
			    else {
				if (quiet < 2) {
				    dprintf(get_message(INSUFFICIENT_LEGACY_DATA));
				    dprintf(get_message(INSUFFICIENT_LEGACY_DATA_0 + get_iw_cfg_error - 1));
				}
			    }
			}
			cards++;
		    }
		}
		else {
		    if (!cm_api && !bios_api && !not_again && verbose) {
			not_again = 1;
			dprintf("%s\n",(char RFAR *)get_message(NO_CM_NO_IW_PNP));
		    }
		}
	    }
	    card++;
	}
	else break;
    }
}

int main(int sys_or_exe)
{
    int rc = 0;
    char *cp;
    char far *tmp;
    char far *env, far *cmd, cmd_len;
    unsigned char iso_csns = 0;
    unsigned short iso_read_port, legacy_rdp = 0;
    struct pnp_config pnpcfg;
    char setup[16];

    mode = sys_or_exe;
    if (sys_or_exe == EXECUTABLE) {
	env = getenv("INTERWAVE");
	if (env) {
	    iwu_strcpy(ini_file, env);
	}
	else ini_file[0]=0;

    /* Parse the IWINIT environment variable */
	env = getenv("IWINIT");
	if (env) {
	    parse_command_line(env, iwu_strlen(env));
	}
    
    /* Parse the actual command line */
	tmp = MK_FP(_psp, 0x80);
	cmd_len = *tmp;
	cmd = MK_FP(_psp, 0x81);
    }
    else {
	cmd = ReqHdr->cmd_line;
	/* skip past device= garbage and get to command tail */
	while (*cmd == ' ' || *cmd == '\t') cmd++;
	while (*cmd != ' ' && *cmd != '\t' && *cmd != '\r' && *cmd != '\n') cmd++;
	/* now count length of command line */
	tmp = cmd;
	for (cmd_len=0; *tmp != '\r' && *tmp != '\n'; tmp++, cmd_len++) ;
	*tmp = 0; /* null terminate line */
    }
    parse_command_line(cmd, cmd_len);

    /* Have fun */
    if (iwu_strlen(ini_file)) {
	iwu_profile_init(profile_buffer, 32768);
	if (!iwu_load_profile(ini_file)) {
	    print_error(CANT_LOAD_PROFILE);
	    return(CANT_LOAD_PROFILE);
	}
	iwu_get_profile_string("languages","default","error",buf,128,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    print_error(NO_CFG_LANGUAGE);
	    return(NO_CFG_LANGUAGE);
	}
	iwu_get_profile_string("languages",buf,"error",buf,128,ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") == 0) {
	    print_error(NO_CFG_LANGUAGE);
	    return(NO_CFG_LANGUAGE);
	}
	for (cp=buf; *cp && *cp != ','; cp++) ;
	if (*cp == ',') {
	    cp++;
	} else {
	    print_error(NO_CFG_LANGUAGE);
	    return(NO_CFG_LANGUAGE);
	}
	while (iwu_isspace(*cp)) cp++;
	iw_mls_init(cp);
	print_message(COPYRIGHT_MESSAGE);
	if (quiet) verbose = 0;
	if (do_help) {
	    print_message(USAGE_MESSAGE);
	}
	else if (os_get_vxd_info()) {
	    init_audio_device();
	    return(rc);
	}
	else {
	    read_only = 0;
	    if (pnp_cm_present()) {
		read_only = 1;
		find_pnp_cards(pnp_cm_cfg.rdp, pnp_cm_cfg.num_csns);
		legacy_rdp = pnp_cm_cfg.rdp;
	    }
	    else if (pnp_bios_present() && !broken_bios) {
		read_only = 1;
		find_pnp_cards(pnp_bios_cfg.rdp, pnp_bios_cfg.num_csns);
		legacy_rdp = pnp_bios_cfg.rdp;
	    }
	    else {
		iso_csns = IwavePnpPing(&iso_read_port);
		if (iso_csns == 0) {
		    OS_OUTPORTB(pidxr, IW_PCCCI);
		    OS_OUTPORTB(pnpwrp, 0x02);
		    delay(10);
		    OS_OUTPORTB(pnpwrp, 0x00);
		    delay(10);
		    IwavePnpKey();
		    OS_OUTPORTB(pidxr, IW_PCCCI);
		    OS_OUTPORTB(pnpwrp, 0x04);
		    delay(10);
		    OS_OUTPORTB(pnpwrp, 0x00);
		    delay(10);
		    IwavePnpRevokeKey();
		    iso_csns = IwavePnpIsol(&iso_read_port);
		}
		if (iso_csns) find_pnp_cards(iso_read_port, iso_csns);
		legacy_rdp = iso_read_port;
	    }
	}
	/* Do the legacy mode cards here vvv=============================*/
	/* Legacy mode cards MUST have a setup stanza in iw.ini */
	iwu_strcpy(setup, "setup 0");
	setup[6] += cards;
	iwu_get_profile_string(setup, "vendor_id", "error", buf, 128, ini_file);
	if (iwu_strcmp((char RFAR *)buf, (char RFAR *)"error") != 0) {
	    iwu_strcpy(pnpcfg.vendor, buf);
	    if (get_iw_cfg(&pnpcfg)) {
                if (!legacy_rdp) legacy_rdp = 0x203;
		pnpcfg.pnp_read_port = legacy_rdp;
		pnpcfg.pnp_csn = (char)(cards + LEGACY_CSN_OFFSET);
		if (!((cm_api && pnp_cm_cfg.num_csns) || (bios_api && pnp_bios_cfg.num_csns))) {
		    IwavePnpKey();
		    OS_OUTPORTB(pidxr, IW_PCCCI);
		    OS_OUTPORTB(pnpwrp, 0x05);
		    delay(10);
		    OS_OUTPORTB(pnpwrp, 0x00);
		    delay(10);
		}
		IwavePnpWake(0x00);
		OS_OUTPORTB(pidxr,0x00);
		OS_OUTPORTB(pnpwrp,(UCHAR)(pnpcfg.pnp_read_port>>2));
	    
		OS_OUTPORTB(pcsnbr,pnpcfg.pnp_csn);
		OS_OUTPORTB(pidxr,IW_PCSNI);
		OS_INPORTB(pnpcfg.pnp_read_port);
		IwavePnpRevokeKey();

		set_iw_pnp_cfg(&pnpcfg);
		IwavePnpGetCfg(&pnpcfg);
		if (verbose) print_iw_cfg(&pnpcfg);
		write_ini_settings(&pnpcfg, cards);
		init_audio_device();
		activate_card(pnpcfg.pnp_csn, turn_card_off);
		cards++;
	    }
	    else {
		if (quiet < 2) {
		    dprintf(get_message(INSUFFICIENT_LEGACY_DATA),
			    get_message(INSUFFICIENT_LEGACY_DATA_0 + get_iw_cfg_error - 1));
		}
	    }
	}

	/* Do the legacy mode cards here ^^^=============================*/
	if (!cards) print_message(NO_IW_CARDS_FOUND);
	if (config_changed) {
	    if (iwu_close_profile(ini_file)) {
		print_error(ERROR_WRITING_PROFILE);
	    }
	}
    }
    else {
	print_error(NO_INTERWAVE_ENVIRONMENT);
	rc = NO_INTERWAVE_ENVIRONMENT;
    }

    return(rc);
}
