/**************************************************************************** * * config.c * * Copyright (c) 1991 Microsoft Corporation. All Rights Reserved. * ***************************************************************************/ /* * Definition of interface to kernel driver (synth.sys) * * The kernel driver's Dos device name is assumed fixed and known * * adlib.mid or adlib.mid0 * * The kernel driver is opened in read/write mode. * * Writing to the driver sends a list of SYNTH_DATA structures * to the driver. The port number MUST be 0x388 or 0x389. * * * Reading always reads just 1 byte - the status port. */ #include // The VDD is just a win32 DLL #include // Definition of VDD calls #include "vdd.h" // Common data with kernel driver #include /* * Debugging */ #if DBG int VddDebugLevel = 1; /*************************************************************************** Generate debug output in printf type format ****************************************************************************/ void VddDbgOut(LPSTR lpszFormat, ...) { char buf[256]; va_list va; OutputDebugStringA("Ad Lib VDD: "); va_start(va, lpszFormat); vsprintf(buf, lpszFormat, va); va_end(va); OutputDebugStringA(buf); OutputDebugStringA("\r\n"); } #define dprintf( _x_ ) VddDbgOut _x_ #define dprintf1( _x_ ) if (VddDebugLevel >= 1) VddDbgOut _x_ #define dprintf2( _x_ ) if (VddDebugLevel >= 2) VddDbgOut _x_ #define dprintf3( _x_ ) if (VddDebugLevel >= 3) VddDbgOut _x_ #define dprintf4( _x_ ) if (VddDebugLevel >= 4) VddDbgOut _x_ #else #define dprintf(x) #define dprintf1(x) #define dprintf2(x) #define dprintf3(x) #define dprintf4(x) #endif // DBG /* * Symbolic names for port addresses */ #define ADLIB_DATA_PORT 0x389 #define ADLIB_REGISTER_SELECT_PORT 0x388 #define ADLIB_STATUS_PORT 0x388 /* * Batch data to the device - for true Adlib use a size of 2 */ #define BATCH_SIZE 40 int Position = 0; SYNTH_DATA PortData[BATCH_SIZE]; /* * Internal Routines */ void MyByteIn(WORD port, BYTE *data); void MyByteOut(WORD port, BYTE data); /* * IO handler table. * * There's no point in providing string handlers because the chip * can't respond very quickly (need gaps of at least 23 microseconds * between writes). */ VDD_IO_HANDLERS handlers = { MyByteIn, NULL, NULL, NULL, MyByteOut, NULL, NULL, NULL}; /* * Note that we rely on the kernel driver to pretend the device is * at address 388 even the driver supports it somewhere else. */ VDD_IO_PORTRANGE ports[] = { { 0x228, 0x229 }, { 0x388, 0x389 } }; /* * Globals */ // // Track timers. The basic rule is that if no timer is started then // the only way the status register can change is via the reset bit // in which case we know what will happen. // // If a timer interrupts then it's 'stopped' // BOOL Timer1Started; BOOL Timer2Started; BYTE Status; /* * Current device handle * * NULL if device is (potentially) free * INVALID_HANDLE_VALUE if device was not obtainable */ HANDLE DeviceHandle; HANDLE OpenDevice(PWSTR DeviceName) { WCHAR DosDeviceName[MAX_PATH]; /* * Make up a string suitable for opening a Dos device */ wcscpy(DosDeviceName, TEXT("\\\\.")); wcscat(DosDeviceName, DeviceName + wcslen(TEXT("\\Device"))); /* * Open the device with GENERIC_READ and GENERIC_WRITE * Also use FILE_SHARE_WRITE so other applications can * set the device volume */ return CreateFile(DosDeviceName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); } /* * Open our device is it can be opened and we haven't tried before * * Returns FALSE if device can't be acquired. */ BOOL CheckDeviceAccess(void) { /* * If we don't have a handle (valid or invalid) already try * opening the device */ if (DeviceHandle == NULL) { DeviceHandle = OpenDevice(STR_ADLIB_DEVICENAME); if (DeviceHandle == INVALID_HANDLE_VALUE) { DeviceHandle = OpenDevice(STR_ADLIB_DEVICENAME L"0"); } Position = 0; } return DeviceHandle != INVALID_HANDLE_VALUE; } /* * Map a write to a port * * How are we going to simulate timer stuff? * Answer: Allow reading of the status port. * * This is optimized to only write when we get a data port write */ void MyByteOut(WORD port, BYTE data) { // // Remember what register is selected // static BYTE AdlibRegister; // // Just package the stuff up and call write file // DWORD BytesWritten; dprintf3(("Received write to Port %4X, Data %2X", port, data)); port = (port & 1) | ADLIB_REGISTER_SELECT_PORT; /* * Check for special values - don't let them switch to * OPL3 mode. */ #if 0 if (port == ADLIB_DATA_PORT && AdlibRegister == AD_NEW) { data &= 0xFE; } #endif if (port == ADLIB_REGISTER_SELECT_PORT) { /* * Just remember which register is supposed to be selected * to cut down the number of times we go to the device driver */ AdlibRegister = data; } else { /* * Write this one to the device */ PortData[Position].IoPort = ADLIB_REGISTER_SELECT_PORT; PortData[Position].PortData = AdlibRegister; PortData[Position + 1].IoPort = port; PortData[Position + 1].PortData = data; Position += 2; if (Position == BATCH_SIZE || AdlibRegister >= 0xA0 && AdlibRegister <= 0xBF || AdlibRegister == AD_MASK) { /* * See if we have the device */ if (CheckDeviceAccess()) { if (!WriteFile(DeviceHandle, &PortData, Position * sizeof(PortData[0]), &BytesWritten, NULL)) { dprintf1(("Failed to write to device!")); } else { /* * Work out what status change may have occurred */ if (AdlibRegister == AD_MASK) { /* * Look for RST and starting timers */ if (data & 0x80) { Status = 0; } /* * We ignore starting of timers if their interrupt * flag is set because the timer status will have to * be set again to make the status for this timer change */ if ((data & 1) && !(Status & 0x40)) { dprintf2(("Timer 1 started")); #if 0 Timer1Started = TRUE; #else Status |= 0xC0; #endif } else { Timer1Started = FALSE; } if ((data & 2) && !(Status & 0x20)) { dprintf2(("Timer 2 started")); #if 0 Timer2Started = TRUE; #else Status |= 0xA0; #endif Timer2Started = TRUE; } else { Timer2Started = FALSE; } } } } Position = 0; } } } /* * Gets called when the application reads from one of our ports. * We know the device only returns interesting things in the status port. */ void MyByteIn(WORD port, BYTE *data) { DWORD BytesRead; dprintf4(("Received read from Port %4X", port)); port = (port & 1) | ADLIB_STATUS_PORT; /* * If we fail simulate nothing at the port */ *data = 0xFF; /* * Say there's nothing there if we didn't get the device driver or * it's not the status port */ if (port != ADLIB_STATUS_PORT || !CheckDeviceAccess()) { return; } #if 0 // WSS interrupt messed this up /* * Are we expecting a state change ? */ if (Timer1Started || Timer2Started) { /* * Read the status port from the driver - this is how the * driver interprets read. * Well, actually don't because the WSS driver doesn't work! */ if (!ReadFile(DeviceHandle, &Status, 1, &BytesRead, NULL)) { dprintf1(("Failed to read from device - code %d", GetLastError())); } else { /* * Look for state change */ if (Status & 0x40) { Timer1Started = FALSE; dprintf2(("Timer 1 finished")); } if (Status & 0x20) { Timer2Started = FALSE; dprintf2(("Timer 2 finished")); } } } #endif dprintf3(("Data read was %2X", Status)); *data = Status; } /* * Standard DLL entry point routine. */ BOOL DllEntryPoint(HINSTANCE hInstance, DWORD Reason, LPVOID Reserved) { switch (Reason) { case DLL_PROCESS_ATTACH: if (!VDDInstallIOHook(hInstance, 2, ports, &handlers)) { dprintf2(("Ad Lib VDD failed to load - error in VDDInstallIoHook")); return FALSE; } else { dprintf2(("Ad Lib VDD loaded OK")); return TRUE; } case DLL_PROCESS_DETACH: VDDDeInstallIOHook(hInstance, 2, ports); /* * Note that this event corresponds to FreeLibrary on our DLL, * NOT termination of the process - so we can't rely on process * termination to close our device handle. * */ if (DeviceHandle) { CloseHandle(DeviceHandle); DeviceHandle = NULL; // Redundant but neater. } return TRUE; default: return TRUE; } }