Thursday, July 07, 2005

vSOUND progress - vCOM info

I overcame a major hurdle today in the development of the vSOUND virtual sound card driver. If you are familiar with WDM Audio drivers you know that the portcls.sys driver exposes functions for creating, registering, and managing WDM Audio drivers. Unfortunately for our application the PcXXX functions hide much of the internal workings of the audio driver. For our vSOUND virtual sound card driver we need to be able to send customs IOCTLs to read and write data to the driver's buffers that represent the virtual A/D input and D/A output of the sound card.

In the typical sound driver you will see something like this:

extern "C" NTSTATUS
DriverEntry
(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPathName
)
{
NTSTATUS ntStatus;

ntStatus =
PcInitializeAdapterDriver
(
DriverObject,
RegistryPathName,
AddDevice
);

return ntStatus;

}

The call to PcInitializeAdapterDriver() causes PortCls.sys to load pointers
to handlers for the certain IRPs like IRP_MJ_CREATE, IRP_MJ_DEVICE_CONTROL, etc… into the driver object. Without access to these handlers we cannot get at the buffers we need for our virtual driver. The question is - how do we intercept these handlers?

It turns out that PortCls provides a function call PcDispatchIrp() that we can use if we can get access to the handlers we need. To get access to the handlers we can override the pointers to the handlers that the call to PcInitializeAdapterDriver() installed:

extern "C" NTSTATUS
DriverEntry
(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPathName
)
{
NTSTATUS ntStatus;

ntStatus =
PcInitializeAdapterDriver
(
DriverObject,
RegistryPathName,
AddDevice
);

if (NT_SUCCESS(ntStatus))
{
DriverObject->
MajorFunction[IRP_MJ_DEVICE_CONTROL] = MSVASDeviceControl;
DriverObject->
MajorFunction[IRP_MJ_CREATE] = MSVADCreateDevice;
DriverObject->
MajorFunction[IRP_MJ_CLOSE] = MSVADCloseDevice;
}

return ntStatus;

}

In the code above we have installed our own handlers for the IOCTLs we are interested in. When our handler is called we can examine the IRP to determine whether the IRP is something we want to handle or pass along to the PortCls driver. We can do this by calling PcDispatchIrp() if we are not interested in the particular IRP.

That is all fine and dandy until you realize that you need to somehow call CreateFile() and DeviceIOControl() on the driver. We need a name that we can call in the CreateFile() function.

The MSVAD's AddDevice() looked like this:
extern "C" NTSTATUS
AddDevice
(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
{
NTSTATUS ntStatus;

ntStatus =
PcAddAdapterDevice
(
DriverObject,
PhysicalDeviceObject,
PCPFNSTARTDEVICE(StartDevice),
MAX_MINIPORTS,
0
);

return ntStatus;
}
The call to PcAddAdapterDevice() creates the device object, initializes the device context, and attaches the device object to the device stack. What we need to do is to create an additional FDO (Functional Device Object). If we create our own FDO we can create a symbolic link to it so Win32 user mode applications can open it and send IOCTLs to it.

Here is the solution:

extern "C" NTSTATUS
AddDevice
(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
{
NTSTATUS ntStatus;
PDEVICE_OBJECT deviceObject;
WCHAR NameBuffer[] = L
"\\Device\\vSOUND";
WCHAR DOSNameBuffer[] = L
"\\DosDevices\\vSOUND";
UNICODE_STRING uniNameString, uniDOSString;
PVSOUND_DEVICE_EXTENSION pDeviceExtension;


ntStatus =
PcAddAdapterDevice
(
DriverObject,
PhysicalDeviceObject,
PCPFNSTARTDEVICE(StartDevice),
MAX_MINIPORTS,
0
);

if (!NT_SUCCESS(ntStatus))
return ntStatus;

KdPrint((
"Success from PcAddAdapterDevice"));

RtlInitUnicodeString(&uniNameString, NameBuffer);
RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);

ntStatus = IoCreateDevice(DriverObject,
sizeof(VSOUND_DEVICE_EXTENSION),
&uniNameString,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&deviceObject);

if(!NT_SUCCESS(ntStatus))
return ntStatus;

KdPrint((
"Success from IoCreateDevice"));

pDeviceExtension =
PVSOUND_DEVICE_EXTENSION)deviceObject->DeviceExtension;
pDeviceExtension-&id = 0x5000;

ntStatus = IoCreateSymbolicLink (&uniDOSString, &uniNameString);

if (!NT_SUCCESS(ntStatus)) //try to delete the symbolic link first
{
KdPrint((
"Failed IoCreateSymbolicLink... trying to delete it..."));
IoDeleteSymbolicLink(&uniDOSString);
ntStatus = IoCreateSymbolicLink(&uniDOSString, &uniNameString);
}

if (!NT_SUCCESS(ntStatus))
{
KdPrint((
"Failed IoCreateSymbolicLink"));
return ntStatus;
}

KdPrint((
"Success from IoCreateSymbolicLink"));

deviceObject-&Flags &= ~DO_DEVICE_INITIALIZING;

return ntStatus;

}
Like in a normal device driver, we create an additional functional device object using IoCreateDevice(). We then create a symbolic link to this FDO using IoCreateSymbolicLink(). If all goes well we can open a handle to the vSOUND driver by calling CreateFile("\\\\.\\vSOUND", ...). And this works... We can now intercept the IOCTLs being sent to the vSOUND driver which means we can define our own custom IOCTL codes to access the buffers we need to get the sound data in and out of the driver!

