/*++ 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 #include #include #include #include // // 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); }