WinUSB communication

Hello,

I?m currently writing an Umdf USB driver. The driver forwards requests to WinUSB and gets data back from the device. When I try to communicate via ReadFile / WriteFile with my UMDF driver from the application, it fails. The communication with DeviceIoControl works. The queue I?m using is WdfIoQueueDispatchParallel (I?m not using a secondary queue for DeviceIoControl). I guess it has something to do with the WinUSB interface. Thanks for any help in advance.

Juergen

Which reads/writes fail? The ones your driver sends via the usb io targets? Or the ones sent by the app to the driver? If the latter, did you implement a default io handler or read and write queue callbacks?

d

Sent from my phone with no t9, all spilling mistakes are not intentional.

-----Original Message-----
From: xxxxx@sybera.de
Sent: Thursday, May 14, 2009 6:51 AM
To: Windows System Software Devs Interest List
Subject: [ntdev] WinUSB communication

Hello,

I?m currently writing an Umdf USB driver. The driver forwards requests to WinUSB and gets data back from the device. When I try to communicate via ReadFile / WriteFile with my UMDF driver from the application, it fails. The communication with DeviceIoControl works. The queue I?m using is WdfIoQueueDispatchParallel (I?m not using a secondary queue for DeviceIoControl). I guess it has something to do with the WinUSB interface. Thanks for any help in advance.

Juergen


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

Thanks for the quick reply,

the problem appears when app sends read to the driver. I implemented the
OnDefaultIoHandler, but its still the same behaviour.

void STDMETHODCALLTYPE CMyQueue::OnDefaultIoHandler(
IWDFIoQueue* pWdfQueue,
IWDFIoRequest* pWdfRequest)
{
OutputDebugString(L"CMyQueue->OnDefaultIoHandler\n");

//Set the completion callback
IRequestCallbackRequestCompletion* pCallbackRequestCompletion = NULL;
HRESULT hr =
this->QueryInterface(IID_PPV_ARGS(&pCallbackRequestCompletion));
pWdfRequest->SetCompletionCallback(pCallbackRequestCompletion, NULL);
pCallbackRequestCompletion->Release();

//Copy current i/o stack locations parameters to the next stack location
pWdfRequest->FormatUsingCurrentType();

//Send down the request with no timeout
hr = pWdfRequest->Send(m_pMyDevice->m_pWdfUsbTargetDevice, 0, 0);
if (FAILED(hr))
{
//If send failed we need to complete the request with failure
pWdfRequest->CompleteWithInformation(hr, 0);
}
}

----- Original Message -----
From: “Doron Holan”
To: “Windows System Software Devs Interest List”
Sent: Thursday, May 14, 2009 4:30 PM
Subject: RE: [ntdev] WinUSB communication

Which reads/writes fail? The ones your driver sends via the usb io targets?
Or the ones sent by the app to the driver? If the latter, did you implement
a default io handler or read and write queue callbacks?

d

Sent from my phone with no t9, all spilling mistakes are not intentional.

-----Original Message-----
From: xxxxx@sybera.de
Sent: Thursday, May 14, 2009 6:51 AM
To: Windows System Software Devs Interest List
Subject: [ntdev] WinUSB communication

Hello,

I?m currently writing an Umdf USB driver. The driver forwards requests to
WinUSB and gets data back from the device. When I try to communicate via
ReadFile / WriteFile with my UMDF driver from the application, it fails. The
communication with DeviceIoControl works. The queue I?m using is
WdfIoQueueDispatchParallel (I?m not using a secondary queue for
DeviceIoControl). I guess it has something to do with the WinUSB interface.
Thanks for any help in advance.

Juergen


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

The OnRead / OnWrite Callbacks are implemented, but will only be called, when the application communicates after system startup. When the driver is stoped/started, the callbacks are never called again.

