;     title  "Interlocked Operations"
;++
;
; Copyright (c) Microsoft Corporation
;
; Module Name:
;
;   intrlock.asm
;
; Abstract:
;
;   This module implements the 32 and 16 bit interlocked operations for ARMv6 and beyond.
;   The instructions used in this module for gaining exclusive access to a memory location 
;   arent supported in ARMv5 and earlier
;
;   The functions aren't optimized because these are brand new instructions that we dont 
;   understand completely. That should be done after profiling and understanding their behavior.
; 
;   This whole file will be discarded when the intrinsics come in.
;
; Environment:
;
;   Kernel and User mode.
;
;--

#include "ksarm.h"

#if defined(_ARM_WORKAROUND_)
; The LDREXH, STREXH, LDREXD, STREXD and CLREX instructions arent supported 
; by the current assembler. Remove this and replace with the actual instructions
; when the assembler supports it
; This is not a generic assembler so I am explicitly defining values only for the 
; instructions I am using


#define LDREXH_R12_R0           0xE1F0CF9F          ;ldrexh      r12, [r0]          (1110 0001 1111 0000 1100 1111 1001 1111)
#define STREXHEQ_R3_R1_R0       0x01E03F91          ;strexheq    r3,  r1,  [r0]     (0000 0001 1110 0000 0011 1111 1001 0001)
#define STREXH_R3_R12_R0        0xE1E03F9C          ;strexh      r3,  r12, [r0]     (1110 0001 1110 0000 0011 1111 1001 1100)
#define LDREXD_R4_R0            0xE1B04F9F          ;ldrexd      r4,  [r0]          (1110 0001 1011 0000 0100 1111 1001 1111)
#define STREXDEQ_R2_R6_R0       0x01A02F96          ;strexdeq    r2,  r6,  [r0]     (0000 0001 1010 0000 0010 1111 1001 0110)

#define CLREX                   0xF57FF01F          ; clrex                         (1111 0101 0111 1111 1111 0000 0001 1111)

#endif


        TEXTAREA
   
    LEAF_ENTRY InterlockedExchange
1       ldrex       r12, [r0]                       ; load from the address and mark it exclusive
        strex       r3,  r1,  [r0]                  ; attempt to store the new value
        cmp         r3,  #1                         ; check if the store failed (0=success, 1=failure)
        beq         %B1                             ; restart if the store failed
        mov         r0,  r12                        ; return the original value
        mov         pc,  lr
    LEAF_END
 
    LEAF_ENTRY InterlockedCompareExchange
1       ldrex       r12, [r0]                       ; load from the address and mark it exclusive
        cmp         r12, r2                         ; compare the value with the comperand(r2)
        strexeq     r3,  r1,  [r0]                  ; if they were equal, attempt to store the new value (r1)
        bne         %F2                             ; if they were not equal jump to (2) which clears the exclusive tag on the address and returns
        cmp         r3,  #1                         ; check the status of the store (returned in r3)
        beq         %B1                             ; go back to the start if it failed (0=success, 1=failure)
        bne         %F3                             ; if it succeeded, jump to (3) and return. there is no need to clrex if strex succeeded
2       dcd         CLREX                           ; clrex
3       mov         r0,  r12
        mov         pc,  lr
    LEAF_END

    LEAF_ENTRY InterlockedCompareExchange16         ; has the same structure as InterlockedCompareExchange, but uses half word operands
1       dcd         LDREXH_R12_R0
        cmp         r12, r2
        dcd         STREXHEQ_R3_R1_R0         
        bne         %F2
        cmp         r3,  #1
        beq         %B1
        bne         %F3
2       dcd         CLREX                           ; clrex
3       mov         r0,  r12
        mov         pc,  lr
    LEAF_END
 
    LEAF_ENTRY InterlockedIncrement
1       ldrex       r12, [r0]                       ; load from the address and mark it exclusive
        add         r12, r12, #1                    ; increment it
        strex       r3,  r12, [r0]                  ; attempt to store the new value
        cmp         r3,  #1                         ; check if it succeeded (0=success, 1=failure)
        beq         %B1                             ; loop if it failed
        mov         r0,  r12                        ; return the increment value if it succeeded
        mov         pc,  lr
    LEAF_END
 
    LEAF_ENTRY InterlockedIncrement16               ; has the same structure as InterlockedIncrement, but uses half word operands
