/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    tcpip\ip\ipmlock.h

Abstract:

    Reader Writer lock primitives for the IP Multicasting

Author:

    Amritansh Raghav

Revision History:

    AmritanR    Created

Notes:

--*/

//
// Need to include "debug.h" before this file is included because
// RT_LOCK is defined there
//

//
// A reader writer lock for kernel mode.
//

typedef struct _RW_LOCK
{
    RT_LOCK rlReadLock;
    RT_LOCK rlWriteLock;
    LONG    lReaderCount;
}RW_LOCK, *PRW_LOCK;

//
// VOID
// InitRwLock(
//  PRW_LOCK    pLock
//  )
//
//  Initializes the spin locks and the reader count
//


#define InitRwLock(l)                                           \
    RtInitializeSpinLock(&((l)->rlReadLock));                   \
    RtInitializeSpinLock(&((l)->rlWriteLock));                  \
    (l)->lReaderCount = 0
        
//
// VOID
// EnterReader(
//  PRW_LOCK    pLock,
//  PKIRQL      pCurrIrql 
//  )
//
// Acquires the Reader Spinlock (now thread is at DPC). 
// InterlockedIncrements the reader count (interlocked because the reader 
// lock is not taken when the count is decremented in ExitReader())
// If the thread is the first reader, also acquires the Writer Spinlock (at
// DPC to be more efficient) to block writers
// Releases the Reader Spinlock from DPC, so that it remains at DPC
// for the duration of the lock being held 
//
// If a writer is in the code, the first reader will wait on the Writer
// Spinlock and all subsequent readers will wait on the Reader Spinlock
// If a reader is in the code and is executing the EnterReader, then a new
// reader will wait for sometime on the Reader Spinlock, and then proceed
// on to the code (at DPC)
//



#define EnterReader(l, q)                                       \
    RtAcquireSpinLock(&((l)->rlReadLock), (q));                 \
    if(InterlockedIncrement(&((l)->lReaderCount)) == 1)         \
        RtAcquireSpinLockAtDpcLevel(&((l)->rlWriteLock));       \
    RtReleaseSpinLockFromDpcLevel(&((l)->rlReadLock))

#define EnterReaderAtDpcLevel(l)                                \
    RtAcquireSpinLockAtDpcLevel(&((l)->rlReadLock));            \
    if(InterlockedIncrement(&((l)->lReaderCount)) == 1)         \
        RtAcquireSpinLockAtDpcLevel(&((l)->rlWriteLock));       \
    RtReleaseSpinLockFromDpcLevel(&((l)->rlReadLock))

//
// VOID
// ExitReader(
//  PRW_LOCK    pLock,
//  KIRQL       kiOldIrql
//  )
//
// InterlockedDec the reader count.
// If this is the last reader, then release the Writer Spinlock to let
// other writers in
// Otherwise, just lower the irql to what was before the lock was
// acquired.  Either way, the irql is down to original irql
//

#define ExitReader(l, q)                                        \
    if(InterlockedDecrement(&((l)->lReaderCount)) == 0)         \
        RtReleaseSpinLock(&((l)->rlWriteLock), q);              \
    else                                                        \
        KeLowerIrql(q)

#define ExitReaderFromDpcLevel(l)                               \
    if(InterlockedDecrement(&((l)->lReaderCount)) == 0)         \
        RtReleaseSpinLockFromDpcLevel(&((l)->rlWriteLock))

//
// EnterWriter(
//  PRW_LOCK    pLock,
//  PKIRQL      pCurrIrql
//  )
//
// Acquire the reader and then the writer spin lock
// If there  are readers in the code, the first writer will wait
// on the Writer Spinlock.  All other writers will wait (with readers)
// on the Reader Spinlock
// If there is a writer in the code then a new writer will wait on 
// the Reader Spinlock

#define EnterWriter(l, q)                                       \
    RtAcquireSpinLock(&((l)->rlReadLock), (q));                 \
    RtAcquireSpinLockAtDpcLevel(&((l)->rlWriteLock))

#define EnterWriterAtDpcLevel(l)                                \
    RtAcquireSpinLockAtDpcLevel(&((l)->rlReadLock));            \
    RtAcquireSpinLockAtDpcLevel(&((l)->rlWriteLock))


//
// ExitWriter(
//  PRW_LOCK    pLock,
//  KIRQL       kiOldIrql
//  )
//
// Release both the locks
//

#define ExitWriter(l, q)                                        \
    RtReleaseSpinLockFromDpcLevel(&((l)->rlWriteLock));         \
    RtReleaseSpinLock(&((l)->rlReadLock), q)


#define ExitWriterFromDpcLevel(l)                               \
    RtReleaseSpinLockFromDpcLevel(&((l)->rlWriteLock));         \
    RtReleaseSpinLockFromDpcLevel(&((l)->rlReadLock))