VOID STDMETHODCALLTYPE CMyQueue::OnRead(
IWDFIoQueue* pWdfQueue,
IWDFIoRequest* pWdfRequest,
SIZE_T BytesToRead)
{
IWDFMemory* pWdfMemory;
HRESULT hr;

OutputDebugString(L"CMyQueue->OnRead\n");

//Get memory object
pWdfRequest->GetOutputMemory(&pWdfMemory);

//The FormatRequestForRead method formats an
//I/O request object for a read operation
hr = m_pMyDevice->m_pWdfUsbReadPipe->FormatRequestForRead(
pWdfRequest,
NULL, //pFile
pWdfMemory,
NULL, //Memory offset
NULL); //DeviceOffset

if (FAILED(hr)) { pWdfRequest->Complete(hr); }
else { ForwardRequest(pWdfRequest, m_pMyDevice->m_pWdfUsbReadPipe); }

//Release Interface
pWdfMemory->Release();
}

Rall wrote:

the problem appears when app sends read to the driver. I implemented
the OnDefaultIoHandler, but its still the same behaviour.

void STDMETHODCALLTYPE CMyQueue::OnDefaultIoHandler(
IWDFIoQueue* pWdfQueue,
IWDFIoRequest* pWdfRequest)
{
OutputDebugString(L"CMyQueue->OnDefaultIoHandler\n");

//Set the completion callback
IRequestCallbackRequestCompletion* pCallbackRequestCompletion = NULL;
HRESULT hr =
this->QueryInterface(IID_PPV_ARGS(&pCallbackRequestCompletion));
pWdfRequest->SetCompletionCallback(pCallbackRequestCompletion, NULL);
pCallbackRequestCompletion->Release();

//Copy current i/o stack locations parameters to the next stack location
pWdfRequest->FormatUsingCurrentType();

//Send down the request with no timeout
hr = pWdfRequest->Send(m_pMyDevice->m_pWdfUsbTargetDevice, 0, 0);
if (FAILED(hr))
{
//If send failed we need to complete the request with failure
pWdfRequest->CompleteWithInformation(hr, 0);
}
}

You are passing these requests through unchanged. That means you are
ASSUMING that winusb.sys knows how to respond to IRP_MJ_READ and
IRP_MJ_WRITE requests, issued from ReadFile and WriteFile calls. To the
best of my knowledge, no one ever promised that it would do so. In
fact, as far as I know, the interface to winusb.sys is not documented at
all. The documented interface is to go through the WinUSB APIs
implemented in winusb.dll. I would guess that those APIs actually
send a series of ioctls to winusb.sys. The trouble with ReadFile and
WriteFile is that there is no way to pass along an endpoint number.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.

In the UMDF case, the UMDF USB IO targets (device and pipe) replace the winusb APIs, but the general gist is correct. ReadFile/WriteFile/read requests/write requests are not the right way to talk to the usb device, the usb io targets are (using their formatting APIs)

d

-----Original Message-----
From: xxxxx@lists.osr.com [mailto:xxxxx@lists.osr.com] On Behalf Of Tim Roberts
Sent: Thursday, May 14, 2009 9:54 AM
To: Windows System Software Devs Interest List
Subject: Re: [ntdev] WinUSB communication

Rall wrote:

the problem appears when app sends read to the driver. I implemented
the OnDefaultIoHandler, but its still the same behaviour.

void STDMETHODCALLTYPE CMyQueue::OnDefaultIoHandler(
IWDFIoQueue* pWdfQueue,
IWDFIoRequest* pWdfRequest)
{
OutputDebugString(L"CMyQueue->OnDefaultIoHandler\n");

//Set the completion callback
IRequestCallbackRequestCompletion* pCallbackRequestCompletion = NULL;
HRESULT hr =
this->QueryInterface(IID_PPV_ARGS(&pCallbackRequestCompletion));
pWdfRequest->SetCompletionCallback(pCallbackRequestCompletion, NULL);
pCallbackRequestCompletion->Release();

//Copy current i/o stack locations parameters to the next stack location
pWdfRequest->FormatUsingCurrentType();

//Send down the request with no timeout
hr = pWdfRequest->Send(m_pMyDevice->m_pWdfUsbTargetDevice, 0, 0);
if (FAILED(hr))
{
//If send failed we need to complete the request with failure
pWdfRequest->CompleteWithInformation(hr, 0);
}
}

