Files
nt-lite/code/kernel/io_pnp.c
T

609 lines
22 KiB
C

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ntlite/ddi/io_driver.h>
#include <ntlite/ddk/device.h>
#include <kernel/io_pnp.h>
#include <kernel/ke_event.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_work.h>
#include <kernel/ke_time.h>
#include <kernel/mm_virt.h>
#include <kernel/ke_debug.h>
#define PNP_WORKERS 2u
typedef enum PNP_BIND_STATE {
PnpUnbound,
PnpReserved,
PnpStarting,
PnpPending,
PnpCancelling,
PnpStarted,
PnpFailed,
PnpCleaning,
PnpStopping,
} PNP_BIND_STATE;
typedef struct PNP_DRIVER {
struct PNP_DRIVER* Next;
const NTL_DEVICE_DESC* Descriptor;
DRIVER_OBJECT* IoDriver;
struct NTL_MODULE* Module;
bool Committed;
} PNP_DRIVER;
typedef struct PNP_NODE {
struct PNP_NODE* Next;
DEVICE_OBJECT* Pdo;
DEVICE_OBJECT* ParentPdo;
NTL_DEVICE* Binding;
const PNP_DRIVER* Driver;
PNP_BIND_STATE State;
bool Removed;
bool Published;
bool CancelIssued;
} PNP_NODE;
struct NTL_DEVICE_RESOURCE {
NTL_DEVICE_RESOURCE* Next;
NTL_DEVICE* Owner;
NTL_DEVICE_RESOURCE_RELEASE Release;
void* Context;
};
struct NTL_DEVICE {
SPINLOCK Lock;
PNP_NODE* Node;
void* Context;
NTL_DEVICE_RESOURCE* Resources;
KSTATUS CompletionStatus;
bool CompletionSet;
bool Closing;
};
static SPINLOCK gPnpLock = SPINLOCK_INIT;
static PNP_DRIVER* gPnpDrivers;
static PNP_DRIVER* gPnpDriversTail;
static PNP_NODE* gPnpNodes;
static PNP_NODE* gPnpNodesTail;
static size_t gPnpNodeCount;
static EVENT* gPnpReady;
static EVENT* gPnpChanged;
static bool gPnpStarted;
static bool gPnpShutdown;
static KWORK_ITEM gPnpWakeItem;
static void IoPnpSignal(void* unused) {
(void)unused;
for (uint32_t i = 0; i < PNP_WORKERS; ++i)
KeSignalEvent(gPnpReady);
KeSignalEvent(gPnpChanged);
}
static void IoPnpWake(void) {
if (gPnpStarted)
(void)KeQueueWorkItem(&gPnpWakeItem);
}
static bool IoPnpMatches(const NTL_DEVICE_DESC* descriptor, const DEVICE_OBJECT* pdo) {
for (uint32_t i = 0; i < descriptor->MatchCount; ++i) {
const NTL_DEVICE_MATCH* match = &descriptor->Matches[i];
if ((uint32_t)match->Bus != (uint32_t)pdo->u.Pdo.BusType)
continue;
size_t length = strlen(match->Id);
for (uint8_t j = 0; j < pdo->u.Pdo.IdCount; ++j) {
SYS_STRING_VIEW id = pdo->u.Pdo.Ids[j].Id;
if (id.Length == length && !memcmp(id.Data, match->Id, length))
return true;
}
}
return false;
}
static bool IoPnpMatchesIoDriver(const DRIVER_OBJECT* driver, const DEVICE_OBJECT* pdo) {
if (!driver->IdTable || !driver->DriverAddDevice) return false;
for (size_t i = 0; i < driver->IdTable->Count; ++i) {
const IO_DEVICE_ID* wanted = &driver->IdTable->Ids[i];
if (wanted->BusType != IoBusUnknown && wanted->BusType != pdo->u.Pdo.BusType)
continue;
for (uint8_t j = 0; j < pdo->u.Pdo.IdCount; ++j)
if (SysSvEqual(wanted->Id, pdo->u.Pdo.Ids[j].Id)) return true;
}
return false;
}
static const PNP_DRIVER* IoPnpFindDriver(const DEVICE_OBJECT* pdo) {
for (PNP_DRIVER* driver = gPnpDrivers; driver; driver = driver->Next) {
if (!driver->Committed) continue;
if ((driver->Descriptor && IoPnpMatches(driver->Descriptor, pdo)) ||
(driver->IoDriver && IoPnpMatchesIoDriver(driver->IoDriver, pdo)))
return driver;
}
return (const PNP_DRIVER*)0;
}
static PNP_NODE* IoPnpFindNode(const DEVICE_OBJECT* pdo) {
for (PNP_NODE* node = gPnpNodes; node; node = node->Next)
if (node->Pdo == pdo)
return node;
return (PNP_NODE*)0;
}
static DEVICE_OBJECT* IoPnpParentPdo(const DEVICE_OBJECT* pdo) {
const DEVICE_OBJECT* parent = pdo->Parent;
for (uint32_t i = 0; parent && i < 64u; ++i) {
if (parent->Type == DeviceObjectPhysical)
return (DEVICE_OBJECT*)parent;
parent = parent->LowerDevice ? parent->LowerDevice : parent->Parent;
}
return (DEVICE_OBJECT*)0;
}
static bool IoPnpHasChildren(const DEVICE_OBJECT* parent) {
for (PNP_NODE* candidate = gPnpNodes; candidate; candidate = candidate->Next) {
const DEVICE_OBJECT* ancestor = candidate->ParentPdo;
for (size_t depth = 0; ancestor && depth < gPnpNodeCount; ++depth) {
if (ancestor == parent) return true;
const PNP_NODE* node = IoPnpFindNode(ancestor);
ancestor = node ? node->ParentPdo : (const DEVICE_OBJECT*)0;
}
}
return false;
}
static void IoPnpReleaseResources(NTL_DEVICE* device) {
uintptr_t flags = KeAcquireSpinlock(&device->Lock);
device->Closing = true;
NTL_DEVICE_RESOURCE* resource = device->Resources;
device->Resources = (NTL_DEVICE_RESOURCE*)0;
KeReleaseSpinlock(&device->Lock, flags);
while (resource) {
NTL_DEVICE_RESOURCE* next = resource->Next;
resource->Owner = (NTL_DEVICE*)0;
resource->Release(resource->Context);
MmFreePool(resource);
resource = next;
}
}
static void IoPnpDispose(PNP_NODE* node) {
NTL_DEVICE* device = node->Binding;
if (device) {
if (node->State == PnpStopping) {
if (node->Driver->Descriptor && node->Driver->Descriptor->Stop)
node->Driver->Descriptor->Stop(device);
else if (node->Driver->IoDriver && !node->Pdo->DeletePending)
(void)IoSendPnpIrpSync(node->Pdo, IRP_MN_REMOVE_DEVICE);
}
IoPnpReleaseResources(device);
MmFreePool(device);
}
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
PNP_NODE** link = &gPnpNodes;
while (*link && *link != node) link = &(*link)->Next;
if (*link) {
*link = node->Next;
if (gPnpNodesTail == node) {
gPnpNodesTail = (PNP_NODE*)0;
for (PNP_NODE* it = gPnpNodes; it; it = it->Next) gPnpNodesTail = it;
}
--gPnpNodeCount;
}
KeReleaseSpinlock(&gPnpLock, flags);
IoDereferenceDeviceObject(node->Pdo);
MmFreePool(node);
IoPnpWake();
}
static void IoPnpStart(PNP_NODE* node) {
const NTL_DEVICE_DESC* desc = node->Driver->Descriptor;
size_t contextSize = desc ? desc->ContextSize : 0;
NTL_DEVICE* device = (NTL_DEVICE*)MmAllocatePool(sizeof(*device) + contextSize);
if (!device) {
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
node->State = PnpFailed;
KeReleaseSpinlock(&gPnpLock, flags);
IoPnpWake();
return;
}
memset(device, 0, sizeof(*device) + contextSize);
device->Lock = (SPINLOCK)SPINLOCK_INIT;
device->Node = node;
device->Context = (void*)(device + 1);
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
node->Binding = device;
node->State = PnpStarting;
KeReleaseSpinlock(&gPnpLock, flags);
KSTATUS result;
if (desc) {
result = desc->Start(device);
} else {
DRIVER_OBJECT* io = node->Driver->IoDriver;
result = io->DriverAddDevice(io, node->Pdo);
if (KSUCCESS(result)) {
result = IoSendPnpIrpSync(node->Pdo, IRP_MN_START_DEVICE);
if (result == NTLSTATUS_NOT_SUPPORTED) result = NTLSTATUS_SUCCESS;
}
}
flags = KeAcquireSpinlock(&gPnpLock);
if (device->CompletionSet)
result = device->CompletionStatus;
node->State = result == NTLSTATUS_PENDING ? PnpPending :
KSUCCESS(result) ? PnpStarted : PnpFailed;
KeReleaseSpinlock(&gPnpLock, flags);
if (KFAILED(result))
DbgPrint("pnp: start failed (0x%x)\n", result);
IoPnpWake();
}
static void IoPnpWorker(void* ignored) {
(void)ignored;
for (;;) {
(void)KeWaitForEvent(gPnpReady, false);
for (;;) {
PNP_NODE* node = (PNP_NODE*)0;
bool stopping = false;
bool cleanup = false;
bool cancel = false;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
for (PNP_NODE* candidate = gPnpNodes; candidate; candidate = candidate->Next) {
if (candidate->State == PnpPending && candidate->Removed &&
!candidate->CancelIssued && (candidate->Driver->Descriptor && candidate->Driver->Descriptor->CancelStart)) {
candidate->CancelIssued = true;
candidate->State = PnpCancelling;
node = candidate;
cancel = true;
break;
}
if (candidate->State == PnpFailed && candidate->Binding && !candidate->Removed) {
candidate->State = PnpCleaning;
node = candidate;
cleanup = true;
break;
}
if (gPnpShutdown && candidate->Removed &&
IoPnpHasChildren(candidate->Pdo))
continue;
if (candidate->Removed && candidate->State != PnpReserved &&
candidate->State != PnpStarting && candidate->State != PnpPending &&
candidate->State != PnpCancelling && candidate->State != PnpStopping &&
candidate->State != PnpCleaning) {
candidate->State = PnpStopping;
node = candidate;
stopping = true;
break;
}
if (candidate->State != PnpUnbound || candidate->Removed ||
gPnpShutdown || !candidate->Published)
continue;
const PNP_DRIVER* driver = IoPnpFindDriver(candidate->Pdo);
if (driver) {
candidate->Driver = driver;
candidate->State = PnpReserved;
node = candidate;
break;
}
}
KeReleaseSpinlock(&gPnpLock, flags);
if (!node) break;
KeSignalEvent(gPnpReady);
if (stopping) {
IoPnpDispose(node);
} else if (cancel) {
node->Driver->Descriptor->CancelStart(node->Binding);
uintptr_t update = KeAcquireSpinlock(&gPnpLock);
node->State = node->Binding->CompletionSet ?
(KSUCCESS(node->Binding->CompletionStatus) ? PnpStarted : PnpFailed) :
PnpPending;
KeReleaseSpinlock(&gPnpLock, update);
IoPnpWake();
} else if (cleanup) {
NTL_DEVICE* device = node->Binding;
if (node->Driver->Descriptor && node->Driver->Descriptor->Stop)
node->Driver->Descriptor->Stop(device);
else if (node->Driver->IoDriver && node->Pdo->AttachedDevice)
(void)IoSendPnpIrpSync(node->Pdo, IRP_MN_REMOVE_DEVICE);
IoPnpReleaseResources(device);
uintptr_t update = KeAcquireSpinlock(&gPnpLock);
node->Binding = (NTL_DEVICE*)0;
node->State = PnpFailed;
KeReleaseSpinlock(&gPnpLock, update);
MmFreePool(device);
IoPnpWake();
} else {
IoPnpStart(node);
}
}
}
}
KSTATUS IoPnpInitialize(void) {
if (gPnpStarted) return NTLSTATUS_ALREADY_EXISTS;
KSTATUS st = KeEventCreate(EventAutoReset, false, &gPnpReady);
if (!KSUCCESS(st)) return st;
st = KeEventCreate(EventAutoReset, false, &gPnpChanged);
if (!KSUCCESS(st)) return st;
for (uint32_t i = 0; i < PNP_WORKERS; ++i) {
const KE_THREAD_CREATE_PARAMETERS params =
KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "pnp.device");
st = KeCreateKernelThread((uintptr_t)IoPnpWorker, 0, &params, (THREAD**)0);
if (!KSUCCESS(st)) return st;
}
KeInitializeWorkItem(&gPnpWakeItem, IoPnpSignal, (void*)0);
gPnpStarted = true;
IoPnpWake();
return NTLSTATUS_SUCCESS;
}
// Reserve declarative and DRIVER_OBJECT names in one namespace.
KSTATUS IoPnpPrepareModule(struct NTL_MODULE* module,
const NTL_DEVICE_DESC* descriptors, uint32_t count,
DRIVER_OBJECT* const* ioDrivers, uint32_t ioCount) {
if (!gPnpStarted || !module || (count && !descriptors) || (ioCount && !ioDrivers))
return NTLSTATUS_INVALID_PARAMETER;
if (count > UINT32_MAX - ioCount) return NTLSTATUS_INVALID_PARAMETER;
PNP_DRIVER* first = (PNP_DRIVER*)0;
PNP_DRIVER* last = (PNP_DRIVER*)0;
for (uint32_t i = 0; i < count + ioCount; ++i) {
PNP_DRIVER* driver = (PNP_DRIVER*)MmAllocatePool(sizeof(*driver));
if (!driver) {
while (first) {
PNP_DRIVER* next = first->Next;
MmFreePool(first);
first = next;
}
return NTLSTATUS_NO_MEMORY;
}
memset(driver, 0, sizeof(*driver));
driver->Module = module;
if (i < count) driver->Descriptor = &descriptors[i];
else driver->IoDriver = ioDrivers[i - count];
if (last) last->Next = driver;
else first = driver;
last = driver;
}
KSTATUS status = NTLSTATUS_SUCCESS;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
if (gPnpShutdown) status = NTLSTATUS_DEVICE_REMOVED;
for (PNP_DRIVER* proposed = first; KSUCCESS(status) && proposed;
proposed = proposed->Next) {
SYS_STRING_VIEW name = proposed->IoDriver ? proposed->IoDriver->Name :
SysSvFromCstr(proposed->Descriptor->Name);
for (PNP_DRIVER* existing = gPnpDrivers; existing; existing = existing->Next) {
SYS_STRING_VIEW oldName = existing->IoDriver ? existing->IoDriver->Name :
SysSvFromCstr(existing->Descriptor->Name);
if (SysSvEqual(name, oldName)) {
status = NTLSTATUS_ALREADY_EXISTS;
break;
}
}
for (PNP_DRIVER* other = proposed->Next; KSUCCESS(status) && other;
other = other->Next) {
SYS_STRING_VIEW otherName = other->IoDriver ? other->IoDriver->Name :
SysSvFromCstr(other->Descriptor->Name);
if (SysSvEqual(name, otherName)) status = NTLSTATUS_ALREADY_EXISTS;
}
}
if (KSUCCESS(status) && first) {
if (gPnpDriversTail) gPnpDriversTail->Next = first;
else gPnpDrivers = first;
gPnpDriversTail = last;
}
KeReleaseSpinlock(&gPnpLock, flags);
if (!KSUCCESS(status)) {
while (first) {
PNP_DRIVER* next = first->Next;
MmFreePool(first);
first = next;
}
}
return status;
}
// Reserved registrations become eligible for binding without allocation.
void IoPnpCommitModule(struct NTL_MODULE* module) {
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
for (PNP_DRIVER* driver = gPnpDrivers; driver; driver = driver->Next)
if (driver->Module == module) driver->Committed = true;
KeReleaseSpinlock(&gPnpLock, flags);
IoPnpWake();
}
// Abort releases reservations before any binding can reference them.
void IoPnpAbortModule(struct NTL_MODULE* module) {
PNP_DRIVER* removed = (PNP_DRIVER*)0;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
PNP_DRIVER** link = &gPnpDrivers;
while (*link) {
PNP_DRIVER* driver = *link;
if (driver->Module != module || driver->Committed) {
link = &driver->Next;
continue;
}
*link = driver->Next;
driver->Next = removed;
removed = driver;
}
gPnpDriversTail = (PNP_DRIVER*)0;
for (PNP_DRIVER* it = gPnpDrivers; it; it = it->Next) gPnpDriversTail = it;
KeReleaseSpinlock(&gPnpLock, flags);
while (removed) {
PNP_DRIVER* next = removed->Next;
MmFreePool(removed);
removed = next;
}
}
KSTATUS IoPnpPhysicalDeviceArrived(DEVICE_OBJECT* pdo) {
if (!pdo || pdo->Type != DeviceObjectPhysical) return NTLSTATUS_INVALID_PARAMETER;
if (!gPnpStarted) return NTLSTATUS_SUCCESS;
PNP_NODE* node = (PNP_NODE*)MmAllocatePool(sizeof(*node));
if (!node) return NTLSTATUS_NO_MEMORY;
memset(node, 0, sizeof(*node));
node->Pdo = pdo;
node->ParentPdo = IoPnpParentPdo(pdo);
IoReferenceDeviceObject(pdo);
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
if (gPnpShutdown) {
KeReleaseSpinlock(&gPnpLock, flags);
IoDereferenceDeviceObject(pdo);
MmFreePool(node);
return NTLSTATUS_DEVICE_REMOVED;
}
if (IoPnpFindNode(pdo)) {
KeReleaseSpinlock(&gPnpLock, flags);
IoDereferenceDeviceObject(pdo);
MmFreePool(node);
return NTLSTATUS_ALREADY_EXISTS;
}
if (gPnpNodesTail) gPnpNodesTail->Next = node;
else gPnpNodes = node;
gPnpNodesTail = node;
++gPnpNodeCount;
KeReleaseSpinlock(&gPnpLock, flags);
return NTLSTATUS_SUCCESS;
}
void IoPnpPhysicalDevicePublished(DEVICE_OBJECT* pdo) {
if (!pdo || !gPnpStarted) return;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
PNP_NODE* node = IoPnpFindNode(pdo);
if (node) node->Published = true;
KeReleaseSpinlock(&gPnpLock, flags);
IoPnpWake();
}
void IoPnpPhysicalDeviceRemoved(DEVICE_OBJECT* pdo) {
if (!pdo || !gPnpStarted) return;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
PNP_NODE* node = IoPnpFindNode(pdo);
if (node) node->Removed = true;
KeReleaseSpinlock(&gPnpLock, flags);
IoPnpWake();
}
KSTATUS IoPnpWaitForIdle(uint64_t timeoutMs) {
if (!gPnpStarted) return NTLSTATUS_INVALID_PARAMETER;
uint64_t deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMs);
for (;;) {
bool idle = true;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
for (PNP_NODE* node = gPnpNodes; node; node = node->Next) {
if (node->State == PnpReserved || node->State == PnpStarting ||
node->State == PnpPending || node->State == PnpCancelling ||
node->State == PnpCleaning ||
node->State == PnpStopping ||
(node->State == PnpFailed && node->Binding) ||
(node->Removed && node->State != PnpPending) ||
(!gPnpShutdown && node->State == PnpUnbound && node->Published &&
IoPnpFindDriver(node->Pdo))) {
idle = false;
break;
}
}
KeReleaseSpinlock(&gPnpLock, flags);
if (idle) return NTLSTATUS_SUCCESS;
uint64_t remaining = KeMonotonicMillisecondsUntil(deadline);
if (!remaining) return NTLSTATUS_TIMEOUT;
uint32_t index = 0;
KSTATUS st = KeWaitForMultipleEvents(&gPnpChanged, 1, false, remaining, &index);
if (st == NTLSTATUS_TIMEOUT) return NTLSTATUS_TIMEOUT;
if (!KSUCCESS(st)) return st;
}
}
KSTATUS IoPnpShutdown(uint64_t timeoutMs) {
if (!gPnpStarted) return NTLSTATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
gPnpShutdown = true;
for (PNP_NODE* node = gPnpNodes; node; node = node->Next)
node->Removed = true;
KeReleaseSpinlock(&gPnpLock, flags);
IoPnpWake();
return IoPnpWaitForIdle(timeoutMs);
}
__declspec(dllexport) void* NtlDeviceContext(NTL_DEVICE* device) {
return device ? device->Context : (void*)0;
}
__declspec(dllexport) const char* NtlDeviceId(NTL_DEVICE* device, uint32_t index) {
if (!device || index >= device->Node->Pdo->u.Pdo.IdCount) return (const char*)0;
return device->Node->Pdo->u.Pdo.Ids[index].Id.Data;
}
__declspec(dllexport) NTL_BUS_TYPE NtlDeviceBus(NTL_DEVICE* device) {
return device ? (NTL_BUS_TYPE)device->Node->Pdo->u.Pdo.BusType : (NTL_BUS_TYPE)0;
}
__declspec(dllexport) KSTATUS NtlDeviceCompleteStart(NTL_DEVICE* device, KSTATUS result) {
if (!device || result == NTLSTATUS_PENDING || !device->Node) return NTLSTATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&gPnpLock);
PNP_NODE* node = device->Node;
if (node->Binding != device || device->CompletionSet ||
(node->State != PnpStarting && node->State != PnpPending &&
node->State != PnpCancelling)) {
KeReleaseSpinlock(&gPnpLock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
device->CompletionSet = true;
device->CompletionStatus = result;
if (node->State == PnpPending) {
node->State = KSUCCESS(result) ? PnpStarted : PnpFailed;
}
KeReleaseSpinlock(&gPnpLock, flags);
IoPnpWake();
return NTLSTATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS NtlDeviceOwnResource(NTL_DEVICE* device, void* context,
NTL_DEVICE_RESOURCE_RELEASE release,
NTL_DEVICE_RESOURCE** out) {
if (!device || !release || !out) return NTLSTATUS_INVALID_PARAMETER;
*out = (NTL_DEVICE_RESOURCE*)0;
NTL_DEVICE_RESOURCE* resource = (NTL_DEVICE_RESOURCE*)MmAllocatePool(sizeof(*resource));
if (!resource) return NTLSTATUS_NO_MEMORY;
resource->Owner = device;
resource->Context = context;
resource->Release = release;
uintptr_t flags = KeAcquireSpinlock(&device->Lock);
if (device->Closing) {
KeReleaseSpinlock(&device->Lock, flags);
MmFreePool(resource);
return NTLSTATUS_OBJECT_DEAD;
}
resource->Next = device->Resources;
device->Resources = resource;
KeReleaseSpinlock(&device->Lock, flags);
*out = resource;
return NTLSTATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS NtlDeviceReleaseResource(NTL_DEVICE_RESOURCE* resource) {
if (!resource || !resource->Owner) return NTLSTATUS_INVALID_PARAMETER;
NTL_DEVICE* device = resource->Owner;
uintptr_t flags = KeAcquireSpinlock(&device->Lock);
NTL_DEVICE_RESOURCE** link = &device->Resources;
while (*link && *link != resource) link = &(*link)->Next;
if (*link != resource) {
KeReleaseSpinlock(&device->Lock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
*link = resource->Next;
resource->Owner = (NTL_DEVICE*)0;
KeReleaseSpinlock(&device->Lock, flags);
resource->Release(resource->Context);
MmFreePool(resource);
return NTLSTATUS_SUCCESS;
}