Skip to main content

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.

Comments

Anonymous 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
Unknown 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?

Popular posts from this blog

History of HPSDR Mercury and Quick Silver

History of HPSDR Mercury and Quick Silver Philip Covington, N8VB Early HPSDR and XYLO In 2005 I started a High Performance SDR (HPSDR) project which was to consist of a motherboard carrying a FPGA/USB 2.0 interface and power supply with the provision for plug in modules through 40 pin headers. I had planned a narrow band high dynamic range module based on a QSD/DDS/PCM4202 audio ADC and a wide bandwidth module based on a high speed 16 bit ADC: http://www.philcovington.com/SDR/PICS/HPSDR_FPGA_USB_Board_top1_800600.jpg http://www.philcovington.com/SDR/PICS/HPSDR_FPGA_USB_Board_top4.jpg I soon selected the LTC2208 ADC from Linear Technology. A representative from Linear Technology came across my blog ( http://pcovington.blogspot.com/ ) and offered evaluation boards and samples to support the project. At about the same time my HPSDR project came about, Phil Harman, VK6APH and Bill Tracey, KD5TFD were interested developing a sound card replacement to be used with the SD...

2323 Wilt

RFFE1 Why you may (or may not) need it

RFFE stands for "Radio Frequency Front End" and the "1" stands for the first version (0-62.5 MHz coverage). When I was designing the QS1R board, I had to decide whether to include bandpass filtering and RF amplification on the board. In fact the initial prototypes "RevA" included RF amplification on the QS1R board. Unlike another DDC based direct sampling receiver "Perseus", QS1R was designed to be more than a SW receiver. In addition to a SW receiver, QS1R was meant to facilitate experimentation in the RF spectrum up to at least 300 MHz. I finally settled on a 55 MHz low pass filter (which can be bypassed) and no active components in front of the ADC on QS1R. Any active devices, bandpass filters, or attenuation would be added by a separate board such as the RFFE1. The antenna that I use for my QS1R is a center fed, non-resonant dipole at 50 feet. The total wire length is about 240 feet with only about 100 feet of that running horizontally...