You are passing these requests through unchanged. That means you are
ASSUMING that winusb.sys knows how to respond to IRP_MJ_READ and
IRP_MJ_WRITE requests, issued from ReadFile and WriteFile calls. To the
best of my knowledge, no one ever promised that it would do so. In
fact, as far as I know, the interface to winusb.sys is not documented at
all. The documented interface is to go through the WinUSB APIs
implemented in winusb.dll. I would guess that those APIs actually
send a series of ioctls to winusb.sys. The trouble with ReadFile and
WriteFile is that there is no way to pass along an endpoint number.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

Well, I think this communication should just be a relation between the
application and my UMDF driver. So when I create a communication queue, then
Read/Write request should come through the same way as DeviceIoControl
(which works). Where is the obstacle that prevents ReadFile/WriteFile
request coming through (the corresponding callbacks are implemented but not
called).

----- Original Message -----
From: “Tim Roberts”
To: “Windows System Software Devs Interest List”
Sent: Thursday, May 14, 2009 6:54 PM
Subject: Re: [ntdev] WinUSB communication

> Rall wrote:
>>
>> the problem appears when app sends read to the driver. I implemented
>> the OnDefaultIoHandler, but its still the same behaviour.
>>
>> void STDMETHODCALLTYPE CMyQueue::OnDefaultIoHandler(
>> IWDFIoQueue* pWdfQueue,
>> IWDFIoRequest* pWdfRequest)
>> {
>> OutputDebugString(L"CMyQueue->OnDefaultIoHandler\n");
>>
>> //Set the completion callback
>> IRequestCallbackRequestCompletion* pCallbackRequestCompletion = NULL;
>> HRESULT hr =
>> this->QueryInterface(IID_PPV_ARGS(&pCallbackRequestCompletion));
>> pWdfRequest->SetCompletionCallback(pCallbackRequestCompletion, NULL);
>> pCallbackRequestCompletion->Release();
>>
>> //Copy current i/o stack locations parameters to the next stack location
>> pWdfRequest->FormatUsingCurrentType();
>>
>> //Send down the request with no timeout
>> hr = pWdfRequest->Send(m_pMyDevice->m_pWdfUsbTargetDevice, 0, 0);
>> if (FAILED(hr))
>> {
>> //If send failed we need to complete the request with failure
>> pWdfRequest->CompleteWithInformation(hr, 0);
>> }
>> }
>
> You are passing these requests through unchanged. That means you are
> ASSUMING that winusb.sys knows how to respond to IRP_MJ_READ and
> IRP_MJ_WRITE requests, issued from ReadFile and WriteFile calls. To the
> best of my knowledge, no one ever promised that it would do so. In
> fact, as far as I know, the interface to winusb.sys is not documented at
> all. The documented interface is to go through the WinUSB APIs
> implemented in winusb.dll. I would guess that those APIs actually
> send a series of ioctls to winusb.sys. The trouble with ReadFile and
> WriteFile is that there is no way to pass along an endpoint number.
>
> –
> Tim Roberts, xxxxx@probo.com
> Providenza & Boekelheide, Inc.
>
>
> —
> NTDEV is sponsored by OSR
>
> For our schedule of WDF, WDM, debugging and other seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at
> http://www.osronline.com/page.cfm?name=ListServer
>

Post you adddevice code which creates the queue(s) and sets up the dispatching

d

Sent from my phone with no t9, all spilling mistakes are not intentional.

-----Original Message-----
From: Rall
Sent: Thursday, May 14, 2009 10:32 AM
To: Windows System Software Devs Interest List
Subject: Re: [ntdev] WinUSB communication

Well, I think this communication should just be a relation between the
application and my UMDF driver. So when I create a communication queue, then
Read/Write request should come through the same way as DeviceIoControl
(which works). Where is the obstacle that prevents ReadFile/WriteFile
request coming through (the corresponding callbacks are implemented but not
called).

