NT4/private/ntos/dd/sound/soundlib/synth.c
2020-09-30 17:12:29 +02:00

1010 lines
24 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1993 Microsoft Corporation
Module Name:
init.c
Abstract:
This module contains code for the initialization phase of the
Midi Synthesiser device
NOTE: This code ASSUMES the volume setting is controlled by mixer code.
Author:
Geraint Davies
Environment:
Kernel mode
Revision History:
--*/
#include <soundlib.h>
#include <synthdrv.h>
#include <synth.h>
#include <string.h>
#include <stdlib.h>
//
// Local definitions
//
BOOLEAN
SynthExcludeRoutine(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN SOUND_EXCLUDE_CODE Code
);
NTSTATUS
SynthDispatch(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack
);
BOOLEAN SynthPresent(PUCHAR base, PUCHAR inbase, volatile BOOLEAN * Fired);
BOOLEAN
SynthMidiIsOpl3(
IN PSYNTH_HARDWARE pHw,
IN OUT volatile BOOLEAN * Fired
);
NTSTATUS
SynthPortValid(
IN OUT PGLOBAL_SYNTH_INFO pGDI,
IN ULONG Port,
IN volatile BOOLEAN * Fired
);
VOID
SynthMidiQuiet(
IN UCHAR DeviceIndex,
IN PSYNTH_HARDWARE pHw
);
NTSTATUS
SynthMidiData(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION pIrpStack
);
//
// Remove initialization stuff from resident memory
//
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,SynthInit)
#pragma alloc_text(INIT,SynthPresent)
#pragma alloc_text(INIT,SynthPortValid)
#pragma alloc_text(INIT,SynthMidiIsOpl3)
#pragma alloc_text(INIT,SynthInit)
#pragma alloc_text(PAGE,SynthCleanup)
#pragma alloc_text(PAGE,SynthMidiQuiet)
#pragma alloc_text(PAGE,SynthMidiData)
#pragma alloc_text(PAGE,SynthDispatch)
#pragma alloc_text(PAGE,SynthExcludeRoutine)
#endif
//
// Device initialization data
//
CONST SOUND_DEVICE_INIT SynthDeviceInit =
{
NULL, NULL,
0x80000000, // 50% volume
FILE_DEVICE_MIDI_OUT,
SYNTH_DEVICE,
"LDMo",
STR_ADLIB_DEVICENAME,
NULL,
SynthExcludeRoutine,
SynthDispatch, // Handles CREATE, CLOSE and WRITE
NULL, // Low level driver takes care of caps
SoundNoVolume,
0
};
BOOLEAN
SynthExcludeRoutine(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN SOUND_EXCLUDE_CODE Code
)
/*++
Routine Description:
Perform mutual exclusion for our devices
Arguments:
pLDI - device info for the device being open, closed, entered or left
Code - Function to perform (see devices.h)
Return Value:
The function value is the final status from the initialization operation.
--*/
{
PGLOBAL_SYNTH_INFO pGDI;
BOOLEAN ReturnCode;
pGDI = pLDI->pGlobalInfo;
ReturnCode = FALSE;
switch (Code) {
case SoundExcludeOpen:
if (pGDI->DeviceInUse == 0xFF) {
pGDI->DeviceInUse = AdlibDevice;
ReturnCode = TRUE;
}
break;
case SoundExcludeClose:
ReturnCode = TRUE;
ASSERT(pGDI->DeviceInUse == AdlibDevice);
pGDI->DeviceInUse = 0xFF;
break;
case SoundExcludeEnter:
ReturnCode = TRUE;
KeWaitForSingleObject(&pGDI->MidiMutex,
Executive,
KernelMode,
FALSE, // Not alertable
NULL);
break;
case SoundExcludeLeave:
ReturnCode = TRUE;
KeReleaseMutex(&pGDI->MidiMutex, FALSE);
break;
case SoundExcludeQueryOpen:
ReturnCode = (BOOLEAN)(pGDI->DeviceInUse == (UCHAR)AdlibDevice);
break;
}
return ReturnCode;
}
NTSTATUS
SynthInit(
IN PDRIVER_OBJECT pDriverObject,
IN PWSTR RegistryPathName,
IN PGLOBAL_SYNTH_INFO pGDI,
IN ULONG SynthPort,
IN BOOLEAN InterruptConnected,
IN INTERFACE_TYPE BusType,
IN ULONG BusNumber,
IN PMIXER_DATA_ITEM MidiOutItem,
IN UCHAR VolumeControlId,
IN BOOLEAN Multiple,
IN SOUND_DISPATCH_ROUTINE *DevCapsRoutine
)
/*++
Routine Description:
This routine performs initialization for the synth
device driver when it is first loaded.
It is called DriverEntry by convention as this is the entry
point the IO subsystem looks for by default.
The design is as follows :
0. Cleanup is always by calling SoundCleanup. This routine
is also called by the unload entry point.
1. Find which bus our device is on (this is needed for
mapping things via the Hal).
1. Allocate space to store our global info
1. Open the driver's registry information and read it
2. Fill in the driver object with our routines
3. Create device
Currently, one device only is created -
1. Midi output
Customize each device type and initialize data
Also store the registry string in our global info so we can
open it again to store volume settings etc on shutdown
4. Check hardware conflicts by calling IoReportResourceUsage
for each device (as required)
(this may need to be called again later if
5. Find our IO port and check the device is really there
Arguments:
pDriverObject - Pointer to a driver object.
pGDI - sythesizer global info - set to 0
SynthPort - Port to use
TestInterrupted - Optional routine to use to test interrupt fired
BusType - Type of bus to try
BusNumber - Number of bus
Return Value:
The function value is the final status from the initialization operation.
--*/
{
/********************************************************************
*
* Local variables
*
********************************************************************/
//
// Return code from last function called
//
NTSTATUS Status;
//
// Pointer to our device info after we create it
//
PLOCAL_DEVICE_INFO pLDI;
/********************************************************************
*
* Fill in our global info
*
********************************************************************/
pGDI->Key = SYNTH_KEY;
pGDI->Hw.Key = SYNTH_HARDWARE_KEY;
pGDI->DriverObject = pDriverObject;
pGDI->DeviceInUse = 0xFF; // Free
pGDI->DevCapsRoutine = DevCapsRoutine;
KeInitializeMutex(&pGDI->MidiMutex,
1 // Level
);
pGDI->BusType = BusType;
pGDI->BusNumber = BusNumber;
//
// Create our device
//
Status = SoundCreateDevice(
&SynthDeviceInit,
(UCHAR)
(Multiple ?
0 :
SOUND_CREATION_NO_NAME_RANGE), // No range for midi out
pDriverObject,
pGDI,
NULL,
&pGDI->Hw,
0,
&pGDI->DeviceObject);
if (!NT_SUCCESS(Status)) {
dprintf1(("Failed to create device %ls - status %8X",
SynthDeviceInit.PrototypeName, Status));
return Status;
}
pLDI = pGDI->DeviceObject->DeviceExtension;
//
// Set the default volume
//
pLDI->Volume.Left = 0x80000000; // Half volume
pLDI->Volume.Right = 0x80000000; // Half volume
//
// Check the synthesizer config
//
Status = SynthPortValid(
pGDI,
SynthPort,
InterruptConnected ? &pGDI->InterruptFired : NULL);
if (!NT_SUCCESS(Status)) {
dprintf1(("Problem finding synthesizer hardware!"));
SoundFreeDevice(pGDI->DeviceObject);
pGDI->DeviceObject = NULL;
return Status;
}
//
// Save the device name where the non-kernel part can pick it up.
//
Status = SoundSaveDeviceName(RegistryPathName, pLDI);
if (!NT_SUCCESS(Status)) {
return Status;
}
pLDI->VolumeControlId = VolumeControlId;
//
// only allow Opl3 mode if there is
// a new (opl3-compatible) synthesizer chip
//
pGDI->IsOpl3 = SynthMidiIsOpl3(
&pGDI->Hw,
InterruptConnected ? &pGDI->InterruptFired : NULL);
SynthMidiQuiet(AdlibDevice, &pGDI->Hw);
return STATUS_SUCCESS;
}
VOID
SynthCleanup(
IN PGLOBAL_SYNTH_INFO pGDI
)
/*++
Routine Description:
Clean up all resources allocated by our initialization
Arguments:
pGDI - Pointer to global data
Return Value:
NONE
--*/
{
//
// Owning driver will remove all devices and free global data
//
if (pGDI->Hw.SynthBase && pGDI->MemType == 0) {
ASSERT(pGDI->Key == SYNTH_KEY);
MmUnmapIoSpace(pGDI->Hw.SynthBase, NUMBER_OF_SYNTH_PORTS);
}
pGDI->Hw.SynthBase = NULL;
}
NTSTATUS
SynthMidiData(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION pIrpStack
)
/*++
Routine Description:
The user has passed in a buffer of midi data to play
The buffer is validated and the data passed to the device
Arguments:
pLDI - Local wave device info
pIrp - The IO request packet
pIrpStack - The current stack location
Return Value:
Irp status
--*/
{
NTSTATUS Status;
ULONG Length;
PSYNTH_DATA pData;
PUCHAR SynthBase;
Length = pIrpStack->Parameters.Write.Length;
pData = (PSYNTH_DATA)pIrp->UserBuffer;
ASSERTMSG("Synth not correctly initialized",
((PSYNTH_HARDWARE)pLDI->HwContext)->Key == SYNTH_HARDWARE_KEY);
SynthBase = ((PSYNTH_HARDWARE)pLDI->HwContext)->SynthBase;
if (Length % sizeof(SYNTH_DATA) != 0) {
return STATUS_BUFFER_TOO_SMALL;
}
Status = STATUS_SUCCESS;
try {
for ( ; Length != 0 ; Length -= sizeof(SYNTH_DATA), pData++) {
USHORT IoPort;
IoPort = pData->IoPort;
if (IoPort < SYNTH_PORT ||
IoPort >= SYNTH_PORT + NUMBER_OF_SYNTH_PORTS) {
Status = STATUS_INVALID_PARAMETER;
break;
}
WRITE_PORT_UCHAR(SynthBase + (IoPort - SYNTH_PORT),
(UCHAR)pData->PortData);
//
// Make sure the SYNTH can keep up
// newer boards need 1us - older boards need
// 3.3 us after selecting the register and 23 us after
// writing the data - as we don't know which is which, we
// wait 23us after every write
//
if (pLDI->DeviceIndex == AdlibDevice) {
KeStallExecutionProcessor(23);
} else {
KeStallExecutionProcessor(10);
}
}
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = STATUS_ACCESS_VIOLATION;
}
pIrp->IoStatus.Information = pIrpStack->Parameters.Write.Length - Length;
return Status;
}
NTSTATUS
SynthMidiReadPort(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION pIrpStack
)
/*++
Routine Description:
The user has passed in a buffer to return the status port value
The buffer is validated and data returned if it's length 1
Arguments:
pLDI - Local wave device info
pIrp - The IO request packet
pIrpStack - The current stack location
Return Value:
Irp status
--*/
{
NTSTATUS Status;
ULONG Length;
PUCHAR SynthBase;
PUCHAR pData;
Length = pIrpStack->Parameters.Read.Length;
pData = (PUCHAR)pIrp->UserBuffer;
SynthBase = ((PSYNTH_HARDWARE)pLDI->HwContext)->SynthBase;
if (Length != sizeof(UCHAR)) {
return STATUS_INVALID_PARAMETER;
}
Status = STATUS_SUCCESS;
try {
*pData = READ_PORT_UCHAR(SynthBase);
pIrp->IoStatus.Information = sizeof(UCHAR);
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = STATUS_ACCESS_VIOLATION;
}
return Status;
}
NTSTATUS
SynthDispatch(
IN OUT PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack
)
/*++
Routine Description:
Driver function dispatch routine
Arguments:
pLDI - local device info
pIrp - Pointer to IO request packet
IrpStack - current stack location
Return Value:
Return status from dispatched routine
--*/
{
NTSTATUS Status;
Status = STATUS_SUCCESS;
//
// Initialize the irp information field.
//
pIrp->IoStatus.Information = 0;
switch (IrpStack->MajorFunction) {
case IRP_MJ_CREATE:
pLDI->DeviceIndex = AdlibDevice;
Status = SoundSetShareAccess(pLDI, IrpStack);
if (NT_SUCCESS(Status) && IrpStack->FileObject->WriteAccess) {
// reset board to silence and to correct mode (opl3 or opl2)
SynthMidiQuiet(pLDI->DeviceIndex, pLDI->HwContext);
//
// Note changed line state
//
SoundLineNotify(pLDI, 0);
}
break;
case IRP_MJ_WRITE:
if (IrpStack->FileObject->WriteAccess) {
Status = SynthMidiData(pLDI, pIrp, IrpStack);
} else {
Status = STATUS_ACCESS_DENIED;
}
break;
case IRP_MJ_READ:
if (IrpStack->FileObject->WriteAccess) {
Status = SynthMidiReadPort(pLDI, pIrp, IrpStack);
} else {
Status = STATUS_ACCESS_DENIED;
}
break;
case IRP_MJ_DEVICE_CONTROL:
switch (IrpStack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_MIDI_SET_VOLUME:
if (pLDI->PreventVolumeSetting) {
Status = STATUS_ACCESS_DENIED;
} else {
Status = SoundIoctlSetVolume(pLDI, pIrp, IrpStack);
}
break;
case IOCTL_MIDI_GET_VOLUME:
Status = SoundIoctlGetVolume(pLDI, pIrp, IrpStack);
break;
case IOCTL_SOUND_GET_CHANGED_VOLUME:
Status = SoundIoctlGetChangedVolume(pLDI, pIrp, IrpStack);
break;
case IOCTL_MIDI_GET_CAPABILITIES:
Status = (*((PGLOBAL_SYNTH_INFO)pLDI->pGlobalInfo)->
DevCapsRoutine)(pLDI, pIrp, IrpStack);
break;
case IOCTL_MIDI_SET_OPL3_MODE:
if (IrpStack->FileObject->WriteAccess) {
if (((PGLOBAL_SYNTH_INFO)pLDI->pGlobalInfo)->IsOpl3) {
pLDI->DeviceIndex = Opl3Device;
SynthMidiQuiet(Opl3Device, pLDI->HwContext);
Status = STATUS_SUCCESS;
} else {
Status = STATUS_INVALID_DEVICE_REQUEST;
}
} else {
Status = STATUS_ACCESS_DENIED;
}
break;
default:
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
break;
case IRP_MJ_CLOSE:
Status = STATUS_SUCCESS;
break;
case IRP_MJ_CLEANUP:
if (IrpStack->FileObject->WriteAccess) {
SynthMidiQuiet(pLDI->DeviceIndex, pLDI->HwContext);
(*pLDI->DeviceInit->ExclusionRoutine)(pLDI, SoundExcludeClose);
pLDI->PreventVolumeSetting = FALSE;
//
// Note changed line state
//
SoundLineNotify(pLDI, 0);
} else {
Status = STATUS_SUCCESS;
}
break;
default:
dprintf1(("Unimplemented major function requested: %08lXH", IrpStack->MajorFunction));
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
//
// Tell the IO subsystem we're done. If the Irp is pending we
// don't touch it as it could be being processed by another
// processor (or may even be complete already !).
//
return Status;
}
VOID
SynthMidiSendFM(
IN PUCHAR PortBase,
IN ULONG Address,
IN UCHAR Data
)
{
// these delays need to be 23us at least for old opl2 chips, even
// though new chips can handle 1 us delays.
WRITE_PORT_UCHAR(PortBase + (Address < 0x100 ? 0 : 2), (UCHAR)Address);
KeStallExecutionProcessor(23);
WRITE_PORT_UCHAR(PortBase + (Address < 0x100 ? 1 : 3), Data);
KeStallExecutionProcessor(23);
}
VOID
SynthMidiQuiet(
IN UCHAR DeviceIndex,
IN PSYNTH_HARDWARE pHw
)
/*++
Routine Description:
Initialize the SYNTH hardware to silence
Arguments:
pHw - hardware data
Return Value:
None
--*/
{
ULONG i;
// D1 ("\nSynthMidiQuietFM");
/*
* sequence to initialise and silence the device
* depends on whether it is an Opl3 device or not
*/
if (DeviceIndex == Opl3Device) {
/* tell the FM chip to use 4-operator mode, and
fill in any other random variables */
SynthMidiSendFM (pHw->SynthBase, AD_NEW, 0x01);
SynthMidiSendFM (pHw->SynthBase, AD_MASK, 0x60);
SynthMidiSendFM (pHw->SynthBase, AD_CONNECTION, 0x3f);
SynthMidiSendFM (pHw->SynthBase, AD_NTS, 0x00);
/* turn off the drums, and use high vibrato/modulation */
SynthMidiSendFM (pHw->SynthBase, AD_DRUM, 0xc0);
/* turn off all the oscillators */
for (i = 0; i < 0x15; i++) {
SynthMidiSendFM (pHw->SynthBase, AD_LEVEL + i, 0x3f);
SynthMidiSendFM (pHw->SynthBase, AD_LEVEL2 + i, 0x3f);
};
/* turn off all the voices */
for (i = 0; i < 0x08; i++) {
SynthMidiSendFM (pHw->SynthBase, AD_BLOCK + i, 0x00);
SynthMidiSendFM (pHw->SynthBase, AD_BLOCK2 + i, 0x00);
};
} else {
/*
* this array gives the offsets of the slots within an opl2
* chip. This is needed to set the attenuation for all slots to max,
* to ensure that the chip is silenced completely - switching off the
* voices alone will not do this.
*/
static CONST BYTE offsetSlot[] = {
0, 1, 2, 3, 4, 5,
8, 9, 10, 11, 12, 13,
16, 17, 18, 19, 20, 21
};
/* base adlib hardware initialisation */
// ensure that if we have a opl3 chip, we are in base mode
SynthMidiSendFM (pHw->SynthBase, AD_NEW, 0x00);
// turn off all the slot oscillators
for (i = 0; i < 18; i++) {
SynthMidiSendFM(pHw->SynthBase, offsetSlot[i] + AD_LEVEL, 0x3f);
}
/* silence all voices */
for (i = 0; i <= 8; i++) {
SynthMidiSendFM(pHw->SynthBase, (AD_FNUMBER | i), 0);
SynthMidiSendFM(pHw->SynthBase, (AD_BLOCK | i), 0);
}
/* switch to percussive mode and silence percussion instruments */
SynthMidiSendFM(pHw->SynthBase, AD_DRUM, (BYTE)0x20);
}
}
BOOLEAN
SynthPresent(PUCHAR base, PUCHAR inbase, volatile BOOLEAN *Fired)
/*++
Routine Description:
Detect the presence or absence of a 3812 (adlib-compatible) synthesizer
at the given i/o address by starting the timer and looking for an
overflow. Can be used to detect left and right synthesizers separately.
Arguments:
base - base output address
inbase - base input address
Return Value:
TRUE if a synthesizer is present at that address
--*/
{
#define inport(port) READ_PORT_UCHAR((PUCHAR)(port))
UCHAR t1, t2;
if (Fired) {
*Fired = FALSE;
}
// check if the chip is present
SynthMidiSendFM(base, 4, 0x60); // mask T1 & T2
SynthMidiSendFM(base, 4, 0x80); // reset IRQ
t1 = inport(inbase); // read status register
SynthMidiSendFM(base, 2, 0xff); // set timer - 1 latch
SynthMidiSendFM(base, 4, 0x21); // unmask & start T1
// this timer should go off in 80 us. It sometimes
// takes more than 100us, but will always have expired within
// 200 us if it is ever going to.
KeStallExecutionProcessor(200);
if (Fired && *Fired) {
return TRUE;
}
t2 = inport(inbase); // read status register
SynthMidiSendFM(base, 4, 0x60);
SynthMidiSendFM(base, 4, 0x80);
if (!((t1 & 0xE0) == 0) || !((t2 & 0xE0) == 0xC0)) {
return(FALSE);
}
return TRUE;
#undef inport
}
NTSTATUS
SynthPortValid(
IN OUT PGLOBAL_SYNTH_INFO pGDI,
IN ULONG Port,
IN volatile BOOLEAN * Fired
)
{
NTSTATUS Status;
//
// Check we're going to be allowed to use this port or whether
// some other device thinks it owns this hardware
//
Status = SoundReportResourceUsage(
pGDI->DeviceObject,
pGDI->BusType,
pGDI->BusNumber,
NULL,
0,
FALSE,
NULL,
&Port,
NUMBER_OF_SYNTH_PORTS);
if (!NT_SUCCESS(Status)) {
return Status;
}
//
// Find where our device is mapped
//
pGDI->Hw.SynthBase = SoundMapPortAddress(
pGDI->BusType,
pGDI->BusNumber,
Port,
NUMBER_OF_SYNTH_PORTS,
&pGDI->MemType);
{
PUCHAR base;
base = pGDI->Hw.SynthBase;
if (!SynthPresent(base, base, Fired)) {
dprintf1(("No synthesizer present"));
//
// Unmap the memory
//
SynthCleanup(pGDI);
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
}
return STATUS_SUCCESS;
}
BOOLEAN
SynthMidiIsOpl3(
IN PSYNTH_HARDWARE pHw,
IN OUT volatile BOOLEAN * Fired
)
/*++
Routine Description:
Check if the midi synthesizer is Opl3 compatible or just adlib-compatible.
Arguments:
pHw - hardware data
Return Value:
TRUE if OPL3-compatible chip. FALSE otherwise.
--*/
{
BOOLEAN bReturn = FALSE;
PUCHAR Port = pHw->SynthBase;
/*
* theory: an opl3-compatible synthesizer chip looks
* exactly like two separate 3812 synthesizers (for left and right
* channels) until switched into opl3 mode. Then, the timer-control
* register for the right half is replaced by a channel connection register
* (among other changes).
*
* We can detect 3812 synthesizers by starting a timer and looking for
* timer overflow. So if we find 3812s at both left and right addresses,
* we then switch to opl3 mode and look again for the right-half. If we
* still find it, then the switch failed and we have an old synthesizer
* if the right half disappeared, we have a new opl3 synthesizer.
*
* NB we use either monaural base-level synthesis, or stereo opl3
* synthesis. If we discover two 3812s (as on early SB Pro and
* PAS), we ignore one of them.
*/
/*
* nice theory - but wrong. The timer on the right half of the
* opl3 chip reports its status in the left-half status register.
* There is no right-half status register on the opl3 chip.
*/
/* ensure base mode */
SynthMidiSendFM (Port, AD_NEW, 0x00);
KeStallExecutionProcessor(20);
/* look for right half of chip */
if (SynthPresent(Port + 2, Port, Fired)) {
/* yes - is this two separate chips or a new opl3 chip ? */
/* switch to opl3 mode */
SynthMidiSendFM (Port, AD_NEW, 0x01);
KeStallExecutionProcessor(20);
if (!SynthPresent(Port + 2, Port, Fired)) {
/* right-half disappeared - so opl3 */
bReturn = TRUE;
}
}
/* reset to 3812 mode */
SynthMidiSendFM (Port, AD_NEW, 0x00);
KeStallExecutionProcessor(20);
return(bReturn);
}