diff --git a/kmtests/CMakeLists.txt b/kmtests/CMakeLists.txt
index 93b5912c15d..ca5c0d1feda 100644
--- a/kmtests/CMakeLists.txt
+++ b/kmtests/CMakeLists.txt
@@ -32,6 +32,7 @@ list(APPEND KMTEST_DRV_SOURCE
ntos_ke/KeApc.c
ntos_ke/KeDpc.c
ntos_ke/KeEvent.c
+ ntos_ke/KeGuardedMutex.c
ntos_ke/KeIrql.c
ntos_ke/KeProcessor.c
ntos_ke/KeSpinLock.c
diff --git a/kmtests/kmtest_drv.rbuild b/kmtests/kmtest_drv.rbuild
index 8993688dda9..22268078ca5 100644
--- a/kmtests/kmtest_drv.rbuild
+++ b/kmtests/kmtest_drv.rbuild
@@ -37,6 +37,7 @@
KeApc.c
KeDpc.c
KeEvent.c
+ KeGuardedMutex.c
KeIrql.c
KeProcessor.c
KeSpinLock.c
diff --git a/kmtests/kmtest_drv/testlist.c b/kmtests/kmtest_drv/testlist.c
index a9b94c63b06..6562c0e1042 100644
--- a/kmtests/kmtest_drv/testlist.c
+++ b/kmtests/kmtest_drv/testlist.c
@@ -25,6 +25,7 @@ KMT_TESTFUNC Test_IoMdl;
KMT_TESTFUNC Test_KeApc;
KMT_TESTFUNC Test_KeDpc;
KMT_TESTFUNC Test_KeEvent;
+KMT_TESTFUNC Test_KeGuardedMutex;
KMT_TESTFUNC Test_KeIrql;
KMT_TESTFUNC Test_KeProcessor;
KMT_TESTFUNC Test_KernelType;
@@ -55,6 +56,7 @@ const KMT_TEST TestList[] =
{ "KeApc", Test_KeApc },
{ "KeDpc", Test_KeDpc },
{ "KeEvent", Test_KeEvent },
+ { "KeGuardedMutex", Test_KeGuardedMutex },
{ "KeIrql", Test_KeIrql },
{ "KeProcessor", Test_KeProcessor },
{ "-KernelType", Test_KernelType },
diff --git a/kmtests/ntos_ke/KeGuardedMutex.c b/kmtests/ntos_ke/KeGuardedMutex.c
new file mode 100644
index 00000000000..ecbe50bebdc
--- /dev/null
+++ b/kmtests/ntos_ke/KeGuardedMutex.c
@@ -0,0 +1,177 @@
+/*
+ * PROJECT: ReactOS kernel-mode tests
+ * LICENSE: GPLv2+ - See COPYING in the top level directory
+ * PURPOSE: Kernel-Mode Test Suite Guarded Mutex test
+ * PROGRAMMER: Thomas Faber
+ */
+
+#include
+
+#define NDEBUG
+#include
+
+#define CheckMutex(Mutex, ExpectedCount, ExpectedOwner, ExpectedContention, \
+ ExpectedKernelApcDisable, ExpectedSpecialApcDisable, \
+ KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, \
+ ExpectedIrql) do \
+{ \
+ ok_eq_long((Mutex)->Count, ExpectedCount); \
+ ok_eq_pointer((Mutex)->Owner, ExpectedOwner); \
+ ok_eq_ulong((Mutex)->Contention, ExpectedContention); \
+ ok_eq_int((Mutex)->KernelApcDisable, ExpectedKernelApcDisable); \
+ if (KmtIsCheckedBuild) \
+ ok_eq_int((Mutex)->SpecialApcDisable, ExpectedSpecialApcDisable); \
+ else \
+ ok_eq_int((Mutex)->SpecialApcDisable, 0x5555); \
+ ok_eq_bool(KeAreApcsDisabled(), KernelApcsDisabled || SpecialApcsDisabled); \
+ ok_eq_int(Thread->KernelApcDisable, KernelApcsDisabled); \
+ ok_eq_bool(KeAreAllApcsDisabled(), AllApcsDisabled); \
+ ok_eq_int(Thread->SpecialApcDisable, SpecialApcsDisabled); \
+ ok_irql(ExpectedIrql); \
+} while (0)
+
+static
+VOID
+TestGuardedMutex(
+ PKGUARDED_MUTEX Mutex,
+ SHORT KernelApcsDisabled,
+ SHORT SpecialApcsDisabled,
+ SHORT AllApcsDisabled,
+ KIRQL OriginalIrql)
+{
+ PKTHREAD Thread = KeGetCurrentThread();
+
+ ok_irql(OriginalIrql);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, 0x5555, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+
+ /* these ASSERT */
+ if (!KmtIsCheckedBuild || OriginalIrql <= APC_LEVEL)
+ {
+ /* acquire/release normally */
+ KeAcquireGuardedMutex(Mutex);
+ CheckMutex(Mutex, 0L, Thread, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled - 1, TRUE, OriginalIrql);
+ ok_bool_false(KeTryToAcquireGuardedMutex(Mutex), "KeTryToAcquireGuardedMutex returned");
+ CheckMutex(Mutex, 0L, Thread, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled - 1, TRUE, OriginalIrql);
+ KeReleaseGuardedMutex(Mutex);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+
+ /* try to acquire */
+ ok_bool_true(KeTryToAcquireGuardedMutex(Mutex), "KeTryToAcquireGuardedMutex returned");
+ CheckMutex(Mutex, 0L, Thread, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled - 1, TRUE, OriginalIrql);
+ KeReleaseGuardedMutex(Mutex);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+ }
+ else
+ /* Make the following test happy */
+ Mutex->SpecialApcDisable = SpecialApcsDisabled - 1;
+
+ /* ASSERT */
+ if (!KmtIsCheckedBuild || OriginalIrql == APC_LEVEL || SpecialApcsDisabled < 0)
+ {
+ /* acquire/release unsafe */
+ KeAcquireGuardedMutexUnsafe(Mutex);
+ CheckMutex(Mutex, 0L, Thread, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+ KeReleaseGuardedMutexUnsafe(Mutex);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+ }
+
+ /* Bugchecks >= DISPATCH_LEVEL */
+ if (!KmtIsCheckedBuild)
+ {
+ /* mismatched acquire/release */
+ KeAcquireGuardedMutex(Mutex);
+ CheckMutex(Mutex, 0L, Thread, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled - 1, TRUE, OriginalIrql);
+ KeReleaseGuardedMutexUnsafe(Mutex);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled - 1, TRUE, OriginalIrql);
+ KeLeaveGuardedRegion();
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+
+ KeAcquireGuardedMutexUnsafe(Mutex);
+ CheckMutex(Mutex, 0L, Thread, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+ KeReleaseGuardedMutex(Mutex);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled + 1, OriginalIrql >= APC_LEVEL || SpecialApcsDisabled != -1, OriginalIrql);
+ KeEnterGuardedRegion();
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+
+ /* release without acquire */
+ KeReleaseGuardedMutexUnsafe(Mutex);
+ CheckMutex(Mutex, 0L, NULL, 0LU, 0x5555, SpecialApcsDisabled - 1, KernelApcsDisabled, SpecialApcsDisabled, AllApcsDisabled, OriginalIrql);
+ KeReleaseGuardedMutex(Mutex);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled, KernelApcsDisabled, SpecialApcsDisabled + 1, OriginalIrql >= APC_LEVEL || SpecialApcsDisabled != -1, OriginalIrql);
+ KeReleaseGuardedMutex(Mutex);
+ /* TODO: here we see that Mutex->Count isn't actually just a count. Test the bits correctly! */
+ CheckMutex(Mutex, 0L, NULL, 0LU, 0x5555, SpecialApcsDisabled, KernelApcsDisabled, SpecialApcsDisabled + 2, OriginalIrql >= APC_LEVEL || SpecialApcsDisabled != -2, OriginalIrql);
+ KeReleaseGuardedMutex(Mutex);
+ CheckMutex(Mutex, 1L, NULL, 0LU, 0x5555, SpecialApcsDisabled, KernelApcsDisabled, SpecialApcsDisabled + 3, OriginalIrql >= APC_LEVEL || SpecialApcsDisabled != -3, OriginalIrql);
+ Thread->SpecialApcDisable -= 3;
+ }
+
+ /* make sure we survive this in case of error */
+ ok_eq_long(Mutex->Count, 1L);
+ Mutex->Count = 1;
+ ok_eq_int(Thread->KernelApcDisable, KernelApcsDisabled);
+ Thread->KernelApcDisable = KernelApcsDisabled;
+ ok_eq_int(Thread->SpecialApcDisable, SpecialApcsDisabled);
+ Thread->SpecialApcDisable = SpecialApcsDisabled;
+ ok_irql(OriginalIrql);
+}
+
+START_TEST(KeGuardedMutex)
+{
+ KGUARDED_MUTEX Mutex;
+ KIRQL OldIrql;
+ PKTHREAD Thread = KeGetCurrentThread();
+ struct {
+ KIRQL Irql;
+ SHORT KernelApcsDisabled;
+ SHORT SpecialApcsDisabled;
+ BOOLEAN AllApcsDisabled;
+ } TestIterations[] =
+ {
+ { PASSIVE_LEVEL, 0, 0, FALSE },
+ { PASSIVE_LEVEL, -1, 0, FALSE },
+ { PASSIVE_LEVEL, -3, 0, FALSE },
+ { PASSIVE_LEVEL, 0, -1, TRUE },
+ { PASSIVE_LEVEL, -1, -1, TRUE },
+ { PASSIVE_LEVEL, -3, -2, TRUE },
+ // 6
+ { APC_LEVEL, 0, 0, TRUE },
+ { APC_LEVEL, -1, 0, TRUE },
+ { APC_LEVEL, -3, 0, TRUE },
+ { APC_LEVEL, 0, -1, TRUE },
+ { APC_LEVEL, -1, -1, TRUE },
+ { APC_LEVEL, -3, -2, TRUE },
+ // 12
+ { DISPATCH_LEVEL, 0, 0, TRUE },
+ { DISPATCH_LEVEL, -1, 0, TRUE },
+ { DISPATCH_LEVEL, -3, 0, TRUE },
+ { DISPATCH_LEVEL, 0, -1, TRUE },
+ { DISPATCH_LEVEL, -1, -1, TRUE },
+ { DISPATCH_LEVEL, -3, -2, TRUE },
+ // 18
+ { HIGH_LEVEL, 0, 0, TRUE },
+ { HIGH_LEVEL, -1, 0, TRUE },
+ { HIGH_LEVEL, -3, 0, TRUE },
+ { HIGH_LEVEL, 0, -1, TRUE },
+ { HIGH_LEVEL, -1, -1, TRUE },
+ { HIGH_LEVEL, -3, -2, TRUE },
+ };
+ int i;
+
+ for (i = 0; i < sizeof TestIterations / sizeof TestIterations[0]; ++i)
+ {
+ trace("Run %d\n", i);
+ KeRaiseIrql(TestIterations[i].Irql, &OldIrql);
+ Thread->KernelApcDisable = TestIterations[i].KernelApcsDisabled;
+ Thread->SpecialApcDisable = TestIterations[i].SpecialApcsDisabled;
+
+ RtlFillMemory(&Mutex, sizeof Mutex, 0x55);
+ KeInitializeGuardedMutex(&Mutex);
+ CheckMutex(&Mutex, 1L, NULL, 0LU, 0x5555, 0x5555, TestIterations[i].KernelApcsDisabled, TestIterations[i].SpecialApcsDisabled, TestIterations[i].AllApcsDisabled, TestIterations[i].Irql);
+ TestGuardedMutex(&Mutex, TestIterations[i].KernelApcsDisabled, TestIterations[i].SpecialApcsDisabled, TestIterations[i].AllApcsDisabled, TestIterations[i].Irql);
+
+ Thread->SpecialApcDisable = 0;
+ Thread->KernelApcDisable = 0;
+ KeLowerIrql(OldIrql);
+ }
+}