----- Original Message -----
From: “Tim Roberts”
To: “Windows System Software Devs Interest List”
Sent: Thursday, May 14, 2009 6:54 PM
Subject: Re: [ntdev] WinUSB communication

> Rall wrote:
>>
>> the problem appears when app sends read to the driver. I implemented
>> the OnDefaultIoHandler, but its still the same behaviour.
>>
>> void STDMETHODCALLTYPE CMyQueue::OnDefaultIoHandler(
>> IWDFIoQueue* pWdfQueue,
>> IWDFIoRequest* pWdfRequest)
>> {
>> OutputDebugString(L"CMyQueue->OnDefaultIoHandler\n");
>>
>> //Set the completion callback
>> IRequestCallbackRequestCompletion* pCallbackRequestCompletion = NULL;
>> HRESULT hr =
>> this->QueryInterface(IID_PPV_ARGS(&pCallbackRequestCompletion));
>> pWdfRequest->SetCompletionCallback(pCallbackRequestCompletion, NULL);
>> pCallbackRequestCompletion->Release();
>>
>> //Copy current i/o stack locations parameters to the next stack location
>> pWdfRequest->FormatUsingCurrentType();
>>
>> //Send down the request with no timeout
>> hr = pWdfRequest->Send(m_pMyDevice->m_pWdfUsbTargetDevice, 0, 0);
>> if (FAILED(hr))
>> {
>> //If send failed we need to complete the request with failure
>> pWdfRequest->CompleteWithInformation(hr, 0);
>> }
>> }
>
> You are passing these requests through unchanged. That means you are
> ASSUMING that winusb.sys knows how to respond to IRP_MJ_READ and
> IRP_MJ_WRITE requests, issued from ReadFile and WriteFile calls. To the
> best of my knowledge, no one ever promised that it would do so. In
> fact, as far as I know, the interface to winusb.sys is not documented at
> all. The documented interface is to go through the WinUSB APIs
> implemented in winusb.dll. I would guess that those APIs actually
> send a series of ioctls to winusb.sys. The trouble with ReadFile and
> WriteFile is that there is no way to pass along an endpoint number.
>
> –
> Tim Roberts, xxxxx@probo.com
> Providenza & Boekelheide, Inc.
>
>
> —
> NTDEV is sponsored by OSR
>
> For our schedule of WDF, WDM, debugging and other seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at
> http://www.osronline.com/page.cfm?name=ListServer
>


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

Do you send device I/O controls to WinUsb or do you complete them in your driver?

If you send ioctls to WinUsb, are you sure they are working? WinUsb wouldn’t understand any controls your app sends. It only understands controls sent by WinUsb or UMDF USB I/O target (which are not documented).

And when you describe working/not-working:

  1. Is it that your UMDF callbacks are not invoked?
  2. Or that request sent to WinUsb fails?

If it is #1 please make sure that you implemented the right interface on the callback class and added it to the QI. Dump queue in the debugger using !wudfqueue and make sure that your callback is listed there.

If it is #2, as explained in this thread, you need to format I/O using UMDF USB I/O targets (or, a less preferred way is to use WinUSB APIs directly). Without these steps WinUsb won’t understand your I/O and fail it.

Please look at the UMDF USB FX2 sample in WDK. It handles all of read/write/ioctls.

Praveen

The strange thing is, that my DeviceIoControl request from the application to the driver allways works (the callback will always called). Not so the ReadFile and WriteFile requests. Maybe I was mistaken that its not WinUSB what I call - but anyway, when using DeviceIoControl from my application, OnDeviceIoControl is called, the request will be forwarded and OnCompletion appears.
The difference between the UMDF USB FX2 sample is, that I?m using just one queue in contrary to using a second queue for DeviceIoControl.

