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;
|
||
}
|
||
}
|
||
|
||
|
||
|