2 * PROJECT: ReactOS Kernel
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: ntoskrnl/mm/ARM3/vadnode.c
5 * PURPOSE: ARM Memory Manager VAD Node Algorithms
6 * PROGRAMMERS: ReactOS Portable Systems Group
7 * Timo Kreuzer (timo.kreuzer@reactos.org)
10 /* INCLUDES *******************************************************************/
16 #define MODULE_INVOLVED_IN_ARM3
17 #include "../ARM3/miarm.h"
19 /* Include Mm version of AVL support */
20 #include "../ARM3/miavl.h"
21 #include "../../../lib/rtl/avlsupp.c"
23 /* GLOBALS ********************************************************************/
25 CHAR MmReadWrite
[32] =
27 MM_NO_ACCESS_ALLOWED
, MM_READ_ONLY_ALLOWED
, MM_READ_ONLY_ALLOWED
,
28 MM_READ_ONLY_ALLOWED
, MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
29 MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
31 MM_NO_ACCESS_ALLOWED
, MM_READ_ONLY_ALLOWED
, MM_READ_ONLY_ALLOWED
,
32 MM_READ_ONLY_ALLOWED
, MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
33 MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
35 MM_NO_ACCESS_ALLOWED
, MM_READ_ONLY_ALLOWED
, MM_READ_ONLY_ALLOWED
,
36 MM_READ_ONLY_ALLOWED
, MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
37 MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
39 MM_NO_ACCESS_ALLOWED
, MM_READ_ONLY_ALLOWED
, MM_READ_ONLY_ALLOWED
,
40 MM_READ_ONLY_ALLOWED
, MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
41 MM_READ_WRITE_ALLOWED
, MM_READ_WRITE_ALLOWED
,
44 /* FUNCTIONS ******************************************************************/
48 MiLocateAddress(IN PVOID VirtualAddress
)
52 PMM_AVL_TABLE Table
= &PsGetCurrentProcess()->VadRoot
;
53 TABLE_SEARCH_RESULT SearchResult
;
55 /* Start with the the hint */
56 FoundVad
= (PMMVAD
)Table
->NodeHint
;
57 if (!FoundVad
) return NULL
;
59 /* Check if this VPN is in the hint, if so, use it */
60 Vpn
= (ULONG_PTR
)VirtualAddress
>> PAGE_SHIFT
;
61 if ((Vpn
>= FoundVad
->StartingVpn
) && (Vpn
<= FoundVad
->EndingVpn
)) return FoundVad
;
63 /* VAD hint didn't work, go look for it */
64 SearchResult
= RtlpFindAvlTableNodeOrParent(Table
,
66 (PMMADDRESS_NODE
*)&FoundVad
);
67 if (SearchResult
!= TableFoundNode
) return NULL
;
69 /* We found it, update the hint */
70 ASSERT(FoundVad
!= NULL
);
71 ASSERT((Vpn
>= FoundVad
->StartingVpn
) && (Vpn
<= FoundVad
->EndingVpn
));
72 Table
->NodeHint
= FoundVad
;
78 MiCheckForConflictingNode(IN ULONG_PTR StartVpn
,
80 IN PMM_AVL_TABLE Table
,
81 OUT PMMADDRESS_NODE
*NodeOrParent
)
83 PMMADDRESS_NODE ParentNode
, CurrentNode
;
85 /* If the tree is empty, there is no conflict */
86 if (Table
->NumberGenericTableElements
== 0) return TableEmptyTree
;
88 /* Start looping from the root node */
89 CurrentNode
= RtlRightChildAvl(&Table
->BalancedRoot
);
90 ASSERT(CurrentNode
!= NULL
);
93 ParentNode
= CurrentNode
;
95 /* This address comes after */
96 if (StartVpn
> CurrentNode
->EndingVpn
)
98 /* Keep searching on the right */
99 CurrentNode
= RtlRightChildAvl(CurrentNode
);
101 else if (EndVpn
< CurrentNode
->StartingVpn
)
103 /* This address ends before the node starts, search on the left */
104 CurrentNode
= RtlLeftChildAvl(CurrentNode
);
108 /* This address is part of this node, return it */
109 *NodeOrParent
= ParentNode
;
110 return TableFoundNode
;
114 /* There is no more child, save the current node as parent */
115 *NodeOrParent
= ParentNode
;
116 if (StartVpn
> ParentNode
->EndingVpn
)
118 return TableInsertAsRight
;
122 return TableInsertAsLeft
;
128 MiInsertNode(IN PMM_AVL_TABLE Table
,
129 IN PMMADDRESS_NODE NewNode
,
130 IN PMMADDRESS_NODE Parent
,
131 IN TABLE_SEARCH_RESULT Result
)
135 /* Insert it into the tree */
136 RtlpInsertAvlTreeNode(Table
, NewNode
, Parent
, Result
);
138 /* Now insert an ARM3 MEMORY_AREA for this node, unless the insert was already from the MEMORY_AREA code */
139 Vad
= (PMMVAD_LONG
)NewNode
;
140 if (Vad
->u
.VadFlags
.Spare
== 0)
143 PMEMORY_AREA MemoryArea
;
145 PEPROCESS Process
= CONTAINING_RECORD(Table
, EPROCESS
, VadRoot
);
146 PVOID AllocatedBase
= (PVOID
)(Vad
->StartingVpn
<< PAGE_SHIFT
);
148 Size
= ((Vad
->EndingVpn
+ 1) - Vad
->StartingVpn
) << PAGE_SHIFT
;
149 Status
= MmCreateMemoryArea(&Process
->Vm
,
150 MEMORY_AREA_OWNED_BY_ARM3
,
158 ASSERT(NT_SUCCESS(Status
));
160 /* Check if this is VM VAD */
161 if (Vad
->ControlArea
== NULL
)
163 /* We store the reactos MEMORY_AREA here */
164 Vad
->FirstPrototypePte
= (PMMPTE
)MemoryArea
;
168 /* This is a section VAD. Store the MAREA here for now */
169 ASSERT(Vad
->u4
.Banked
== (PVOID
)0xDEADBABE);
170 Vad
->u4
.Banked
= (PVOID
)MemoryArea
;
177 MiInsertVad(IN PMMVAD Vad
,
178 IN PEPROCESS Process
)
180 TABLE_SEARCH_RESULT Result
;
181 PMMADDRESS_NODE Parent
= NULL
;
183 /* Validate the VAD and set it as the current hint */
184 ASSERT(Vad
->EndingVpn
>= Vad
->StartingVpn
);
185 Process
->VadRoot
.NodeHint
= Vad
;
187 /* Find the parent VAD and where this child should be inserted */
188 Result
= RtlpFindAvlTableNodeOrParent(&Process
->VadRoot
, (PVOID
)Vad
->StartingVpn
, &Parent
);
189 ASSERT(Result
!= TableFoundNode
);
190 ASSERT((Parent
!= NULL
) || (Result
== TableEmptyTree
));
192 /* Do the actual insert operation */
193 MiInsertNode(&Process
->VadRoot
, (PVOID
)Vad
, Parent
, Result
);
198 MiInsertBasedSection(IN PSECTION Section
)
200 TABLE_SEARCH_RESULT Result
;
201 PMMADDRESS_NODE Parent
= NULL
;
202 ASSERT(Section
->Address
.EndingVpn
>= Section
->Address
.StartingVpn
);
204 /* Find the parent VAD and where this child should be inserted */
205 Result
= RtlpFindAvlTableNodeOrParent(&MmSectionBasedRoot
, (PVOID
)Section
->Address
.StartingVpn
, &Parent
);
206 ASSERT(Result
!= TableFoundNode
);
207 ASSERT((Parent
!= NULL
) || (Result
== TableEmptyTree
));
208 MiInsertNode(&MmSectionBasedRoot
, &Section
->Address
, Parent
, Result
);
213 MiRemoveNode(IN PMMADDRESS_NODE Node
,
214 IN PMM_AVL_TABLE Table
)
218 /* Call the AVL code */
219 RtlpDeleteAvlTreeNode(Table
, Node
);
221 /* Decrease element count */
222 Table
->NumberGenericTableElements
--;
224 /* Check if this node was the hint */
225 if (Table
->NodeHint
== Node
)
227 /* Get a new hint, unless we're empty now, in which case nothing */
228 if (!Table
->NumberGenericTableElements
) Table
->NodeHint
= NULL
;
229 else Table
->NodeHint
= Table
->BalancedRoot
.RightChild
;
232 /* Free the node from ReactOS view as well */
233 Vad
= (PMMVAD_LONG
)Node
;
234 if (Vad
->u
.VadFlags
.Spare
== 0)
236 PMEMORY_AREA MemoryArea
;
239 /* Check if this is VM VAD */
240 if (Vad
->ControlArea
== NULL
)
242 /* We store the ReactOS MEMORY_AREA here */
243 MemoryArea
= (PMEMORY_AREA
)Vad
->FirstPrototypePte
;
247 /* This is a section VAD. We store the ReactOS MEMORY_AREA here */
248 MemoryArea
= (PMEMORY_AREA
)Vad
->u4
.Banked
;
251 /* Make sure one actually still exists */
254 /* Make sure we have not already freed it */
255 ASSERT(MemoryArea
!= (PVOID
)0xDEADBAB1);
257 /* Get the process */
258 Process
= CONTAINING_RECORD(Table
, EPROCESS
, VadRoot
);
260 /* We only create fake memory-areas for ARM3 VADs */
261 ASSERT(MemoryArea
->Type
== MEMORY_AREA_OWNED_BY_ARM3
);
262 ASSERT(MemoryArea
->Vad
== NULL
);
265 MmFreeMemoryArea(&Process
->Vm
, MemoryArea
, NULL
, NULL
);
267 /* Check if this is VM VAD */
268 if (Vad
->ControlArea
== NULL
)
270 /* Delete the pointer to it */
271 Vad
->FirstPrototypePte
= (PVOID
)0xDEADBAB1;
275 /* Delete the pointer to it */
276 Vad
->u4
.Banked
= (PVOID
)0xDEADBAB1;
284 MiGetPreviousNode(IN PMMADDRESS_NODE Node
)
286 PMMADDRESS_NODE Parent
;
288 /* Get the left child */
289 if (RtlLeftChildAvl(Node
))
291 /* Get right-most child */
292 Node
= RtlLeftChildAvl(Node
);
293 while (RtlRightChildAvl(Node
)) Node
= RtlRightChildAvl(Node
);
297 Parent
= RtlParentAvl(Node
);
298 ASSERT(Parent
!= NULL
);
299 while (Parent
!= Node
)
301 /* The parent should be a right child, return the real predecessor */
302 if (RtlIsRightChildAvl(Node
))
304 /* Return it unless it's the root */
305 if (Parent
== RtlParentAvl(Parent
)) Parent
= NULL
;
309 /* Keep lopping until we find our parent */
311 Parent
= RtlParentAvl(Node
);
320 MiGetNextNode(IN PMMADDRESS_NODE Node
)
322 PMMADDRESS_NODE Parent
;
324 /* Get the right child */
325 if (RtlRightChildAvl(Node
))
327 /* Get left-most child */
328 Node
= RtlRightChildAvl(Node
);
329 while (RtlLeftChildAvl(Node
)) Node
= RtlLeftChildAvl(Node
);
333 Parent
= RtlParentAvl(Node
);
334 ASSERT(Parent
!= NULL
);
335 while (Parent
!= Node
)
337 /* The parent should be a left child, return the real predecessor */
338 if (RtlIsLeftChildAvl(Node
))
344 /* Keep lopping until we find our parent */
346 Parent
= RtlParentAvl(Node
);
355 MiFindEmptyAddressRangeInTree(IN SIZE_T Length
,
356 IN ULONG_PTR Alignment
,
357 IN PMM_AVL_TABLE Table
,
358 OUT PMMADDRESS_NODE
*PreviousVad
,
361 PMMADDRESS_NODE Node
, PreviousNode
;
362 ULONG_PTR PageCount
, AlignmentVpn
, LowVpn
, HighVpn
;
365 /* Calculate page numbers for the length, alignment, and starting address */
366 PageCount
= BYTES_TO_PAGES(Length
);
367 AlignmentVpn
= Alignment
>> PAGE_SHIFT
;
368 LowVpn
= ALIGN_UP_BY((ULONG_PTR
)MM_LOWEST_USER_ADDRESS
>> PAGE_SHIFT
, AlignmentVpn
);
370 /* Check if the table is empty */
371 if (Table
->NumberGenericTableElements
== 0)
373 /* Tree is empty, the candidate address is already the best one */
374 *Base
= LowVpn
<< PAGE_SHIFT
;
375 return TableEmptyTree
;
378 /* Otherwise, follow the leftmost child of the right root node's child */
379 Node
= RtlRightChildAvl(&Table
->BalancedRoot
);
380 while (RtlLeftChildAvl(Node
)) Node
= RtlLeftChildAvl(Node
);
382 /* Start a search to find a gap */
386 /* Check if the gap below the current node is suitable */
387 if (Node
->StartingVpn
>= LowVpn
+ PageCount
)
389 /* There is enough space to add our node */
390 *Base
= LowVpn
<< PAGE_SHIFT
;
392 /* Can we use the current node as parent? */
393 if (RtlLeftChildAvl(Node
) == NULL
)
395 /* Node has no left child, so use it as parent */
397 return TableInsertAsLeft
;
401 /* Node has a left child, this means that the previous node is
402 the right-most child of it's left child and can be used as
403 the parent. In case we use the space before the left-most
404 node, it's left child must be NULL. */
405 ASSERT(PreviousNode
!= NULL
);
406 ASSERT(RtlRightChildAvl(PreviousNode
) == NULL
);
407 *PreviousVad
= PreviousNode
;
408 return TableInsertAsRight
;
412 /* The next candidate is above the current node */
413 if (Node
->EndingVpn
>= LowVpn
)
414 LowVpn
= ALIGN_UP_BY(Node
->EndingVpn
+ 1, AlignmentVpn
);
416 /* Remember the current node and go to the next node */
418 Node
= MiGetNextNode(Node
);
421 /* We're up to the highest VAD, will this allocation fit above it? */
422 HighVpn
= ((ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
+ 1) / PAGE_SIZE
;
423 if (HighVpn
>= LowVpn
+ PageCount
)
425 /* Yes! Use this VAD to store the allocation */
426 *PreviousVad
= PreviousNode
;
427 *Base
= LowVpn
<< PAGE_SHIFT
;
428 return TableInsertAsRight
;
431 /* Nyet, there's no free address space for this allocation, so we'll fail */
432 return TableFoundNode
;
437 MiFindEmptyAddressRangeDownTree(IN SIZE_T Length
,
438 IN ULONG_PTR BoundaryAddress
,
439 IN ULONG_PTR Alignment
,
440 IN PMM_AVL_TABLE Table
,
442 OUT PMMADDRESS_NODE
*Parent
)
444 PMMADDRESS_NODE Node
, OldNode
, Child
;
445 ULONG_PTR LowVpn
, HighVpn
, AlignmentVpn
;
446 PFN_NUMBER PageCount
;
449 ASSERT(BoundaryAddress
);
450 ASSERT(BoundaryAddress
<= ((ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
));
451 ASSERT((Alignment
& (PAGE_SIZE
- 1)) == 0);
453 /* Calculate page numbers for the length and alignment */
454 Length
= ROUND_TO_PAGES(Length
);
455 PageCount
= Length
>> PAGE_SHIFT
;
456 AlignmentVpn
= Alignment
/ PAGE_SIZE
;
458 /* Check if there is enough space below the boundary */
459 if ((ALIGN_UP_BY((ULONG_PTR
)MM_LOWEST_USER_ADDRESS
, Alignment
) + Length
) >
460 (BoundaryAddress
+ 1))
462 return TableFoundNode
;
465 /* Check if the table is empty */
466 if (Table
->NumberGenericTableElements
== 0)
468 /* Tree is empty, the candidate address is already the best one */
469 *Base
= ALIGN_DOWN_BY(BoundaryAddress
+ 1 - Length
, Alignment
);
470 return TableEmptyTree
;
473 /* Calculate the initial upper margin */
474 HighVpn
= (BoundaryAddress
+ 1) >> PAGE_SHIFT
;
476 /* Starting from the root, follow the right children until we found a node
477 that ends above the boundary */
478 Node
= RtlRightChildAvl(&Table
->BalancedRoot
);
479 while ((Node
->EndingVpn
< HighVpn
) &&
480 ((Child
= RtlRightChildAvl(Node
)) != NULL
)) Node
= Child
;
482 /* Now loop the Vad nodes */
485 /* Calculate the lower margin */
486 LowVpn
= ALIGN_UP_BY(Node
->EndingVpn
+ 1, AlignmentVpn
);
488 /* Check if the current bounds are suitable */
489 if ((HighVpn
> LowVpn
) && ((HighVpn
- LowVpn
) >= PageCount
))
491 /* There is enough space to add our node */
492 LowVpn
= ALIGN_DOWN_BY(HighVpn
- PageCount
, AlignmentVpn
);
493 *Base
= LowVpn
<< PAGE_SHIFT
;
495 /* Can we use the current node as parent? */
496 if (!RtlRightChildAvl(Node
))
498 /* Node has no right child, so use it as parent */
500 return TableInsertAsRight
;
504 /* Node has a right child, the node we had before is the most
505 left grandchild of that right child, use it as parent. */
507 return TableInsertAsLeft
;
511 /* Update the upper margin if necessary */
512 if (Node
->StartingVpn
< HighVpn
) HighVpn
= Node
->StartingVpn
;
514 /* Remember the current node and go to the previous node */
516 Node
= MiGetPreviousNode(Node
);
519 /* Check if there's enough space before the lowest Vad */
520 LowVpn
= ALIGN_UP_BY((ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
, Alignment
) / PAGE_SIZE
;
521 if ((HighVpn
> LowVpn
) && ((HighVpn
- LowVpn
) >= PageCount
))
523 /* There is enough space to add our address */
524 LowVpn
= ALIGN_DOWN_BY(HighVpn
- PageCount
, Alignment
>> PAGE_SHIFT
);
525 *Base
= LowVpn
<< PAGE_SHIFT
;
527 return TableInsertAsLeft
;
530 /* No address space left at all */
533 return TableFoundNode
;
538 MiFindEmptyAddressRangeDownBasedTree(IN SIZE_T Length
,
539 IN ULONG_PTR BoundaryAddress
,
540 IN ULONG_PTR Alignment
,
541 IN PMM_AVL_TABLE Table
,
544 PMMADDRESS_NODE Node
, LowestNode
;
545 ULONG_PTR LowVpn
, BestVpn
;
548 ASSERT(Table
== &MmSectionBasedRoot
);
549 ASSERT(BoundaryAddress
);
550 ASSERT(BoundaryAddress
<= ((ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
+ 1));
552 /* Compute page length, make sure the boundary address is valid */
553 Length
= ROUND_TO_PAGES(Length
);
554 if ((BoundaryAddress
+ 1) < Length
) return STATUS_NO_MEMORY
;
556 /* Check if the table is empty */
557 BestVpn
= ROUND_DOWN(BoundaryAddress
+ 1 - Length
, Alignment
);
558 if (Table
->NumberGenericTableElements
== 0)
560 /* Tree is empty, the candidate address is already the best one */
562 return STATUS_SUCCESS
;
565 /* Go to the right-most node which should be the biggest address */
566 Node
= Table
->BalancedRoot
.RightChild
;
567 while (RtlRightChildAvl(Node
)) Node
= RtlRightChildAvl(Node
);
569 /* Check if we can fit in here */
570 LowVpn
= ROUND_UP(Node
->EndingVpn
+ 1, Alignment
);
571 if ((LowVpn
< BoundaryAddress
) && (Length
<= (BoundaryAddress
- LowVpn
)))
573 #if (NTDDI_VERSION >= NTDDI_VISTA)
574 /* Return the address. */
577 /* Note: this is a compatibility hack that mimics a bug in the 2k3
578 kernel. It will can waste up to Alignment bytes of memory above
579 the allocation. This bug was fixed in Windows Vista */
580 *Base
= ROUND_DOWN(BoundaryAddress
- Length
, Alignment
);
582 return STATUS_SUCCESS
;
585 /* Now loop the Vad nodes */
588 /* Break out if we've reached the last node */
589 LowestNode
= MiGetPreviousNode(Node
);
590 if (!LowestNode
) break;
592 /* Check if this node could contain the requested address */
593 LowVpn
= ROUND_UP(LowestNode
->EndingVpn
+ 1, Alignment
);
594 if ((LowestNode
->EndingVpn
< BestVpn
) &&
595 (LowVpn
< Node
->StartingVpn
) &&
596 (Length
<= (Node
->StartingVpn
- LowVpn
)))
598 /* Check if we need to take BoundaryAddress into account */
599 if (BoundaryAddress
< Node
->StartingVpn
)
601 /* Return the optimal VPN address */
603 return STATUS_SUCCESS
;
607 /* The upper margin is given by the Node's starting address */
608 *Base
= ROUND_DOWN(Node
->StartingVpn
- Length
, Alignment
);
609 return STATUS_SUCCESS
;
613 /* Move to the next node */
617 /* Check if there's enough space before the lowest Vad */
618 if ((Node
->StartingVpn
> (ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
) &&
619 ((Node
->StartingVpn
- (ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
) >= Length
))
621 /* Check if it fits in perfectly */
622 if (BoundaryAddress
< Node
->StartingVpn
)
624 /* Return the optimal VPN address */
626 return STATUS_SUCCESS
;
629 /* Return an aligned base address within this node */
630 *Base
= ROUND_DOWN(Node
->StartingVpn
- Length
, Alignment
);
631 return STATUS_SUCCESS
;
634 /* No address space left at all */
635 return STATUS_NO_MEMORY
;
640 MiCheckSecuredVad(IN PMMVAD Vad
,
643 IN ULONG ProtectionMask
)
645 ULONG_PTR StartAddress
, EndAddress
;
647 /* Compute start and end address */
648 StartAddress
= (ULONG_PTR
)Base
;
649 EndAddress
= StartAddress
+ Size
- 1;
651 /* Are we deleting/unmapping, or changing? */
652 if (ProtectionMask
< MM_DELETE_CHECK
)
654 /* Changing... are we allowed to do so? */
655 if ((Vad
->u
.VadFlags
.NoChange
== 1) &&
656 (Vad
->u2
.VadFlags2
.SecNoChange
== 1) &&
657 (Vad
->u
.VadFlags
.Protection
!= ProtectionMask
))
660 DPRINT1("Trying to mess with a no-change VAD!\n");
661 return STATUS_INVALID_PAGE_PROTECTION
;
666 /* This is allowed */
670 /* ARM3 doesn't support this yet */
671 ASSERT(Vad
->u2
.VadFlags2
.MultipleSecured
== 0);
673 /* Is this a one-secured VAD, like a TEB or PEB? */
674 if (Vad
->u2
.VadFlags2
.OneSecured
)
676 /* Is this allocation being described by the VAD? */
677 if ((StartAddress
<= ((PMMVAD_LONG
)Vad
)->u3
.Secured
.EndVpn
) &&
678 (EndAddress
>= ((PMMVAD_LONG
)Vad
)->u3
.Secured
.StartVpn
))
681 if (ProtectionMask
& MM_DECOMMIT
)
683 DPRINT1("Not allowed to change protection on guard page!\n");
684 return STATUS_INVALID_PAGE_PROTECTION
;
687 /* ARM3 doesn't have read-only VADs yet */
688 ASSERT(Vad
->u2
.VadFlags2
.ReadOnly
== 0);
690 /* Check if read-write protections are allowed */
691 if (MmReadWrite
[ProtectionMask
] < MM_READ_WRITE_ALLOWED
)
693 DPRINT1("Invalid protection mask for RW access!\n");
694 return STATUS_INVALID_PAGE_PROTECTION
;
699 /* All good, allow the change */
700 return STATUS_SUCCESS
;