HRESULT CMyDriver::OnDeviceAdd(
IWDFDriver* pWdfDriver,
IWDFDeviceInitialize* pWdfDeviceInit)
{
HRESULT hr;
PCMyDevice pMyDevice;

// Allocate a new instance of the device class.
pMyDevice = new CMyDevice();
if (NULL == pMyDevice)
return E_OUTOFMEMORY;

// Initialize the instance.
hr = pMyDevice->Initialize(pWdfDriver, pWdfDeviceInit);
if (SUCCEEDED(hr))
{
//Configure the device and release the reference
hr = pMyDevice->Configure();
pMyDevice->Release();
}
return hr;
}

HRESULT CMyDevice::Configure(VOID)
{
PCMyQueue pMyQueue;
HRESULT hr;

//Create new queue class
pMyQueue = new CMyQueue(this);
if (pMyQueue == NULL)
return E_OUTOFMEMORY;

//Initialize queue
hr = pMyQueue->Initialize(m_pWdfDevice);
if (SUCCEEDED(hr))
{
hr = pMyQueue->Configure();
pMyQueue->Release();
}

//Create device interface
if (SUCCEEDED(hr))
hr = m_pWdfDevice->CreateDeviceInterface(&GUID_DEVINTERFACE, NULL);

//Activate device interface
if (SUCCEEDED(hr))
hr = m_pWdfDevice->AssignDeviceInterfaceState(&GUID_DEVINTERFACE, NULL, TRUE);

return hr;
}

HRESULT CMyQueue::Initialize(IWDFDevice* pWdfDevice)
{
IUnknown* pUnknown;
IWDFIoQueue* pWdfQueue;
HRESULT hr;

//First initialize the base class. This will create the IoQueue
//object and setup automatic forwarding of I/O controls.
AddRef();
pUnknown = static_cast((CUnknown*)this);
hr = pWdfDevice->CreateIoQueue(
pUnknown,
TRUE,
WdfIoQueueDispatchParallel,
TRUE,
FALSE,
&pWdfQueue);
pUnknown->Release();

// If that succeeded then set our member variable.
if (SUCCEEDED(hr))
{
//Save queue element
m_pWdfQueue = pWdfQueue;

//Release interface
pWdfQueue->Release();
}

return hr;
}

VOID STDMETHODCALLTYPE CMyQueue::OnDeviceIoControl(
IWDFIoQueue* pWdfQueue,
IWDFIoRequest* pWdfRequest,
ULONG ControlCode,
SIZE_T InputBufferSize,
SIZE_T OutputBufferSize)
{
//Check the control code
switch (ControlCode)
{
case IOCTL_READ_PIPE :
//Read the pipe
OnRead(pWdfQueue, pWdfRequest, 8);
return;

case IOCTL_WRITE_PIPE :
//Write the pipe
OnWrite(pWdfQueue, pWdfRequest, 8);
return;
}

//Complete request
pWdfRequest->CompleteWithInformation(S_OK, 0);
return;
}

VOID STDMETHODCALLTYPE CMyQueue::OnRead(
IWDFIoQueue* pWdfQueue,
IWDFIoRequest* pWdfRequest,
SIZE_T BytesToRead)
{
IWDFMemory* pWdfMemory;
HRESULT hr;

//Get memory object
pWdfRequest->GetOutputMemory(&pWdfMemory);

//The FormatRequestForRead method formats an
//I/O request object for a read operation
hr = m_pMyDevice->m_pWdfUsbReadPipe->FormatRequestForRead(
pWdfRequest,
NULL, //pFile
pWdfMemory,
NULL, //Memory offset
NULL); //DeviceOffset

if (FAILED(hr)) { pWdfRequest->Complete(hr); }
else { ForwardRequest(pWdfRequest, m_pMyDevice->m_pWdfUsbReadPipe); }

//Release Interface
pWdfMemory->Release();
}

