[ofw] [PATCH v2 1/3] WinMAD: kernel filter driver
Sean Hefty
sean.hefty at intel.com
Thu Dec 11 12:17:07 PST 2008
The WinMad driver is an upper filter driver for Infiniband HCAs. It uses the
kernel IBAL MAD interfaces for access to qp 0 and 1. It exposes a simple file
system interface to userspace for registering and deregistering MAD agents,
and supports file read/write operations to send/receive MADs.
WinMad does not implement SMI or GSI interfaces. It only exposes a userspace
interface to MAD services that is optimized for Windows.
The driver supports asynchronous reads and writes using overlapped I/O, and
handles PnP device removal events with active userspace clients.
File writes results in sending MADs using a specified MAD agent. File read
operations receive MADs across all registered agents for a single user.
Signed-off-by: Sean Hefty <sean.hefty at intel.com>
---
I'm re-posting the WinMad code for merging into the trunk, rather than posting
only the diffs against the changes in my tree. I would like to merge all drivers
and libraries by the end of next week. All files are available in the SVN winverbs
branch.
/*
* Copyright (c) 2008 Intel Corporation. All rights reserved.
*
* This software is available to you under the OpenIB.org BSD license
* below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef _WM_DRIVER_H_
#define _WM_DRIVER_H_
#include <ntddk.h>
#include <wdm.h>
#include <wdf.h>
#include <rdma\verbs.h>
#include <iba\ib_al_ifc.h>
#include "wm_ioctl.h"
#if WINVER <= _WIN32_WINNT_WINXP
#define KGUARDED_MUTEX FAST_MUTEX
#define KeInitializeGuardedMutex ExInitializeFastMutex
#define KeAcquireGuardedMutex ExAcquireFastMutex
#define KeReleaseGuardedMutex ExReleaseFastMutex
#endif
extern WDFDEVICE ControlDevice;
typedef struct _WM_IB_PORT
{
NET64 Guid;
} WM_IB_PORT;
typedef struct _WM_IB_DEVICE
{
LIST_ENTRY Entry;
LONG Ref;
KEVENT Event;
NET64 Guid;
union
{
RDMA_INTERFACE_VERBS VerbsInterface;
ib_al_ifc_t IbInterface;
};
ib_al_handle_t hIbal;
ib_pnp_handle_t hPnp;
//ib_ca_handle_t hDevice;
//ib_pd_handle_t hPd;
int PortCount;
WM_IB_PORT *pPortArray;
} WM_IB_DEVICE;
WM_IB_DEVICE *WmIbDeviceGet(NET64 Guid);
void WmIbDevicePut(WM_IB_DEVICE *pDevice);
#endif // _WM_DRIVER_H_
/*
* Copyright (c) 2008 Intel Corporation. All rights reserved.
*
* This software is available to you under the OpenIB.org BSD license
* below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef _WM_PROVIDER_H_
#define _WM_PROVIDER_H_
#include <ntddk.h>
#include <wdf.h>
#include <wdm.h>
#include <iba\ib_al.h>
#include "wm_driver.h"
#include "index_list.h"
typedef struct _WM_PROVIDER
{
LIST_ENTRY Entry;
INDEX_LIST RegIndex;
WDFQUEUE ReadQueue;
ib_mad_element_t *MadHead;
ib_mad_element_t *MadTail;
KGUARDED_MUTEX Lock;
LONG Ref;
KEVENT Event;
LONG Pending;
LONG Active;
KEVENT SharedEvent;
LONG Exclusive;
KEVENT ExclusiveEvent;
} WM_PROVIDER;
void WmProviderGet(WM_PROVIDER *pProvider);
void WmProviderPut(WM_PROVIDER *pProvider);
NTSTATUS WmProviderInit(WM_PROVIDER *pProvider);
void WmProviderCleanup(WM_PROVIDER *pProvider);
void WmProviderRemoveHandler(WM_PROVIDER *pProvider, WM_IB_DEVICE *pDevice);
void WmProviderDisableRemove(WM_PROVIDER *pProvider);
void WmProviderEnableRemove(WM_PROVIDER *pProvider);
void WmProviderRead(WM_PROVIDER *pProvider, WDFREQUEST Request);
void WmProviderWrite(WM_PROVIDER *pProvider, WDFREQUEST Request);
void WmReceiveHandler(ib_mad_svc_handle_t hService, void *Context,
ib_mad_element_t *pMad);
void WmSendHandler(ib_mad_svc_handle_t hService, void *Context,
ib_mad_element_t *pMad);
void WmProviderFlushReceives(WM_PROVIDER *pProvider,
struct _WM_REGISTRATION *pRegistration);
void WmProviderCancel(WM_PROVIDER *pProvider, WDFREQUEST Request);
#endif // _WM_PROVIDER_H_
/*
* Copyright (c) 2008 Intel Corporation. All rights reserved.
*
* This software is available to you under the OpenIB.org BSD license
* below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef _WM_REG_H_
#define _WM_REG_H_
#include <ntddk.h>
#include <wdm.h>
#include <iba\ib_types.h>
#include "wm_driver.h"
#include "wm_provider.h"
typedef struct _WM_REGISTRATION
{
WM_PROVIDER *pProvider;
WM_IB_DEVICE *pDevice;
ib_al_handle_t hIbal;
ib_ca_handle_t hCa;
ib_pd_handle_t hPd;
ib_qp_handle_t hQp;
ib_pool_key_t hMadPool;
ib_mad_svc_handle_t hService;
SIZE_T Id;
LONG Ref;
} WM_REGISTRATION;
void WmRegister(WM_PROVIDER *pProvider, WDFREQUEST Request);
void WmDeregister(WM_PROVIDER *pProvider, WDFREQUEST Request);
void WmRegFree(WM_REGISTRATION *pRegistration);
void WmRegRemoveHandler(WM_REGISTRATION *pRegistration);
WM_REGISTRATION *WmRegAcquire(WM_PROVIDER *pProvider, UINT64 Id);
void WmRegRelease(WM_REGISTRATION *pRegistration);
#endif // __WM_REG_H_
/*
* Copyright (c) 2008 Intel Corporation. All rights reserved.
*
* This software is available to you under the OpenIB.org BSD license
* below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <ntddk.h>
#include <wdf.h>
#include <wdmsec.h>
#include <ntstatus.h>
#include <initguid.h>
#include "wm_ioctl.h"
#include "wm_driver.h"
#include "wm_provider.h"
#include "wm_reg.h"
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(WM_IB_DEVICE, WmIbDeviceGetContext)
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(WM_PROVIDER, WmProviderGetContext)
WDFDEVICE ControlDevice;
static LIST_ENTRY DevList;
static LIST_ENTRY ProvList;
static KGUARDED_MUTEX Lock;
static EVT_WDF_DRIVER_DEVICE_ADD WmIbDeviceAdd;
static EVT_WDF_OBJECT_CONTEXT_CLEANUP WmIbDeviceCleanup;
static EVT_WDF_DEVICE_D0_ENTRY WmPowerD0Entry;
static EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL WmIoDeviceControl;
static EVT_WDF_IO_QUEUE_IO_READ WmIoRead;
static EVT_WDF_IO_QUEUE_IO_WRITE WmIoWrite;
static EVT_WDF_DEVICE_FILE_CREATE WmFileCreate;
static EVT_WDF_FILE_CLEANUP WmFileCleanup;
static EVT_WDF_FILE_CLOSE WmFileClose;
static WM_IB_DEVICE *WmIbDeviceFind(NET64 Guid)
{
WM_IB_DEVICE *cur_dev, *dev = NULL;
LIST_ENTRY *entry;
for (entry = DevList.Flink; entry != &DevList; entry = entry->Flink) {
cur_dev = CONTAINING_RECORD(entry, WM_IB_DEVICE, Entry);
if (cur_dev->Guid == Guid) {
dev = cur_dev;
break;
}
}
return dev;
}
WM_IB_DEVICE *WmIbDeviceGet(NET64 Guid)
{
WM_IB_DEVICE *dev;
KeAcquireGuardedMutex(&Lock);
dev = WmIbDeviceFind(Guid);
if (dev != NULL) {
InterlockedIncrement(&dev->Ref);
}
KeReleaseGuardedMutex(&Lock);
return dev;
}
void WmIbDevicePut(WM_IB_DEVICE *pDevice)
{
if (InterlockedDecrement(&pDevice->Ref) == 0) {
KeSetEvent(&pDevice->Event, 0, FALSE);
}
}
static VOID WmIoDeviceControl(WDFQUEUE Queue, WDFREQUEST Request,
size_t OutLen, size_t InLen, ULONG IoControlCode)
{
WDFFILEOBJECT file;
WM_PROVIDER *prov;
UNREFERENCED_PARAMETER(OutLen);
UNREFERENCED_PARAMETER(InLen);
UNREFERENCED_PARAMETER(Queue);
file = WdfRequestGetFileObject(Request);
prov = WmProviderGetContext(file);
switch (IoControlCode) {
case WM_IOCTL_REGISTER:
WmRegister(prov, Request);
break;
case WM_IOCTL_DEREGISTER:
WmDeregister(prov, Request);
break;
case WM_IOCTL_CANCEL:
WmProviderCancel(prov, Request);
break;
default:
WdfRequestComplete(Request, STATUS_PROCEDURE_NOT_FOUND);
break;
}
}
static VOID WmIoRead(WDFQUEUE Queue, WDFREQUEST Request, size_t Length)
{
WDFFILEOBJECT file;
WM_PROVIDER *prov;
UNREFERENCED_PARAMETER(Queue);
file = WdfRequestGetFileObject(Request);
prov = WmProviderGetContext(file);
WmProviderRead(prov, Request);
}
static VOID WmIoWrite(WDFQUEUE Queue, WDFREQUEST Request, size_t Length)
{
WDFFILEOBJECT file;
WM_PROVIDER *prov;
UNREFERENCED_PARAMETER(Queue);
file = WdfRequestGetFileObject(Request);
prov = WmProviderGetContext(file);
WmProviderWrite(prov, Request);
}
static VOID WmFileCreate(WDFDEVICE Device, WDFREQUEST Request,
WDFFILEOBJECT FileObject)
{
WM_PROVIDER *prov = WmProviderGetContext(FileObject);
UNREFERENCED_PARAMETER(Device);
WmProviderInit(prov);
KeAcquireGuardedMutex(&Lock);
InsertHeadList(&ProvList, &prov->Entry);
KeReleaseGuardedMutex(&Lock);
WdfRequestComplete(Request, STATUS_SUCCESS);
}
static VOID WmFileCleanup(WDFFILEOBJECT FileObject)
{
WM_PROVIDER *prov = WmProviderGetContext(FileObject);
KeAcquireGuardedMutex(&Lock);
RemoveEntryList(&prov->Entry);
KeReleaseGuardedMutex(&Lock);
WmProviderCleanup(prov);
}
static VOID WmFileClose(WDFFILEOBJECT FileObject)
{
UNREFERENCED_PARAMETER(FileObject);
}
static VOID WmCreateControlDevice(WDFDRIVER Driver)
{
PWDFDEVICE_INIT pinit;
WDF_FILEOBJECT_CONFIG fileconfig;
WDF_OBJECT_ATTRIBUTES attr;
WDF_IO_QUEUE_CONFIG ioconfig;
NTSTATUS status;
WDFQUEUE queue;
DECLARE_CONST_UNICODE_STRING(name, L"\\Device\\WinMad");
DECLARE_CONST_UNICODE_STRING(symlink, L"\\DosDevices\\WinMad");
pinit = WdfControlDeviceInitAllocate(Driver,
&SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);
if (pinit == NULL) {
return;
}
WdfDeviceInitSetExclusive(pinit, FALSE);
status = WdfDeviceInitAssignName(pinit, &name);
if (!NT_SUCCESS(status)) {
goto err1;
}
WDF_FILEOBJECT_CONFIG_INIT(&fileconfig, WmFileCreate, WmFileClose,
WmFileCleanup);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, WM_PROVIDER);
WdfDeviceInitSetFileObjectConfig(pinit, &fileconfig, &attr);
WDF_OBJECT_ATTRIBUTES_INIT(&attr);
status = WdfDeviceCreate(&pinit, &attr, &ControlDevice);
if (!NT_SUCCESS(status)) {
goto err1;
}
status = WdfDeviceCreateSymbolicLink(ControlDevice, &symlink);
if (!NT_SUCCESS(status)) {
goto err2;
}
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioconfig, WdfIoQueueDispatchParallel);
ioconfig.EvtIoDeviceControl = WmIoDeviceControl;
ioconfig.EvtIoRead = WmIoRead;
ioconfig.EvtIoWrite = WmIoWrite;
status = WdfIoQueueCreate(ControlDevice, &ioconfig,
WDF_NO_OBJECT_ATTRIBUTES, &queue);
if (!NT_SUCCESS(status)) {
goto err2;
}
WdfControlFinishInitializing(ControlDevice);
return;
err2:
WdfObjectDelete(ControlDevice);
return;
err1:
WdfDeviceInitFree(pinit);
}
static ib_ca_attr_t *WmQueryCaAttributes(WM_IB_DEVICE *pDevice)
{
ib_ca_attr_t *attr;
UINT32 size;
ib_api_status_t ib_status;
size = 0;
ib_status = pDevice->VerbsInterface.Verbs.
query_ca(pDevice->VerbsInterface.Verbs.p_hca_dev, NULL, &size, NULL);
if (ib_status != IB_INSUFFICIENT_MEMORY) {
attr = NULL;
goto out;
}
attr = ExAllocatePoolWithTag(PagedPool, size, 'acmw');
if (attr == NULL) {
goto out;
}
ib_status = pDevice->VerbsInterface.Verbs.
query_ca(pDevice->VerbsInterface.Verbs.p_hca_dev, attr, &size, NULL);
if (ib_status != IB_SUCCESS) {
ExFreePool(attr);
attr = NULL;
}
out:
return attr;
}
static NTSTATUS WmAddCa(WM_IB_DEVICE *pDevice)
{
NTSTATUS status;
ib_api_status_t ib_status;
ib_ca_attr_t *attr;
UINT32 size;
UINT8 i;
attr = WmQueryCaAttributes(pDevice);
if (attr == NULL) {
return STATUS_NO_MEMORY;
}
size = sizeof(WM_IB_PORT) * attr->num_ports;
pDevice->pPortArray = ExAllocatePoolWithTag(PagedPool, size, 'pimw');
if (pDevice->pPortArray == NULL) {
status = STATUS_NO_MEMORY;
goto out;
}
for (i = 0; i < attr->num_ports; i++) {
pDevice->pPortArray[i].Guid = attr->p_port_attr[i].port_guid;
}
pDevice->PortCount = attr->num_ports;
status = STATUS_SUCCESS;
out:
ExFreePool(attr);
return status;
}
static NTSTATUS WmPowerD0Entry(WDFDEVICE Device, WDF_POWER_DEVICE_STATE PreviousState)
{
WM_IB_DEVICE *dev;
BOOLEAN create;
NTSTATUS status;
dev = WmIbDeviceGetContext(Device);
status = WdfFdoQueryForInterface(Device, &GUID_RDMA_INTERFACE_VERBS,
(PINTERFACE) &dev->VerbsInterface,
sizeof(dev->VerbsInterface), VerbsVersion(2,
0),
NULL);
if (!NT_SUCCESS(status)) {
return status;
}
dev->Guid = dev->VerbsInterface.Verbs.guid;
status = WmAddCa(dev);
dev->VerbsInterface.InterfaceHeader.InterfaceDereference(dev->VerbsInterface.
InterfaceHeader.Context);
if (!NT_SUCCESS(status)) {
return status;
}
status = WdfFdoQueryForInterface(Device, &GUID_IB_AL_INTERFACE,
(PINTERFACE) &dev->IbInterface,
sizeof(dev->IbInterface),
AL_INTERFACE_VERSION, NULL);
if (!NT_SUCCESS(status)) {
return status;
}
KeAcquireGuardedMutex(&Lock);
create = IsListEmpty(&DevList);
InsertHeadList(&DevList, &dev->Entry);
KeReleaseGuardedMutex(&Lock);
if (create) {
WmCreateControlDevice(WdfGetDriver());
}
return STATUS_SUCCESS;
}
static NTSTATUS WmIbDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
{
WDF_OBJECT_ATTRIBUTES attr;
WDF_PNPPOWER_EVENT_CALLBACKS power;
WDFDEVICE dev;
WM_IB_DEVICE *pdev;
NTSTATUS status;
WdfFdoInitSetFilter(DeviceInit);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, WM_IB_DEVICE);
attr.EvtCleanupCallback = WmIbDeviceCleanup;
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&power);
power.EvtDeviceD0Entry = WmPowerD0Entry;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &power);
status = WdfDeviceCreate(&DeviceInit, &attr, &dev);
if (!NT_SUCCESS(status)) {
return status;
}
pdev = WmIbDeviceGetContext(dev);
RtlZeroMemory(pdev, sizeof *pdev);
pdev->Ref = 1;
KeInitializeEvent(&pdev->Event, NotificationEvent, FALSE);
return STATUS_SUCCESS;
}
static VOID WmIbDeviceCleanup(WDFDEVICE Device)
{
WM_PROVIDER *prov;
WM_IB_DEVICE *pdev;
WM_REGISTRATION *reg;
LIST_ENTRY *entry;
BOOLEAN destroy;
WDFDEVICE ctrldev;
pdev = (WmIbDeviceGetContext(Device));
KeAcquireGuardedMutex(&Lock);
RemoveEntryList(&pdev->Entry);
destroy = IsListEmpty(&DevList);
ctrldev = ControlDevice;
for (entry = ProvList.Flink; entry != &ProvList; entry = entry->Flink) {
prov = CONTAINING_RECORD(entry, WM_PROVIDER, Entry);
WmProviderRemoveHandler(prov, pdev);
}
KeReleaseGuardedMutex(&Lock);
if (InterlockedDecrement(&pdev->Ref) > 0) {
KeWaitForSingleObject(&pdev->Event, Executive, KernelMode, FALSE, NULL);
}
pdev->IbInterface.wdm.InterfaceDereference(pdev->IbInterface.wdm.Context);
if (pdev->pPortArray != NULL) {
ExFreePool(pdev->pPortArray);
}
if (destroy) {
WdfObjectDelete(ctrldev);
}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDFDRIVER driv;
InitializeListHead(&DevList);
InitializeListHead(&ProvList);
KeInitializeGuardedMutex(&Lock);
WDF_DRIVER_CONFIG_INIT(&config, WmIbDeviceAdd);
status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES,
&config, &driv);
if (!NT_SUCCESS(status)) {
return status;
}
return STATUS_SUCCESS;
}
/*
* Copyright (c) 2008 Intel Corporation. All rights reserved.
*
* This software is available to you under the OpenIB.org BSD license
* below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "index_list.c"
#include "wm_driver.h"
#include "wm_ioctl.h"
#include "wm_provider.h"
#include "wm_reg.h"
void WmProviderGet(WM_PROVIDER *pProvider)
{
InterlockedIncrement(&pProvider->Ref);
}
void WmProviderPut(WM_PROVIDER *pProvider)
{
if (InterlockedDecrement(&pProvider->Ref) == 0) {
KeSetEvent(&pProvider->Event, 0, FALSE);
}
}
NTSTATUS WmProviderInit(WM_PROVIDER *pProvider)
{
WDF_IO_QUEUE_CONFIG config;
NTSTATUS status;
IndexListInit(&pProvider->RegIndex);
pProvider->MadHead = NULL;
pProvider->MadTail = NULL;
KeInitializeGuardedMutex(&pProvider->Lock);
pProvider->Ref = 1;
KeInitializeEvent(&pProvider->Event, NotificationEvent, FALSE);
pProvider->Pending = 0;
pProvider->Active = 0;
KeInitializeEvent(&pProvider->SharedEvent, NotificationEvent, FALSE);
pProvider->Exclusive = 0;
KeInitializeEvent(&pProvider->ExclusiveEvent, SynchronizationEvent, FALSE);
WDF_IO_QUEUE_CONFIG_INIT(&config, WdfIoQueueDispatchManual);
status = WdfIoQueueCreate(ControlDevice, &config,
WDF_NO_OBJECT_ATTRIBUTES, &pProvider->ReadQueue);
return status;
}
static void WmInsertMad(WM_PROVIDER *pProvider, ib_mad_element_t *pMad)
{
if (pProvider->MadHead == NULL) {
pProvider->MadHead = pMad;
} else {
pProvider->MadTail->p_next = pMad;
}
pProvider->MadTail = pMad;
}
static ib_mad_element_t *WmRemoveMad(WM_PROVIDER *pProvider)
{
ib_mad_element_t *mad;
mad = pProvider->MadHead;
if (mad != NULL) {
pProvider->MadHead = (ib_mad_element_t *) mad->p_next;
mad->p_next = NULL;
}
return mad;
}
void WmProviderFlushReceives(WM_PROVIDER *pProvider, WM_REGISTRATION *pRegistration)
{
ib_mad_element_t *mad, *next, *list;
WdfObjectAcquireLock(pProvider->ReadQueue);
list = pProvider->MadHead;
pProvider->MadHead = NULL;
for (mad = list; mad != NULL; mad = next) {
next = mad->p_next;
mad->p_next = NULL;
if (mad->send_context1 == pRegistration) {
pRegistration->pDevice->IbInterface.put_mad(mad);
} else {
WmInsertMad(pProvider, mad);
}
}
WdfObjectReleaseLock(pProvider->ReadQueue);
}
void WmProviderCleanup(WM_PROVIDER *pProvider)
{
WM_REGISTRATION *reg;
while ((reg = IndexListRemoveHead(&pProvider->RegIndex)) != NULL) {
WmRegFree(reg);
}
if (InterlockedDecrement(&pProvider->Ref) > 0) {
KeWaitForSingleObject(&pProvider->Event, Executive, KernelMode, FALSE, NULL);
}
WdfIoQueuePurgeSynchronously(pProvider->ReadQueue);
WdfObjectDelete(pProvider->ReadQueue);
IndexListDestroy(&pProvider->RegIndex);
}
// See comment above WmProviderRemoveHandler.
static void WmProviderLockRemove(WM_PROVIDER *pProvider)
{
KeAcquireGuardedMutex(&pProvider->Lock);
pProvider->Exclusive++;
KeClearEvent(&pProvider->SharedEvent);
while (pProvider->Active > 0) {
KeReleaseGuardedMutex(&pProvider->Lock);
KeWaitForSingleObject(&pProvider->ExclusiveEvent, Executive, KernelMode,
FALSE, NULL);
KeAcquireGuardedMutex(&pProvider->Lock);
}
pProvider->Active++;
KeReleaseGuardedMutex(&pProvider->Lock);
}
// See comment above WmProviderRemoveHandler.
static void WmProviderUnlockRemove(WM_PROVIDER *pProvider)
{
KeAcquireGuardedMutex(&pProvider->Lock);
pProvider->Exclusive--;
pProvider->Active--;
if (pProvider->Exclusive > 0) {
KeSetEvent(&pProvider->ExclusiveEvent, 0, FALSE);
} else if (pProvider->Pending > 0) {
KeSetEvent(&pProvider->SharedEvent, 0, FALSE);
}
KeReleaseGuardedMutex(&pProvider->Lock);
}
/*
* Must hold pProvider->Lock. Function may release and re-acquire.
* See comment above WmProviderRemoveHandler.
*/
void WmProviderDisableRemove(WM_PROVIDER *pProvider)
{
while (pProvider->Exclusive > 0) {
pProvider->Pending++;
KeReleaseGuardedMutex(&pProvider->Lock);
KeWaitForSingleObject(&pProvider->SharedEvent, Executive, KernelMode,
FALSE, NULL);
KeAcquireGuardedMutex(&pProvider->Lock);
pProvider->Pending--;
}
InterlockedIncrement(&pProvider->Active);
}
/*
* No need to hold pProvider->Lock when releasing.
* See comment above WmProviderRemoveHandler.
*/
void WmProviderEnableRemove(WM_PROVIDER *pProvider)
{
InterlockedDecrement(&pProvider->Active);
if (pProvider->Exclusive > 0) {
KeSetEvent(&pProvider->ExclusiveEvent, 0, FALSE);
}
}
/*
* The remove handler blocks all other threads executing through this
* provider until the remove has been processed. Because device removal is
* rare, we want a simple, optimized code path for all calls that access
* the underlying hardware device, making use of any locks that we would
* have to acquire anyway. The locking for exclusive access can be
* as ugly and slow as needed.
*/
void WmProviderRemoveHandler(WM_PROVIDER *pProvider, WM_IB_DEVICE *pDevice)
{
WM_REGISTRATION *reg;
SIZE_T i;
WmProviderLockRemove(pProvider);
IndexListForEach(&pProvider->RegIndex, i) {
reg = IndexListAt(&pProvider->RegIndex, i);
if (reg->pDevice == pDevice) {
WmRegRemoveHandler(reg);
}
}
WmProviderUnlockRemove(pProvider);
}
static NTSTATUS WmCopyRead(WM_PROVIDER *pProvider, WM_IO_MAD *pIoMad,
ib_mad_element_t *pMad, size_t *pLen)
{
WM_REGISTRATION *reg;
reg = (WM_REGISTRATION *) pMad->send_context1;
pIoMad->Id = reg->Id;
pIoMad->Status = pMad->status;
pIoMad->Timeout = pMad->timeout_ms;
pIoMad->Retries = pMad->retry_cnt;
pIoMad->Length = pMad->size;
pIoMad->Address.Qpn = pMad->remote_qp;
pIoMad->Address.Qkey = pMad->remote_qkey;
pIoMad->Address.PkeyIndex = pMad->pkey_index;
if ((pIoMad->Address.GrhValid = (UINT8) pMad->grh_valid)) {
pIoMad->Address.VersionClassFlow = pMad->p_grh->ver_class_flow;
pIoMad->Address.HopLimit = pMad->p_grh->hop_limit;
pIoMad->Address.GidIndex = 0; // TODO: update IBAL to use SGID index
RtlCopyMemory(pIoMad->Address.Gid, pMad->p_grh->dest_gid.raw, 16);
}
pIoMad->Address.Lid = pMad->remote_lid;
pIoMad->Address.ServiceLevel = pMad->remote_sl;
pIoMad->Address.PathBits = pMad->path_bits;
pIoMad->Address.StaticRate = 0;
pIoMad->Address.Reserved = 0;
if (*pLen >= sizeof(WM_IO_MAD) + pMad->size) {
RtlCopyMemory(pIoMad + 1, pMad->p_mad_buf, pMad->size);
*pLen = sizeof(WM_IO_MAD) + pMad->size;
return STATUS_SUCCESS;
} else {
*pLen = sizeof(WM_IO_MAD);
return STATUS_MORE_ENTRIES;
}
}
void WmProviderRead(WM_PROVIDER *pProvider, WDFREQUEST Request)
{
WM_REGISTRATION *reg;
NTSTATUS status;
WM_IO_MAD *wmad;
size_t outlen, len = 0;
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(WM_IO_MAD), &wmad, &outlen);
if (!NT_SUCCESS(status)) {
goto out;
}
WdfObjectAcquireLock(pProvider->ReadQueue);
if (pProvider->MadHead == NULL) {
status = WdfRequestForwardToIoQueue(Request, pProvider->ReadQueue);
WdfObjectReleaseLock(pProvider->ReadQueue);
if (NT_SUCCESS(status)) {
return;
}
goto out;
}
len = outlen;
status = WmCopyRead(pProvider, wmad, pProvider->MadHead, &len);
if (NT_SUCCESS(status)) {
reg = (WM_REGISTRATION *) pProvider->MadHead->send_context1;
reg->pDevice->IbInterface.put_mad(WmRemoveMad(pProvider));
}
WdfObjectReleaseLock(pProvider->ReadQueue);
out:
WdfRequestCompleteWithInformation(Request, status, len);
}
static NTSTATUS WmSendMad(WM_REGISTRATION *pRegistration, WM_IO_MAD *pIoMad, UINT32 size)
{
ib_al_ifc_t *pifc;
NTSTATUS status;
ib_mad_element_t *mad;
ib_api_status_t ib_status;
pifc = &pRegistration->pDevice->IbInterface;
ib_status = pifc->get_mad(pRegistration->hMadPool, size, &mad);
if (ib_status != IB_SUCCESS) {
return STATUS_NO_MEMORY;
}
mad->context1 = pRegistration;
RtlCopyMemory(mad->p_mad_buf, pIoMad + 1, size);
mad->remote_qp = pIoMad->Address.Qpn;
mad->remote_qkey = pIoMad->Address.Qkey;
mad->resp_expected = (pIoMad->Timeout > 0);
mad->timeout_ms = pIoMad->Timeout;
mad->retry_cnt = pIoMad->Retries;
if ((mad->grh_valid = pIoMad->Address.GrhValid)) {
mad->p_grh->ver_class_flow = pIoMad->Address.VersionClassFlow;
mad->p_grh->hop_limit = pIoMad->Address.HopLimit;
// TODO: update IBAL to use SGID index
// mad->p_grh->src_gid_index = pIoMad->Address.GidIndex;
RtlCopyMemory(mad->p_grh->dest_gid.raw, pIoMad->Address.Gid, 16);
}
mad->remote_lid = pIoMad->Address.Lid;
mad->remote_sl = pIoMad->Address.ServiceLevel;
mad->pkey_index = pIoMad->Address.PkeyIndex;
mad->path_bits = pIoMad->Address.PathBits;
mad->p_mad_buf->trans_id &= 0xFFFFFFFF00000000;
ib_status = pifc->send_mad(pRegistration->hService, mad, NULL);
if (ib_status != IB_SUCCESS) {
status = STATUS_UNSUCCESSFUL;
goto err;
}
return STATUS_SUCCESS;
err:
pRegistration->pDevice->IbInterface.put_mad(mad);
return status;
}
void WmProviderWrite(WM_PROVIDER *pProvider, WDFREQUEST Request)
{
WM_REGISTRATION *reg;
NTSTATUS status;
WM_IO_MAD *wmad;
size_t inlen;
status = WdfRequestRetrieveInputBuffer(Request, sizeof(WM_IO_MAD) + 24,
&wmad, &inlen);
if (!NT_SUCCESS(status)) {
goto out;
}
reg = WmRegAcquire(pProvider, wmad->Id);
if (reg == NULL) {
status = STATUS_NOT_FOUND;
goto out;
}
status = WmSendMad(reg, wmad, (UINT32) (inlen - sizeof(WM_IO_MAD)));
WmRegRelease(reg);
out:
WdfRequestComplete(Request, status);
}
void WmReceiveHandler(ib_mad_svc_handle_t hService, void *Context,
ib_mad_element_t *pMad)
{
WM_REGISTRATION *reg;
WM_PROVIDER *prov = Context;
WDFREQUEST request;
NTSTATUS status;
WM_IO_MAD *wmad;
size_t len = 0;
UNREFERENCED_PARAMETER(hService);
WdfObjectAcquireLock(prov->ReadQueue);
status = WdfIoQueueRetrieveNextRequest(prov->ReadQueue, &request);
if (!NT_SUCCESS(status)) {
WmInsertMad(prov, pMad);
WdfObjectReleaseLock(prov->ReadQueue);
return;
}
status = WdfRequestRetrieveOutputBuffer(request, sizeof(WM_IO_MAD), &wmad, &len);
if (!NT_SUCCESS(status)) {
reg = (WM_REGISTRATION *) pMad->send_context1;
reg->pDevice->IbInterface.put_mad(pMad);
goto out;
}
status = WmCopyRead(prov, wmad, pMad, &len);
if (NT_SUCCESS(status)) {
reg = (WM_REGISTRATION *) pMad->send_context1;
reg->pDevice->IbInterface.put_mad(pMad);
} else {
WmInsertMad(prov, pMad);
}
WdfObjectReleaseLock(prov->ReadQueue);
out:
WdfRequestCompleteWithInformation(request, status, len);
}
void WmSendHandler(ib_mad_svc_handle_t hService, void *Context,
ib_mad_element_t *pMad)
{
if (pMad->status == IB_SUCCESS) {
((WM_REGISTRATION *) pMad->context1)->pDevice->IbInterface.put_mad(pMad);
} else {
pMad->send_context1 = (void*) pMad->context1;
WmReceiveHandler(hService, Context, pMad);
}
}
void WmProviderCancel(WM_PROVIDER *pProvider, WDFREQUEST Request)
{
WDFREQUEST request;
NTSTATUS status;
WdfObjectAcquireLock(pProvider->ReadQueue);
status = WdfIoQueueRetrieveNextRequest(pProvider->ReadQueue, &request);
while (NT_SUCCESS(status)) {
WdfRequestComplete(request, STATUS_CANCELLED);
status = WdfIoQueueRetrieveNextRequest(pProvider->ReadQueue, &request);
}
WdfObjectReleaseLock(pProvider->ReadQueue);
WdfRequestComplete(Request, status);
}
/*
* Copyright (c) 2008 Intel Corporation. All rights reserved.
*
* This software is available to you under the OpenIB.org BSD license
* below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <iba\ib_al.h>
#include "wm_driver.h"
#include "wm_reg.h"
#include "wm_ioctl.h"
WM_REGISTRATION *WmRegAcquire(WM_PROVIDER *pProvider, UINT64 Id)
{
WM_REGISTRATION *reg;
KeAcquireGuardedMutex(&pProvider->Lock);
WmProviderDisableRemove(pProvider);
reg = IndexListAt(&pProvider->RegIndex, (SIZE_T) Id);
if (reg != NULL && reg->pDevice != NULL) {
InterlockedIncrement(®->Ref);
} else {
reg = NULL;
WmProviderEnableRemove(pProvider);
}
KeReleaseGuardedMutex(&pProvider->Lock);
return reg;
}
void WmRegRelease(WM_REGISTRATION *pRegistration)
{
WmProviderEnableRemove(pRegistration->pProvider);
InterlockedDecrement(&pRegistration->Ref);
}
static WM_REGISTRATION *WmRegAlloc(WM_PROVIDER *pProvider)
{
WM_REGISTRATION *reg;
reg = ExAllocatePoolWithTag(PagedPool, sizeof(WM_REGISTRATION), 'grmw');
if (reg == NULL) {
return NULL;
}
RtlZeroMemory(reg, sizeof(WM_REGISTRATION));
reg->Ref = 1;
reg->pProvider = pProvider;
WmProviderGet(pProvider);
return reg;
}
static int WmConvertMethods(ib_mad_svc_t *svc, WM_IO_REGISTER *pAttributes)
{
int i, j, unsolicited = 0;
for (i = 0; i < 16; i++) {
for (j = 0; j < 8; j++) {
if (((pAttributes->Methods[i] >> j) & 0x01) != 0) {
svc->method_array[i * 8 + j] = 1;
unsolicited = 1;
}
}
}
return unsolicited;
}
static NTSTATUS WmRegInit(WM_REGISTRATION *pRegistration, WM_IO_REGISTER *pAttributes)
{
WM_IB_DEVICE *dev;
ib_qp_create_t attr;
ib_mad_svc_t svc;
ib_api_status_t ib_status;
NTSTATUS status;
RtlZeroMemory(&attr, sizeof attr);
if (pAttributes->Qpn == 0) {
attr.qp_type = IB_QPT_QP0_ALIAS;
} else if (pAttributes->Qpn == IB_QP1) {
attr.qp_type = IB_QPT_QP1_ALIAS;
} else {
return STATUS_BAD_NETWORK_PATH;
}
dev = WmIbDeviceGet(pAttributes->Guid);
if (dev == NULL) {
return STATUS_NO_SUCH_DEVICE;
}
if (--pAttributes->Port > dev->PortCount) {
status = STATUS_INVALID_PORT_HANDLE;
goto err1;
}
ib_status = dev->IbInterface.open_al(&pRegistration->hIbal);
if (ib_status != IB_SUCCESS) {
status = STATUS_UNSUCCESSFUL;
goto err1;
}
ib_status = dev->IbInterface.open_ca(pRegistration->hIbal, pAttributes->Guid,
NULL, NULL, &pRegistration->hCa);
if (ib_status != IB_SUCCESS) {
goto err2;
}
ib_status = dev->IbInterface.alloc_pd(pRegistration->hCa, IB_PDT_ALIAS,
NULL, &pRegistration->hPd);
if (ib_status != IB_SUCCESS) {
goto err3;
}
attr.sq_depth = attr.rq_depth = 1;
attr.sq_sge = attr.rq_sge = 1;
attr.sq_signaled = 1;
ib_status = dev->IbInterface.get_spl_qp(pRegistration->hPd,
dev->pPortArray[pAttributes->Port].Guid,
&attr, pRegistration, NULL,
&pRegistration->hMadPool,
&pRegistration->hQp);
if (ib_status != IB_SUCCESS) {
status = STATUS_UNSUCCESSFUL;
goto err4;
}
svc.mad_svc_context = pRegistration->pProvider;
svc.pfn_mad_send_cb = WmSendHandler;
svc.pfn_mad_recv_cb = WmReceiveHandler;
svc.support_unsol = WmConvertMethods(&svc, pAttributes);
svc.mgmt_class = pAttributes->Class;
svc.mgmt_version = pAttributes->Version;
svc.svc_type = IB_MAD_SVC_DEFAULT;
ib_status = dev->IbInterface.reg_mad_svc(pRegistration->hQp, &svc,
&pRegistration->hService);
if (ib_status != IB_SUCCESS) {
status = STATUS_UNSUCCESSFUL;
goto err5;
}
pRegistration->pDevice = dev;
return STATUS_SUCCESS;
err5:
dev->IbInterface.destroy_qp(pRegistration->hQp, NULL);
err4:
dev->IbInterface.dealloc_pd(pRegistration->hPd, NULL);
err3:
dev->IbInterface.close_ca(pRegistration->hCa, NULL);
err2:
dev->IbInterface.close_al(pRegistration->hIbal);
err1:
WmIbDevicePut(dev);
pRegistration->pDevice = NULL;
return status;
}
void WmRegister(WM_PROVIDER *pProvider, WDFREQUEST Request)
{
WM_REGISTRATION *reg;
WM_IO_REGISTER *attr;
UINT64 *id;
NTSTATUS status;
status = WdfRequestRetrieveInputBuffer(Request, sizeof(WM_IO_REGISTER), &attr, NULL);
if (!NT_SUCCESS(status)) {
goto err1;
}
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(UINT64), &id, NULL);
if (!NT_SUCCESS(status)) {
goto err1;
}
reg = WmRegAlloc(pProvider);
if (reg == NULL) {
status = STATUS_NO_MEMORY;
goto err1;
}
KeAcquireGuardedMutex(&pProvider->Lock);
WmProviderDisableRemove(pProvider);
KeReleaseGuardedMutex(&pProvider->Lock);
status = WmRegInit(reg, attr);
if (!NT_SUCCESS(status)) {
goto err2;
}
KeAcquireGuardedMutex(&pProvider->Lock);
reg->Id = IndexListInsertHead(&pProvider->RegIndex, reg);
if (reg->Id == 0) {
status = STATUS_NO_MEMORY;
goto err2;
}
KeReleaseGuardedMutex(&pProvider->Lock);
WmProviderEnableRemove(pProvider);
*id = reg->Id;
WdfRequestCompleteWithInformation(Request, status, sizeof(UINT64));
return;
err2:
WmRegFree(reg);
WmProviderEnableRemove(pProvider);
err1:
WdfRequestComplete(Request, status);
}
void WmDeregister(WM_PROVIDER *pProvider, WDFREQUEST Request)
{
WM_REGISTRATION *reg;
UINT64 *id;
NTSTATUS status;
status = WdfRequestRetrieveInputBuffer(Request, sizeof(UINT64), &id, NULL);
if (!NT_SUCCESS(status)) {
goto out;
}
KeAcquireGuardedMutex(&pProvider->Lock);
WmProviderDisableRemove(pProvider);
reg = IndexListAt(&pProvider->RegIndex, (SIZE_T) *id);
if (reg == NULL) {
status = STATUS_NO_SUCH_DEVICE;
} else if (reg->Ref > 1) {
status = STATUS_ACCESS_DENIED;
} else {
IndexListRemove(&pProvider->RegIndex, (SIZE_T) *id);
status = STATUS_SUCCESS;
}
KeReleaseGuardedMutex(&pProvider->Lock);
if (NT_SUCCESS(status)) {
WmRegFree(reg);
}
WmProviderEnableRemove(pProvider);
out:
WdfRequestComplete(Request, status);
}
void WmRegFree(WM_REGISTRATION *pRegistatration)
{
WmRegRemoveHandler(pRegistatration);
WmProviderPut(pRegistatration->pProvider);
ExFreePool(pRegistatration);
}
void WmRegRemoveHandler(WM_REGISTRATION *pRegistration)
{
if (pRegistration->pDevice == NULL) {
return;
}
pRegistration->pDevice->IbInterface.destroy_qp(pRegistration->hQp, NULL);
pRegistration->pDevice->IbInterface.dealloc_pd(pRegistration->hPd, NULL);
pRegistration->pDevice->IbInterface.close_ca(pRegistration->hCa, NULL);
pRegistration->pDevice->IbInterface.close_al(pRegistration->hIbal);
WmProviderFlushReceives(pRegistration->pProvider, pRegistration);
WmIbDevicePut(pRegistration->pDevice);
pRegistration->pDevice = NULL;
}
More information about the ofw
mailing list