Unfortunately, because we are trying to do something very out of the ordinary none of this is documented in the DDK so it took a while to figure it out. But it is a major problem that we have overcome on the way to a workable virtual sound card driver for use with PowerSDR. Now someone who has experience in writing device drivers might wonder why I didn't use the WDM prefered method of registering a Device Interface instead of creating a new FDO with a NT sytle name - That is the approach I took initially and it did not work. Upon searching the web and newsgroups it seems that others have tried this approach and were not successful. The answer to why probably lies somewhere in ks.sys or PortCls.sys that we do not have the source code to.

vCOM:

There will be a new build of vCOM available soon. Two users have reported that they have gotten total system freezes while using the driver. They did not get BSODs - just a total system hang up. According to Windows Internals, Forth Edition by Mark E. Russinovich and David A. Solomon there are three things that can cause the system to hang or become unresponsive:

1. A device driver does not return from an ISR.

Since we are not servicing ISRs in the vCOM driver this is not the problem.

2. A high priority realtime thread prempts the windowing system driver's input threads.

3. A deadlock occurs in kernel mode.

I think that #2 is the culprit. In trying to squeeze maximum performance out of the driver I set the thread priorities to the real-time range in the vCOM driver. Obviously this is not a good idea. I will adjust the thread priorities to normal and release a new build. I am 95% sure this is the problem causing the hang on some systems. The threads only rarely run since I implemented the Fast read and Fast write functions in the vCOM driver for build 222. Occasionally these threads will run when data is not immediately available. This would explain the intermittency of the problem.

3 comments:

simhi said...

Hi,
I have tried to use the technique of symbolic link but whenever my user application is calling "CreateFile" My XP machine blinks and then crash.

Any ideas what I am doing wrong ?

Please note that I compile my user application using bcc32. Maybe this is causing the problem ?


The code of AddDevice function is:
{
PAGED_CODE();

NTSTATUS ntStatus;

PDEVICE_OBJECT deviceObject;
WCHAR DOSNameBuffer[] = L"\\DosDevices\\vSOUND";
UNICODE_STRING uniNameString, uniDOSString;

ntStatus =
PcAddAdapterDevice
(
DriverObject,
PhysicalDeviceObject,
PCPFNSTARTDEVICE(StartDevice),
MAX_MINIPORTS,
0
);

if (!NT_SUCCESS(ntStatus))
return ntStatus;

RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
ntStatus = IoCreateDevice(DriverObject,0,
&uniNameString, FILE_DEVICE_UNKNOWN, 0, FALSE,
&deviceObject);

if(!NT_SUCCESS(ntStatus))
return ntStatus;

ntStatus = IoCreateSymbolicLink (&uniDOSString, &uniNameString);
if (!NT_SUCCESS(ntStatus)) //try to delete the symbolic link first
{ KdPrint(("Failed IoCreateSymbolicLink... trying to delete it..."));
IoDeleteSymbolicLink(&uniDOSString);
ntStatus = IoCreateSymbolicLink(&uniDOSString, &uniNameString);
}
if (!NT_SUCCESS(ntStatus))
{ KdPrint(("Failed IoCreateSymbolicLink"));
return ntStatus;
}
deviceObject->Flags = ~DO_DEVICE_INITIALIZING;
return ntStatus;
} // AddDevice

xin said...

Hi simhi,

you need to add dispatch functions for create, close and cleanup. when createfile create the device, they will be called. otherwise, there is no dispatch fuctions to support createfile in portcls.

good luck.

xin

ashu said...

Hi

1.
How do you define the new IOCTLS, using CTL_CODE? If so then what is the "type"(2nd parameter of CTL_CODE)?

2.
Is it possible to keep the IRP_MJ_CREATE, IRP_MJ_CLOSE as it is in MSVAD and change only MJ_DEVICE_IO_CONTROL? so in that case, i will call createFile(\\.\VSOUND) and use DeviceIoControl() to send my IOCTLS?