VOID STDMETHODCALLTYPE CMyQueue::OnWrite(
IWDFIoQueue* pWdfQueue,
IWDFIoRequest* pWdfRequest,
SIZE_T BytesToWrite)
{
IWDFMemory* pWdfMemory;
HRESULT hr;

//Get memory objects
pWdfRequest->GetInputMemory(&pWdfMemory);

//The FormatRequestForRead method formats an
//I/O request object for a read operation
hr = m_pMyDevice->m_pWdfUsbWritePipe->FormatRequestForWrite(
pWdfRequest,
NULL, //pFile
pWdfMemory,
NULL, //Memory offset
NULL); //DeviceOffset

if (FAILED(hr)) { pWdfRequest->Complete(hr); }
else { ForwardRequest(pWdfRequest, m_pMyDevice->m_pWdfUsbWritePipe); }

//Release Interface
pWdfMemory->Release();
}

void CMyQueue::ForwardRequest(
IWDFIoRequest* pWdfRequest,
IWDFIoTarget* pWdfTarget)
{
//Set the completion callback
IRequestCallbackRequestCompletion* pCallbackRequestCompletion = NULL;
HRESULT hr = this->QueryInterface(IID_PPV_ARGS(&pCallbackRequestCompletion));
pWdfRequest->SetCompletionCallback(pCallbackRequestCompletion, NULL);
pCallbackRequestCompletion->Release();

//Send down the request with no timeout
hr = pWdfRequest->Send(pWdfTarget, 0, 0);
if (FAILED(hr))
{
//If send failed we need to complete the request with failure
pWdfRequest->CompleteWithInformation(hr, 0);
}
}

VOID CMyQueue::OnCompletion(
IWDFIoRequest* pWdfRequest,
IWDFIoTarget* pWdfIoTarget,
IWDFRequestCompletionParams* pWdfParams,
PVOID pContext)
{
//Complete the request
pWdfRequest->CompleteWithInformation(
pWdfParams->GetCompletionStatus(),
pWdfParams->GetInformation());
}

Juergen, did you notice the OSR USB Fx2 UMDF driver sample in
\src\umdf\usb\fx2_driver\final ?

My first shot at a bulk USB driver used this sample and worked without
hiccups. And it’s a great reference to compare own code to.

xxxxx@sybera.de wrote:

The strange thing is, that my DeviceIoControl request from the application to the driver allways works (the callback will always called). Not so the ReadFile and WriteFile requests. Maybe I was mistaken that its not WinUSB what I call - but anyway, when using DeviceIoControl from my application, OnDeviceIoControl is called, the request will be forwarded and OnCompletion appears.
The difference between the UMDF USB FX2 sample is, that I´m using just one queue in contrary to using a second queue for DeviceIoControl.

Well, let me ask one more obvious COM question. In the declaration of
CMyQueue, do you derive from IQueueCallbackRead and IQueueCallbackWrite
in addition to IQueueCallbackDeviceIoControl? Do you check for those in
CMyQueue::QueryInterface handler?


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.

As Praveen suggested, !wudfqueue will tell you which io callback interfaces are correctly registered on the queue

d

-----Original Message-----
From: xxxxx@lists.osr.com [mailto:xxxxx@lists.osr.com] On Behalf Of Tim Roberts
Sent: Friday, May 15, 2009 9:47 AM
To: Windows System Software Devs Interest List
Subject: Re: [ntdev] WinUSB communication

xxxxx@sybera.de wrote:
> The strange thing is, that my DeviceIoControl request from the application to the driver allways works (the callback will always called). Not so the ReadFile and WriteFile requests. Maybe I was mistaken that its not WinUSB what I call - but anyway, when using DeviceIoControl from my application, OnDeviceIoControl is called, the request will be forwarded and OnCompletion appears.
> The difference between the UMDF USB FX2 sample is, that I?m using just one queue in contrary to using a second queue for DeviceIoControl.
>

Well, let me ask one more obvious COM question. In the declaration of
CMyQueue, do you derive from IQueueCallbackRead and IQueueCallbackWrite
in addition to IQueueCallbackDeviceIoControl? Do you check for those in
CMyQueue::QueryInterface handler?


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer