windows-nt/Source/XPSP1/NT/multimedia/media/drivers/drvlib/drvutil.c

972 lines
24 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/****************************************************************************
*
* drvutil.c
*
* Multimedia kernel driver support component (drvlib)
*
* Copyright (c) 1993-1995 Microsoft Corporation
*
* Support functions common to multiple multi-media drivers :
*
* -- Open a device
* -- Translate a kernel IO return code to a multi-media return code
* -- Count the number of devices of a given type
* -- Set and Get data synchronously to a kernel device
*
* History
* 01-Feb-1992 - Robin Speed (RobinSp) wrote it
* 04-Feb-1992 - Reviewed by SteveDav
*
***************************************************************************/
#include <drvlib.h>
#include <ntddwave.h>
#include <ntddmidi.h>
#include <ntddaux.h>
#include <registry.h>
/*
** External name of driver
*/
extern TCHAR DriverName[];
/*
** We need an hInstance module handle to get strings from since the kernel
** driver returns string Ids in its structures
*/
extern HINSTANCE hInstance;
/*
** Internal structures
*/
/*
** List of cached device descriptions. This is generated when the
** driver is loaded from the data in the registry so that multiple
** queries are faster and consistent.
*/
typedef struct _DEVICE_LIST {
struct _DEVICE_LIST *Next; // Next pointer
DWORD DeviceType; // Type of device
ULONG CardIndex; // Card entry registry key index
PVOID DeviceInstanceData; // Instance data for this specific device
// if needed.
ULONG DeviceInstanceDataSize;
WCHAR Name[1]; // Device name
} DEVICE_LIST, *PDEVICE_LIST;
PDEVICE_LIST DeviceList;
/*
** Internal routines
*/
PDEVICE_LIST sndFindDevice(DWORD DeviceType, DWORD dwId);
BOOL sndProcessDevices(HKEY DevicesKey, PDEVICE_LIST *DeviceList, DWORD CardIndex);
BOOL sndAppendToDeviceList(PDEVICE_LIST *pList, DWORD DeviceType, DWORD CardIndex,
LPWSTR Name);
VOID sndFreeDeviceList(VOID);
BOOL sndEnumKey(HKEY Key, DWORD Index, LPTSTR OurKey, HKEY *pSubkey);
/****************************************************************************
* @doc INTERNAL
*
* @api MMRESULT | sndOpenDev | Open the kernel driver device corresponding
* to a logical wave device id
*
* @parm UINT | DeviceType | The type of device
*
* @parm DWORD | dwId | The device id
*
* @parm PHANDLE | phDev | Where to return the kernel device handle
*
* @parm ACCESS_MASK | Access | The desired access
*
* @comm For our sound devices the only relevant access are read and
* read/write. Device should ALWAYS allow opens for read unless some
* resource or access-rights restriction occurs.
***************************************************************************/
MMRESULT sndOpenDev(UINT DeviceType, DWORD dwId,
PHANDLE phDev, DWORD Access)
{
PDEVICE_LIST List;
WCHAR cDev[SOUND_MAX_DEVICE_NAME + 4];
List = sndFindDevice(DeviceType, dwId);
*phDev = INVALID_HANDLE_VALUE; // Always initialise the return value
if (List == NULL) {
return MMSYSERR_BADDEVICEID;
}
//
// Create the device name and open it - remove '\Device'
//
wsprintf(cDev, L"\\\\.\\%ls", List->Name);
*phDev = CreateFile(cDev,
Access,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
Access != GENERIC_READ ? FILE_FLAG_OVERLAPPED : 0,
NULL);
//
// Check up on the driver for refusing to open
// multiply for read
//
WinAssert(!(GetLastError() == ERROR_ACCESS_DENIED &&
Access == GENERIC_READ));
//
// Return status to caller
//
return *phDev != INVALID_HANDLE_VALUE ? MMSYSERR_NOERROR : sndTranslateStatus();
}
/****************************************************************************
* @doc INTERNAL
*
* @api void | sndTranslateStatus | This function translates an NT status
* code into a multi-media error code as far as possible.
*
* @parm NTSTATUS | Status | The NT base operating system return status.
*
*
* @rdesc The multi-media error code.
***************************************************************************/
DWORD sndTranslateStatus(void)
{
#if DBG
UINT n;
switch (n=GetLastError()) {
#else
switch (GetLastError()) {
#endif
case NO_ERROR:
case ERROR_IO_PENDING:
return MMSYSERR_NOERROR;
case ERROR_BUSY:
return MMSYSERR_ALLOCATED;
case ERROR_NOT_SUPPORTED:
case ERROR_INVALID_FUNCTION:
return MMSYSERR_NOTSUPPORTED;
case ERROR_NOT_ENOUGH_MEMORY:
return MMSYSERR_NOMEM;
case ERROR_ACCESS_DENIED:
return MMSYSERR_BADDEVICEID;
case ERROR_INSUFFICIENT_BUFFER:
return MMSYSERR_INVALPARAM;
default:
dprintf2(("sndTranslateStatus: LastError = %d", n));
return MMSYSERR_ERROR;
}
}
/****************************************************************************
* @doc INTERNAL
*
* @api DWORD | sndGetNumDevs | This function returns the number of (kernel)
*
* @parm UINT | DeviceType | The Device type
*
* @rdesc The number of devices.
***************************************************************************/
DWORD sndGetNumDevs(UINT DeviceType)
{
//
// Search the device list for devices of our type
//
int i;
PDEVICE_LIST List;
for (List = DeviceList, i= 0; List != NULL; List = List->Next) {
if (List->DeviceType == DeviceType) {
i++;
}
}
return i;
}
/****************************************************************************
* @doc INTERNAL
*
* @api DWORD | sndSetData | This function sets the volume given a device id
* and could be used for other soft value setting
* when only read access is required to the device
*
* @parm UINT | DeviceType | The Device type
*
* @parm UINT | DeviceId | The device id
*
* @parm UINT | Length | Length of data to set
*
* @parm PBYTE | Data | Data to set
*
* @parm ULONG | Ioctl | The Ioctl to use
*
* @rdesc MM... return code.
***************************************************************************/
MMRESULT sndSetData(UINT DeviceType, UINT DeviceId, UINT Length, PBYTE Data,
ULONG Ioctl)
{
HANDLE hDev;
MMRESULT mRet;
DWORD BytesReturned;
//
// Open the device
//
mRet = sndOpenDev(DeviceType, DeviceId, &hDev, GENERIC_READ);
if (mRet != MMSYSERR_NOERROR) {
return mRet;
}
//
// Set our data.
//
// Setting the overlapped parameter (last) to null means we
// wait until the operation completes.
//
mRet = DeviceIoControl(hDev, Ioctl, Data, Length, NULL, 0,
&BytesReturned, NULL) ?
MMSYSERR_NOERROR : sndTranslateStatus();
//
// Close our file and return
//
CloseHandle(hDev);
return mRet;
}
/****************************************************************************
* @doc INTERNAL
*
* @api DWORD | sndGetData | This function gets data from a given device
* specified by device id when read-only access is
* sufficient
*
* @parm UINT | DeviceType | The Device type
*
* @parm UINT | DeviceId | The device id
*
* @parm UINT | Length | Length of data to set
*
* @parm PBYTE | Data | Data to set
*
* @parm ULONG | Ioctl | The Ioctl to use
*
* @rdesc MM... return code.
***************************************************************************/
MMRESULT sndGetData(UINT DeviceType, UINT DeviceId, UINT Length, PBYTE Data,
ULONG Ioctl)
{
HANDLE hDev;
MMRESULT mRet;
DWORD BytesReturned;
//
// Open the wave output device
//
mRet = sndOpenDev(DeviceType, DeviceId, &hDev, GENERIC_READ);
if (mRet != MMSYSERR_NOERROR) {
return mRet;
}
//
// Set our data.
//
// Setting the overlapped parameter (last) to null means we
// wait until the operation completes.
//
mRet = DeviceIoControl(hDev, Ioctl, NULL, 0, (LPVOID)Data, Length,
&BytesReturned, NULL) ?
MMSYSERR_NOERROR : sndTranslateStatus();
//
// Close our file and return
//
CloseHandle(hDev);
return mRet;
}
/****************************************************************************
* @doc INTERNAL
*
* @api MMRESULT | sndGetHandleData | Get data from a device using its handle
*
* @parm PWAVEALLOC | pClient | Client handle.
*
* @parm DWORD | dwSize | Size of the data
*
* @parm PVOID | pData | Where to put the data.
*
* @parm ULONG | Function | The Ioctl to use
*
* @rdesc MMSYS... return value.
***************************************************************************/
MMRESULT sndGetHandleData(HANDLE hDev,
DWORD dwSize,
PVOID pData,
ULONG Ioctl,
HANDLE hEvent)
{
OVERLAPPED Overlap;
DWORD BytesReturned;
WinAssert(hDev != NULL);
memset(&Overlap, 0, sizeof(Overlap));
Overlap.hEvent = hEvent;
//
// Issue the IO control. We must wait with our own event because
// setting the overlapped object to null will complete if other
// IOs complete.
//
if (!DeviceIoControl(hDev,
Ioctl,
NULL,
0,
pData,
dwSize,
&BytesReturned,
&Overlap)) {
DWORD cbTransfer;
//
// Wait for completion if necessary
//
if (GetLastError() == ERROR_IO_PENDING) {
if (!GetOverlappedResult(hDev, &Overlap, &cbTransfer,
TRUE)) {
return sndTranslateStatus();
}
} else {
return sndTranslateStatus();
}
}
//
// We'd better peek aleratbly to flush out any IO
// completions so that things like RESET only
// return when all buffers have been completed
//
// This relies on the fact that SleepEx will return
// WAIT_IO_COMPLETION in preference to OK
//
while (SetEvent(hEvent) &&
WaitForSingleObjectEx(hEvent, 0, TRUE) == WAIT_IO_COMPLETION) {}
return MMSYSERR_NOERROR;
}
/****************************************************************************
* @doc INTERNAL
*
* @api MMRESULT | sndSetHandleData | Pass data to a device using its handle
*
* @parm PWAVEALLOC | pClient | Client handle.
*
* @parm DWORD | dwSize | Size of the data
*
* @parm PVOID | pData | Data to send.
*
* @parm ULONG | Function | The Ioctl to use
*
* @rdesc MMSYS... return value.
***************************************************************************/
MMRESULT sndSetHandleData(HANDLE hDev,
DWORD dwSize,
PVOID pData,
ULONG Ioctl,
HANDLE hEvent)
{
OVERLAPPED Overlap;
DWORD BytesReturned;
WinAssert(hDev != NULL);
memset((PVOID)&Overlap, 0, sizeof(Overlap));
Overlap.hEvent = hEvent;
//
// Issue the IO control. We must wait with our own event because
// setting the overlapped object to null will complete if other
// IOs complete.
//
if (!DeviceIoControl(hDev,
Ioctl,
pData,
dwSize,
NULL,
0,
&BytesReturned,
&Overlap)) {
DWORD cbTransfer;
//
// Wait for completion if necessary
//
if (GetLastError() == ERROR_IO_PENDING) {
if (!GetOverlappedResult(hDev, &Overlap, &cbTransfer,
TRUE)) {
return sndTranslateStatus();
}
} else {
return sndTranslateStatus();
}
}
//
// We'd better peek alertably to flush out any IO
// completions so that things like RESET only
// return when all buffers have been completed
//
// This relies on the fact that SleepEx will return
// WAIT_IO_COMPLETION in preference to OK
//
while (SleepEx(0, TRUE) == WAIT_IO_COMPLETION) {}
return MMSYSERR_NOERROR;
}
HKEY sndOpenParametersKey(VOID)
{
return DrvOpenRegKey(DriverName, TEXT(""));
}
/****************************************************************************
* @doc INTERNAL
*
* @api MMRESULT | sndFindDevices | Find all the devices using its handle
*
* @parm HKEY | Key| Handle to
*
* @parm PDEVICE_LIST * | DeviceList | The data about the devices found is
* returned here.
*
* @rdesc MMSYS... return value.
*
* @comm
* The registry structure is assumed to be (eg)
*
* HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\sndsys\Parameters..
*
* Parameters
* Device0 One key for each card found
* DriverParameters
* Interrupt REG_DWORD
* Port REG_DWORD
* DmaChannel REG_DWORD
* Devices (Volatile key)
* WSSWave0 REG_BINARY (device data)
* WSSMix1 REG_BINARY (device data)
* MixerSettings REG_BINARY
*
* The (device data) for each device contains :
*
* DWORD DeviceType
* BYTE DeviceInstanceData[]
*
* NOTE - DON'T use the 'standard' names (eg WaveOut0) for your device
* or mmdrv will try to grab those devices.
*
*
***************************************************************************/
MMRESULT sndFindDevices(VOID)
{
DWORD Index;
HKEY DriverKey;
WinAssert(DeviceList == NULL);
/*
** Open the driver's registry key
*/
DriverKey = sndOpenParametersKey();
if (DriverKey == NULL) {
return sndTranslateStatus();
}
for (Index = 0; ; Index++) {
BOOL Rc;
HKEY Subkey;
if (!sndEnumKey(DriverKey, Index, SOUND_DEVICES_SUBKEY, &Subkey)) {
sndFreeDeviceList();
RegCloseKey(DriverKey);
return sndTranslateStatus();
}
/*
** Null returned subkey means end of list
*/
if (Subkey == NULL) {
break;
}
/*
** Process all the data for this device :
*/
Rc = sndProcessDevices(Subkey, &DeviceList, Index);
RegCloseKey(Subkey);
if (!Rc) {
sndFreeDeviceList();
RegCloseKey(DriverKey);
return sndTranslateStatus();
}
}
RegCloseKey(DriverKey);
return MMSYSERR_NOERROR;
}
BOOL sndProcessDevices(HKEY DevicesKey, PDEVICE_LIST *DeviceList, DWORD CardIndex)
{
BOOL Rc;
DWORD Index;
DWORD Length;
DWORD Type;
DWORD Value;
Rc = TRUE;
for (Index = 0; ; Index++) {
WCHAR DeviceName[SOUND_MAX_DEVICE_NAME];
DWORD Error;
DWORD cbSize;
PBYTE Data;
Length = sizeof(DeviceName) / sizeof(DeviceName[0]);
cbSize = 0;
/*
** Can't get the value at the same time as the key name because
** bugs in RegEnumValue mean that if the value is too big you
** won't get any of it!
*/
Error = RegEnumValue(DevicesKey,
Index,
DeviceName,
&Length,
NULL,
NULL,
NULL,
&cbSize);
if (Error == ERROR_NO_MORE_ITEMS) {
break;
}
/*
** Check we got the data we wanted
*/
if (Error != ERROR_SUCCESS) {
Rc = FALSE;
break;
}
if (cbSize < sizeof(DWORD)) {
SetLastError(ERROR_INVALID_DATA);
Rc = FALSE;
break;
}
/*
** Get some space for the value
*/
Data = (PBYTE)HeapAlloc(hHeap, 0, cbSize);
if (Data == NULL) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
Rc = FALSE;
break;
}
/*
** Read the actual value
*/
Error = RegQueryValueEx(DevicesKey,
DeviceName,
NULL,
&Type,
Data,
&cbSize);
Value = *(LPDWORD)Data;
HeapFree(hHeap, 0, Data);
/*
** The kernel driver is supposed to put either a binary or
** DWORD into the registry
*/
if (Error != ERROR_SUCCESS) {
Rc = FALSE;
break;
}
if (Type != REG_DWORD && Type != REG_BINARY ||
cbSize < sizeof(DWORD)) {
dprintf(("Invalid device type!"));
WinAssert(FALSE);
SetLastError(ERROR_INVALID_DATA);
Rc = FALSE;
break;
}
/*
** Store the device data in the table
**
** When we actually open it we're going to need to append
** \\.\ .. to it
*/
if (!sndAppendToDeviceList(DeviceList, Value, CardIndex, DeviceName)) {
Rc = FALSE;
break;
}
}
return Rc;
}
BOOL sndEnumKey(HKEY Key, DWORD Index, LPTSTR OurKey, HKEY *pSubkey)
{
DWORD cchName;
WCHAR SubKeyName[50];
HKEY Subkey;
UINT OurKeyLen;
OurKeyLen = lstrlen(OurKey);
cchName = sizeof(SubKeyName) / sizeof(SubKeyName[0]) - OurKeyLen;
switch (RegEnumKeyEx(Key,
Index,
SubKeyName,
&cchName,
NULL,
NULL,
NULL,
NULL)) {
case ERROR_SUCCESS:
if (OurKeyLen != 0) {
wcscat(SubKeyName, TEXT("\\"));
}
wcscat(SubKeyName, OurKey);
if (ERROR_SUCCESS != RegOpenKeyEx(Key,
SubKeyName,
0,
KEY_READ,
&Subkey)) {
return FALSE;
} else {
*pSubkey = Subkey;
return TRUE;
}
case ERROR_NO_MORE_ITEMS:
*pSubkey = NULL;
return TRUE;
default:
return FALSE;
}
}
BOOL sndAppendToDeviceList(
PDEVICE_LIST *pList,
DWORD DeviceType,
DWORD CardIndex,
LPWSTR Name)
{
PDEVICE_LIST pNew;
#if DBG
PDEVICE_LIST List;
for (List = *pList; List != NULL; List = List->Next) {
if (List->DeviceType == DeviceType &&
lstrcmpi(List->Name, Name) == 0) {
dprintf(("Duplicate device name found - %ls!!", Name));
DebugBreak();
}
}
#endif // DBG
/*
** Allocate space for the new entry.
**
** Note that the space for the trailing null in the name is accounted
** for by the length of 1 given to the Name structure entry.
*/
pNew = (PDEVICE_LIST)HeapAlloc(
hHeap,
0,
sizeof(DEVICE_LIST) + lstrlen(Name) * sizeof(WCHAR));
if (pNew == NULL) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
/*
** Fill in the new entry
*/
pNew->DeviceType = DeviceType;
pNew->CardIndex = CardIndex;
lstrcpy(pNew->Name, Name);
pNew->DeviceInstanceData = NULL;
pNew->Next = *pList;
*pList = pNew;
return TRUE;
}
VOID sndFreeDeviceList(VOID)
{
PDEVICE_LIST pFree;
for (; DeviceList != NULL; ) {
pFree = DeviceList;
DeviceList = pFree->Next;
if (pFree->DeviceInstanceData != NULL) {
HeapFree(hHeap, 0, (LPVOID)pFree->DeviceInstanceData);
}
HeapFree(hHeap, 0, (LPSTR)pFree);
}
}
/*
** Load the device data into the device list
*/
PVOID sndLoadDeviceData(DWORD DeviceType, DWORD dwId, LPDWORD pSize)
{
PDEVICE_LIST List;
HKEY DriverKey;
DWORD Index;
BOOL Rc;
PVOID DeviceData;
DeviceData = NULL;
List = sndFindDevice(DeviceType, dwId);
WinAssert(List != NULL);
/*
** Search through all the entries trying to open the key with the
** device name. Note that it's possible for the registry to have
** totally changed so we can't assume all this is going to work
** or give the correct result. We can check the device type of
** the entry we open and of course the name must be correct.
*/
DriverKey = sndOpenParametersKey();
for (Index = 0, Rc = FALSE; ; Index++) {
HKEY Subkey;
DWORD Type;
DWORD Size;
if (!sndEnumKey(DriverKey, Index, SOUND_DEVICES_SUBKEY, &Subkey)) {
RegCloseKey(DriverKey);
return FALSE;
}
/*
** Null returned subkey means end of list
*/
if (Subkey == NULL) {
break;
}
if (Rc) {
/*
** Try to get the data for this device
*/
Size = 0;
Rc = (ERROR_SUCCESS == RegQueryValueEx(Subkey,
List->Name,
NULL,
&Type,
NULL,
&Size));
}
if (Rc) {
/*
** Allocate some space and try to load it
*/
DeviceData = (PVOID)HeapAlloc(hHeap, 0, Size);
if (DeviceData != NULL) {
Rc = (ERROR_SUCCESS == RegQueryValueEx(Subkey,
List->Name,
NULL,
&Type,
DeviceData,
&Size));
RegCloseKey(Subkey);
if (!Rc ||
*(LPDWORD)DeviceData != DeviceType) {
LocalFree((HLOCAL)DeviceData);
DeviceData = NULL;
} else {
*pSize = Size;
}
}
/*
** This was the one we wanted
*/
break;
} else {
RegCloseKey(Subkey);
}
}
RegCloseKey(DriverKey);
return DeviceData;
}
PDEVICE_LIST sndFindDevice(DWORD DeviceType, DWORD dwId)
{
PDEVICE_LIST List;
DWORD CurrentId;
for (List = DeviceList, CurrentId = 0; List != NULL; List = List->Next) {
if (List->DeviceType == DeviceType) {
if (CurrentId == dwId) {
break;
} else {
CurrentId++;
}
}
}
return List;
}
BOOL sndFindDeviceInstanceData(DWORD DeviceType, DWORD dwId,
PVOID *InstanceData)
{
PDEVICE_LIST List;
List = sndFindDevice(DeviceType, dwId);
if (List == NULL) {
return FALSE;
}
*InstanceData = List->DeviceInstanceData;
return TRUE;
}
BOOL sndSetDeviceInstanceData(DWORD DeviceType, DWORD dwId,
PVOID InstanceData)
{
PDEVICE_LIST List;
List = sndFindDevice(DeviceType, dwId);
WinAssert(List != NULL);
WinAssert(List->DeviceInstanceData == NULL);
List->DeviceInstanceData = InstanceData;
return TRUE;
}
/*
** Package up LoadString a bit
*/
void InternalLoadString(UINT StringId, LPTSTR pszString, UINT Length)
{
ZeroMemory(pszString, Length * sizeof(pszString[0]));
if (StringId != 0) {
LoadString(hInstance, StringId, pszString, Length);
}
}