458 lines
11 KiB
C
458 lines
11 KiB
C
|
/****************************************************************************
|
|||
|
*
|
|||
|
* 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 <windows.h> // The VDD is just a win32 DLL
|
|||
|
#include <vddsvc.h> // Definition of VDD calls
|
|||
|
#include "vdd.h" // Common data with kernel driver
|
|||
|
#include <stdio.h>
|
|||
|
|
|||
|
/*
|
|||
|
* 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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|