1       dcd         LDREXH_R12_R0
        add         r12, r12, #1
        dcd         STREXH_R3_R12_R0
        cmp         r3, #1
        beq         %B1
        mov         r0,  r12
        mov         pc,  lr
    LEAF_END
 
    LEAF_ENTRY InterlockedDecrement                 ; has the same structure as InterlockedIncrement
1       ldrex       r12, [r0]
        sub         r12, r12, #1
        strex       r3,  r12, [r0]
        cmp         r3, #1
        beq         %B1
        mov         r0,  r12
        mov         pc,  lr
    LEAF_END
     
    LEAF_ENTRY InterlockedDecrement16               ; has the same structure as InterlockedIncrement
1       dcd         LDREXH_R12_R0
        sub         r12, r12, #1
        dcd         STREXH_R3_R12_R0
        cmp         r3, #1
        beq         %B1
        mov         r0,  r12
        mov         pc,  lr
    LEAF_END  

    LEAF_ENTRY InterlockedExchangeAdd               ; has the same structure as InterlockedIncrement
1       ldrex       r12, [r0]
        add         r2,  r12, r1
        strex       r3,  r2,  [r0]
        cmp         r3,  #1
        beq         %B1
        mov         r0,  r12
        mov         pc,  lr
    LEAF_END
     
    LEAF_ENTRY InterlockedOr                        ; has the same structure as InterlockedIncrement
1       ldrex       r12, [r0]
        orr         r2,  r12, r1
        strex       r3,  r2,  [r0]
        cmp         r3,  #1
        beq         %B1
        mov         r0,  r12
        mov         pc,  lr
    LEAF_END

    LEAF_ENTRY InterlockedAnd                       ; has the same structure as InterlockedIncrement
1       ldrex       r12, [r0]
        and         r2,  r12, r1
        strex       r3,  r2,  [r0]
        cmp         r3,  #1
        beq         %B1
        mov         r0,  r12
        mov         pc,  lr
    LEAF_END
     
    LEAF_ENTRY InterlockedXor                       ; has the same structure as InterlockedIncrement
1       ldrex       r12, [r0]
        eor         r2,  r12, r1
        strex       r3,  r2,  [r0]
        cmp         r3,  #1
        beq         %B1
        mov         r0,  r12
        mov         pc,  lr
    LEAF_END
    
    
    ; InterlockedCompareExchange64 - 
    ; Arguments:
    ; r0 = Pointer to Destination
    ; r1 = Exchange.LowPart
    ; r2 = Exchange.HighPart
    ; r3 = Comperand.LowPart
    ; [sp] = Comperand.HighPart
    
    NESTED_ENTRY InterlockedCompareExchange64
        stmdb       sp!, {r4-r7, lr}
        PROLOG_END
        ldr         r12,  [sp, #0x14]               ; load r12 with Comperand.HighPart
        mov         r6,   r1                        ; move [Exchange.LowPart,Exchange.HighPart] to [r6,r7] because the strex instruction demands that Rm be an even numbered register
        mov         r7,   r2
1       dcd         LDREXD_R4_R0                    ; ldrexd      r4,  [r0] (r4 = Destination.LowPart, r5 = Destination.HighPart)
        cmp         r4,  r3                         ; if (Destination.LowPart == Comperand.LowPart)
        cmpeq       r5,  r12                        ;  && (Destination.HightPart == Comperand.HighPart)
        dcd         STREXDEQ_R2_R6_R0               ; streqd      r2,  r6, [r0] (attempt to store the new value if equal. [r0] = r6; [r0 + 4] = r7;)
        bne         %F2                             ; if not equal, branch to end and return
        cmp         r2,  #1                         ; check the status of the attempt (0=success, 1=failure)
        beq         %B1                             ; loop if the store failed
        bne         %F3
2       dcd         CLREX                           ; clrex
3       mov         r0,  r4
        mov         r1,  r5
        ldmia       sp!, {r4-r7, pc}
    NESTED_END
     
    END
 
