KD System Rewrite:
[reactos.git] / reactos / ntoskrnl / ke / i386 / fpu.c
1 /* $Id$
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/ke/i386/fpu.c
6 * PURPOSE: Handles the FPU
7 *
8 * PROGRAMMERS: David Welch (welch@mcmail.com)
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #include <roscfg.h>
14 #include <ntoskrnl.h>
15 #define NDEBUG
16 #include <internal/debug.h>
17
18 /* DEFINES *******************************************************************/
19
20 /* x87 Status Word exception flags */
21 #define X87_SW_IE (1<<0) /* Invalid Operation */
22 #define X87_SW_DE (1<<1) /* Denormalized Operand */
23 #define X87_SW_ZE (1<<2) /* Zero Devide */
24 #define X87_SW_OE (1<<3) /* Overflow */
25 #define X87_SW_UE (1<<4) /* Underflow */
26 #define X87_SW_PE (1<<5) /* Precision */
27 #define X87_SW_SE (1<<6) /* Stack Fault */
28
29 #define X87_SW_ES (1<<7) /* Error Summary */
30
31 /* MXCSR exception flags */
32 #define MXCSR_IE (1<<0) /* Invalid Operation */
33 #define MXCSR_DE (1<<1) /* Denormalized Operand */
34 #define MXCSR_ZE (1<<2) /* Zero Devide */
35 #define MXCSR_OE (1<<3) /* Overflow */
36 #define MXCSR_UE (1<<4) /* Underflow */
37 #define MXCSR_PE (1<<5) /* Precision */
38 #define MXCSR_DAZ (1<<6) /* Denormals Are Zeros (P4 only) */
39
40 /* GLOBALS *******************************************************************/
41
42 ULONG HardwareMathSupport = 0;
43 static ULONG MxcsrFeatureMask = 0, XmmSupport = 0;
44 ULONG FxsrSupport = 0; /* used by Ki386ContextSwitch for SMP */
45
46 /* FUNCTIONS *****************************************************************/
47
48 STATIC USHORT
49 KiTagWordFnsaveToFxsave(USHORT TagWord)
50 {
51 INT tmp;
52
53 /*
54 * Converts the tag-word. 11 (Empty) is converted into 0, everything else into 1
55 */
56 tmp = ~TagWord; /* Empty is now 00, any 2 bits containing 1 mean valid */
57 tmp = (tmp | (tmp >> 1)) & 0x5555; /* 0V0V0V0V0V0V0V0V */
58 tmp = (tmp | (tmp >> 1)) & 0x3333; /* 00VV00VV00VV00VV */
59 tmp = (tmp | (tmp >> 2)) & 0x0f0f; /* 0000VVVV0000VVVV */
60 tmp = (tmp | (tmp >> 4)) & 0x00ff; /* 00000000VVVVVVVV */
61
62 return tmp;
63 }
64
65 STATIC USHORT
66 KiTagWordFxsaveToFnsave(PFXSAVE_FORMAT FxSave)
67 {
68 USHORT TagWord = 0;
69 UCHAR Tag;
70 INT i;
71 struct FPREG { USHORT Significand[4]; USHORT Exponent; } *FpReg;
72
73 for (i = 0; i < 8; i++)
74 {
75 if (FxSave->TagWord & (1 << i)) /* valid */
76 {
77 FpReg = (struct FPREG *)(FxSave->RegisterArea + (i * 16));
78 switch (FpReg->Exponent & 0x00007fff)
79 {
80 case 0x0000:
81 if (FpReg->Significand[0] == 0 && FpReg->Significand[1] == 0 &&
82 FpReg->Significand[2] == 0 && FpReg->Significand[3] == 0)
83 {
84 Tag = 1; /* Zero */
85 }
86 else
87 {
88 Tag = 2; /* Special */
89 }
90 break;
91
92 case 0x7fff:
93 Tag = 2; /* Special */
94 break;
95
96 default:
97 if (FpReg->Significand[3] & 0x00008000)
98 {
99 Tag = 0; /* Valid */
100 }
101 else
102 {
103 Tag = 2; /* Special */
104 }
105 break;
106 }
107 }
108 else /* empty */
109 {
110 Tag = 3;
111 }
112 TagWord |= Tag << (i * 2);
113 }
114
115 return TagWord;
116 }
117
118 STATIC VOID
119 KiFnsaveToFxsaveFormat(PFXSAVE_FORMAT FxSave, CONST PFNSAVE_FORMAT FnSave)
120 {
121 INT i;
122
123 FxSave->ControlWord = (USHORT)FnSave->ControlWord;
124 FxSave->StatusWord = (USHORT)FnSave->StatusWord;
125 FxSave->TagWord = KiTagWordFnsaveToFxsave((USHORT)FnSave->TagWord);
126 FxSave->ErrorOpcode = (USHORT)(FnSave->ErrorSelector >> 16);
127 FxSave->ErrorOffset = FnSave->ErrorOffset;
128 FxSave->ErrorSelector = FnSave->ErrorSelector & 0x0000ffff;
129 FxSave->DataOffset = FnSave->DataOffset;
130 FxSave->DataSelector = FnSave->DataSelector & 0x0000ffff;
131 if (XmmSupport)
132 FxSave->MXCsr = 0x00001f80 & MxcsrFeatureMask;
133 else
134 FxSave->MXCsr = 0;
135 FxSave->MXCsrMask = MxcsrFeatureMask;
136 memset(FxSave->Reserved3, 0, sizeof(FxSave->Reserved3) +
137 sizeof(FxSave->Reserved4)); /* XXX - doesnt zero Align16Byte because
138 Context->ExtendedRegisters is only 512 bytes, not 520 */
139 for (i = 0; i < 8; i++)
140 {
141 memcpy(FxSave->RegisterArea + (i * 16), FnSave->RegisterArea + (i * 10), 10);
142 memset(FxSave->RegisterArea + (i * 16) + 10, 0, 6);
143 }
144 }
145
146 STATIC VOID
147 KiFxsaveToFnsaveFormat(PFNSAVE_FORMAT FnSave, CONST PFXSAVE_FORMAT FxSave)
148 {
149 INT i;
150
151 FnSave->ControlWord = 0xffff0000 | FxSave->ControlWord;
152 FnSave->StatusWord = 0xffff0000 | FxSave->StatusWord;
153 FnSave->TagWord = 0xffff0000 | KiTagWordFxsaveToFnsave(FxSave);
154 FnSave->ErrorOffset = FxSave->ErrorOffset;
155 FnSave->ErrorSelector = FxSave->ErrorSelector & 0x0000ffff;
156 FnSave->ErrorSelector |= FxSave->ErrorOpcode << 16;
157 FnSave->DataOffset = FxSave->DataOffset;
158 FnSave->DataSelector = FxSave->DataSelector | 0xffff0000;
159 for (i = 0; i < 8; i++)
160 {
161 memcpy(FnSave->RegisterArea + (i * 10), FxSave->RegisterArea + (i * 16), 10);
162 }
163 }
164
165 VOID
166 KiFloatingSaveAreaToFxSaveArea(PFX_SAVE_AREA FxSaveArea, CONST FLOATING_SAVE_AREA *FloatingSaveArea)
167 {
168 if (FxsrSupport)
169 {
170 KiFnsaveToFxsaveFormat(&FxSaveArea->U.FxArea, (PFNSAVE_FORMAT)FloatingSaveArea);
171 }
172 else
173 {
174 memcpy(&FxSaveArea->U.FnArea, FloatingSaveArea, sizeof(FxSaveArea->U.FnArea));
175 }
176 FxSaveArea->NpxSavedCpu = 0;
177 FxSaveArea->Cr0NpxState = FloatingSaveArea->Cr0NpxState;
178 }
179
180 BOOL
181 KiContextToFxSaveArea(PFX_SAVE_AREA FxSaveArea, PCONTEXT Context)
182 {
183 BOOL FpuContextChanged = FALSE;
184
185 /* First of all convert the FLOATING_SAVE_AREA into the FX_SAVE_AREA */
186 if ((Context->ContextFlags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT)
187 {
188 KiFloatingSaveAreaToFxSaveArea(FxSaveArea, &Context->FloatSave);
189 FpuContextChanged = TRUE;
190 }
191
192 /* Now merge the FX_SAVE_AREA from the context with the destination area */
193 if ((Context->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS)
194 {
195 if (FxsrSupport)
196 {
197 PFXSAVE_FORMAT src = (PFXSAVE_FORMAT)Context->ExtendedRegisters;
198 PFXSAVE_FORMAT dst = &FxSaveArea->U.FxArea;
199 dst->MXCsr = src->MXCsr & MxcsrFeatureMask;
200 memcpy(dst->Reserved3, src->Reserved3,
201 sizeof(src->Reserved3) + sizeof(src->Reserved4));
202
203 if ((Context->ContextFlags & CONTEXT_FLOATING_POINT) != CONTEXT_FLOATING_POINT)
204 {
205 dst->ControlWord = src->ControlWord;
206 dst->StatusWord = src->StatusWord;
207 dst->TagWord = src->TagWord;
208 dst->ErrorOpcode = src->ErrorOpcode;
209 dst->ErrorOffset = src->ErrorOffset;
210 dst->ErrorSelector = src->ErrorSelector;
211 dst->DataOffset = src->DataOffset;
212 dst->DataSelector = src->DataSelector;
213 memcpy(dst->RegisterArea, src->RegisterArea, sizeof(src->RegisterArea));
214
215 FxSaveArea->NpxSavedCpu = 0;
216 FxSaveArea->Cr0NpxState = 0;
217 }
218 FpuContextChanged = TRUE;
219 }
220 }
221
222 return FpuContextChanged;
223 }
224
225 VOID INIT_FUNCTION
226 KiCheckFPU(VOID)
227 {
228 unsigned short int status;
229 int cr0;
230 ULONG Flags;
231 PKPRCB Prcb = KeGetCurrentPrcb();
232
233 Ke386SaveFlags(Flags);
234 Ke386DisableInterrupts();
235
236 HardwareMathSupport = 0;
237 FxsrSupport = 0;
238 XmmSupport = 0;
239
240 cr0 = Ke386GetCr0();
241 cr0 |= X86_CR0_NE | X86_CR0_MP;
242 cr0 &= ~(X86_CR0_EM | X86_CR0_TS);
243 Ke386SetCr0(cr0);
244
245 #if defined(__GNUC__)
246 asm volatile("fninit\n\t");
247 asm volatile("fstsw %0\n\t" : "=a" (status));
248 #elif defined(_MSC_VER)
249 __asm
250 {
251 fninit;
252 fstsw status
253 }
254 #else
255 #error Unknown compiler for inline assembler
256 #endif
257
258 if (status != 0)
259 {
260 /* Set the EM flag in CR0 so any FPU instructions cause a trap. */
261 Ke386SetCr0(Ke386GetCr0() | X86_CR0_EM);
262 Ke386RestoreFlags(Flags);
263 return;
264 }
265
266 /* fsetpm for i287, ignored by i387 */
267 #if defined(__GNUC__)
268 asm volatile(".byte 0xDB, 0xE4\n\t");
269 #elif defined(_MSC_VER)
270 __asm _emit 0xDB __asm _emit 0xe4
271 #else
272 #error Unknown compiler for inline assembler
273 #endif
274
275 HardwareMathSupport = 1;
276
277 /* check for and enable MMX/SSE support if possible */
278 if ((Prcb->FeatureBits & X86_FEATURE_FXSR) != 0)
279 {
280 BYTE DummyArea[sizeof(FX_SAVE_AREA) + 15];
281 PFX_SAVE_AREA FxSaveArea;
282
283 /* enable FXSR */
284 FxsrSupport = 1;
285
286 /* we need a 16 byte aligned FX_SAVE_AREA */
287 FxSaveArea = (PFX_SAVE_AREA)DummyArea;
288 if ((ULONG_PTR)FxSaveArea & 0x0f)
289 {
290 FxSaveArea = (PFX_SAVE_AREA)(((ULONG_PTR)FxSaveArea + 0x10) & (~0x0f));
291 }
292
293 Ke386SetCr4(Ke386GetCr4() | X86_CR4_OSFXSR);
294 memset(&FxSaveArea->U.FxArea, 0, sizeof(FxSaveArea->U.FxArea));
295 asm volatile("fxsave %0" : : "m"(FxSaveArea->U.FxArea));
296 MxcsrFeatureMask = FxSaveArea->U.FxArea.MXCsrMask;
297 if (MxcsrFeatureMask == 0)
298 {
299 MxcsrFeatureMask = 0x0000ffbf;
300 }
301 }
302 /* FIXME: Check for SSE3 in Ke386CpuidFlags2! */
303 if (Prcb->FeatureBits & (X86_FEATURE_SSE | X86_FEATURE_SSE2))
304 {
305 Ke386SetCr4(Ke386GetCr4() | X86_CR4_OSXMMEXCPT);
306
307 /* enable SSE */
308 XmmSupport = 1;
309 }
310
311 Ke386SetCr0(Ke386GetCr0() | X86_CR0_TS);
312 Ke386RestoreFlags(Flags);
313 }
314
315 /* This is a rather naive implementation of Ke(Save/Restore)FloatingPointState
316 which will not work for WDM drivers. Please feel free to improve */
317
318 #define FPU_STATE_SIZE 108
319
320 NTSTATUS STDCALL
321 KeSaveFloatingPointState(OUT PKFLOATING_SAVE Save)
322 {
323 char *FpState;
324
325 ASSERT_IRQL(DISPATCH_LEVEL); /* FIXME: is this removed for non-debug builds? I hope not! */
326
327 /* check if we are doing software emulation */
328 if (!HardwareMathSupport)
329 {
330 return STATUS_ILLEGAL_FLOAT_CONTEXT;
331 }
332
333 FpState = ExAllocatePool(PagedPool, FPU_STATE_SIZE);
334 if (NULL == FpState)
335 {
336 return STATUS_INSUFFICIENT_RESOURCES;
337 }
338 *((PVOID *) Save) = FpState;
339
340 #if defined(__GNUC__)
341 asm volatile("fsave %0\n\t" : "=m" (*FpState));
342 #elif defined(_MSC_VER)
343 __asm mov eax, FpState;
344 __asm fsave [eax];
345 #else
346 #error Unknown compiler for inline assembler
347 #endif
348
349 KeGetCurrentThread()->NpxIrql = KeGetCurrentIrql();
350
351 return STATUS_SUCCESS;
352 }
353
354 NTSTATUS STDCALL
355 KeRestoreFloatingPointState(IN PKFLOATING_SAVE Save)
356 {
357 char *FpState = *((PVOID *) Save);
358
359 if (KeGetCurrentThread()->NpxIrql != KeGetCurrentIrql())
360 {
361 KEBUGCHECK(UNDEFINED_BUG_CODE);
362 }
363
364 #if defined(__GNUC__)
365 __asm__("frstor %0\n\t" : "=m" (*FpState));
366 #elif defined(_MSC_VER)
367 __asm mov eax, FpState;
368 __asm frstor [eax];
369 #else
370 #error Unknown compiler for inline assembler
371 #endif
372
373 ExFreePool(FpState);
374
375 return STATUS_SUCCESS;
376 }
377
378 NTSTATUS
379 KiHandleFpuFault(PKTRAP_FRAME Tf, ULONG ExceptionNr)
380 {
381 if (ExceptionNr == 7) /* device not present */
382 {
383 BOOL FpuInitialized = FALSE;
384 unsigned int cr0 = Ke386GetCr0();
385 PKTHREAD CurrentThread;
386 PFX_SAVE_AREA FxSaveArea;
387 KIRQL oldIrql;
388 #ifndef CONFIG_SMP
389 PKTHREAD NpxThread;
390 #endif
391
392 (void) cr0;
393 ASSERT((cr0 & X86_CR0_TS) == X86_CR0_TS);
394 ASSERT((Tf->Eflags & X86_EFLAGS_VM) == 0);
395 ASSERT((cr0 & X86_CR0_EM) == 0);
396
397 /* disable scheduler, clear TS in cr0 */
398 ASSERT_IRQL(DISPATCH_LEVEL);
399 KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
400 asm volatile("clts");
401
402 CurrentThread = KeGetCurrentThread();
403 #ifndef CONFIG_SMP
404 NpxThread = KeGetCurrentPrcb()->NpxThread;
405 #endif
406
407 ASSERT(CurrentThread != NULL);
408 DPRINT("Device not present exception happened! (Cr0 = 0x%x, NpxState = 0x%x)\n", cr0, CurrentThread->NpxState);
409
410 #ifndef CONFIG_SMP
411 /* check if the current thread already owns the FPU */
412 if (NpxThread != CurrentThread) /* FIXME: maybe this could be an assertation */
413 {
414 /* save the FPU state into the owner's save area */
415 if (NpxThread != NULL)
416 {
417 KeGetCurrentPrcb()->NpxThread = NULL;
418 FxSaveArea = (PFX_SAVE_AREA)((char *)NpxThread->InitialStack - sizeof (FX_SAVE_AREA));
419 /* the fnsave might raise a delayed #MF exception */
420 if (FxsrSupport)
421 {
422 asm volatile("fxsave %0" : : "m"(FxSaveArea->U.FxArea));
423 }
424 else
425 {
426 asm volatile("fnsave %0" : : "m"(FxSaveArea->U.FnArea));
427 FpuInitialized = TRUE;
428 }
429 NpxThread->NpxState = NPX_STATE_VALID;
430 }
431 #endif /* !CONFIG_SMP */
432
433 /* restore the state of the current thread */
434 ASSERT((CurrentThread->NpxState & NPX_STATE_DIRTY) == 0);
435 FxSaveArea = (PFX_SAVE_AREA)((char *)CurrentThread->InitialStack - sizeof (FX_SAVE_AREA));
436 if (CurrentThread->NpxState & NPX_STATE_VALID)
437 {
438 if (FxsrSupport)
439 {
440 FxSaveArea->U.FxArea.MXCsr &= MxcsrFeatureMask;
441 asm volatile("fxrstor %0" : : "m"(FxSaveArea->U.FxArea));
442 }
443 else
444 {
445 asm volatile("frstor %0" : : "m"(FxSaveArea->U.FnArea));
446 }
447 }
448 else /* NpxState & NPX_STATE_INVALID */
449 {
450 DPRINT("Setting up clean FPU state\n");
451 if (FxsrSupport)
452 {
453 memset(&FxSaveArea->U.FxArea, 0, sizeof(FxSaveArea->U.FxArea));
454 FxSaveArea->U.FxArea.ControlWord = 0x037f;
455 if (XmmSupport)
456 {
457 FxSaveArea->U.FxArea.MXCsr = 0x00001f80 & MxcsrFeatureMask;
458 }
459 asm volatile("fxrstor %0" : : "m"(FxSaveArea->U.FxArea));
460 }
461 else if (!FpuInitialized)
462 {
463 asm volatile("finit");
464 }
465 }
466 KeGetCurrentPrcb()->NpxThread = CurrentThread;
467 #ifndef CONFIG_SMP
468 }
469 #endif
470
471 CurrentThread->NpxState |= NPX_STATE_DIRTY;
472 KeLowerIrql(oldIrql);
473 DPRINT("Device not present exception handled!\n");
474
475 return STATUS_SUCCESS;
476 }
477 else /* ExceptionNr == 16 || ExceptionNr == 19 */
478 {
479 EXCEPTION_RECORD Er;
480 UCHAR DummyContext[sizeof(CONTEXT) + 16];
481 PCONTEXT Context;
482 KPROCESSOR_MODE PreviousMode;
483 PKTHREAD CurrentThread, NpxThread;
484 KIRQL oldIrql;
485
486 ASSERT(ExceptionNr == 16 || ExceptionNr == 19); /* math fault or XMM fault*/
487
488 KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
489
490 NpxThread = KeGetCurrentPrcb()->NpxThread;
491 CurrentThread = KeGetCurrentThread();
492 if (NpxThread == NULL)
493 {
494 KeLowerIrql(oldIrql);
495 DPRINT1("!!! Math/Xmm fault ignored! (NpxThread == NULL)\n");
496 return STATUS_SUCCESS;
497 }
498
499 PreviousMode = ((Tf->Cs & 0xffff) == USER_CS) ? (UserMode) : (KernelMode);
500 DPRINT("Math/Xmm fault happened! (PreviousMode = %s)\n",
501 (PreviousMode == UserMode) ? ("UserMode") : ("KernelMode"));
502
503 ASSERT(NpxThread == CurrentThread); /* FIXME: Is not always true I think */
504
505 /* For fxsave we have to align Context->ExtendedRegisters on 16 bytes */
506 Context = (PCONTEXT)DummyContext;
507 Context = (PCONTEXT)((ULONG_PTR)Context + 0x10 - ((ULONG_PTR)Context->ExtendedRegisters & 0x0f));
508
509 /* Get FPU/XMM state */
510 Context->FloatSave.Cr0NpxState = 0;
511 if (FxsrSupport)
512 {
513 PFXSAVE_FORMAT FxSave = (PFXSAVE_FORMAT)Context->ExtendedRegisters;
514 FxSave->MXCsrMask = MxcsrFeatureMask;
515 memset(FxSave->RegisterArea, 0, sizeof(FxSave->RegisterArea) +
516 sizeof(FxSave->Reserved3) + sizeof(FxSave->Reserved4));
517 asm volatile("fxsave %0" : : "m"(*FxSave));
518 KeLowerIrql(oldIrql);
519 KiFxsaveToFnsaveFormat((PFNSAVE_FORMAT)&Context->FloatSave, FxSave);
520 }
521 else
522 {
523 PFNSAVE_FORMAT FnSave = (PFNSAVE_FORMAT)&Context->FloatSave;
524 asm volatile("fnsave %0" : : "m"(*FnSave));
525 KeLowerIrql(oldIrql);
526 KiFnsaveToFxsaveFormat((PFXSAVE_FORMAT)Context->ExtendedRegisters, FnSave);
527 }
528
529 /* Fill the rest of the context */
530 Context->ContextFlags = CONTEXT_FULL;
531 KeTrapFrameToContext(Tf, Context);
532 Context->ContextFlags |= CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS;
533
534 /* Determine exception code */
535 if (ExceptionNr == 16)
536 {
537 USHORT FpuStatusWord = Context->FloatSave.StatusWord & 0xffff;
538 DPRINT("FpuStatusWord = 0x%04x\n", FpuStatusWord);
539
540 if (FpuStatusWord & X87_SW_IE)
541 Er.ExceptionCode = STATUS_FLOAT_INVALID_OPERATION;
542 else if (FpuStatusWord & X87_SW_DE)
543 Er.ExceptionCode = STATUS_FLOAT_DENORMAL_OPERAND;
544 else if (FpuStatusWord & X87_SW_ZE)
545 Er.ExceptionCode = STATUS_FLOAT_DIVIDE_BY_ZERO;
546 else if (FpuStatusWord & X87_SW_OE)
547 Er.ExceptionCode = STATUS_FLOAT_OVERFLOW;
548 else if (FpuStatusWord & X87_SW_UE)
549 Er.ExceptionCode = STATUS_FLOAT_UNDERFLOW;
550 else if (FpuStatusWord & X87_SW_PE)
551 Er.ExceptionCode = STATUS_FLOAT_INEXACT_RESULT;
552 else if (FpuStatusWord & X87_SW_SE)
553 Er.ExceptionCode = STATUS_FLOAT_STACK_CHECK;
554 else
555 ASSERT(0); /* not reached */
556 /* FIXME: is this the right way to get the correct EIP of the faulting instruction? */
557 Er.ExceptionAddress = (PVOID)Context->FloatSave.ErrorOffset;
558 }
559 else /* ExceptionNr == 19 */
560 {
561 /* FIXME: When should we use STATUS_FLOAT_MULTIPLE_FAULTS? */
562 Er.ExceptionCode = STATUS_FLOAT_MULTIPLE_TRAPS;
563 Er.ExceptionAddress = (PVOID)Tf->Eip;
564 }
565
566 Er.ExceptionFlags = 0;
567 Er.ExceptionRecord = NULL;
568 Er.NumberParameters = 0;
569
570 /* Dispatch exception */
571 DPRINT("Dispatching exception (ExceptionCode = 0x%08x)\n", Er.ExceptionCode);
572 KiDispatchException(&Er, Context, Tf, PreviousMode, TRUE);
573
574 DPRINT("Math-fault handled!\n");
575 return STATUS_SUCCESS;
576 }
577
578 return STATUS_UNSUCCESSFUL;
579 }
580