diff --git a/epicardium/modules/dispatcher.c b/epicardium/modules/dispatcher.c
index 03f8534cc0a1c02dccc61f8c03e486e2ab472ff8..355bc0471730b23d592b3187f0dd5a6a04ec9483 100644
--- a/epicardium/modules/dispatcher.c
+++ b/epicardium/modules/dispatcher.c
@@ -1,21 +1,20 @@
 #include "modules/log.h"
+#include "modules/mutex.h"
 
 #include "api/dispatcher.h"
 
 #include "FreeRTOS.h"
 #include "task.h"
-#include "semphr.h"
 
 #define TIMEOUT pdMS_TO_TICKS(2000)
 
 TaskHandle_t dispatcher_task_id;
 
-static StaticSemaphore_t api_mutex_data;
-SemaphoreHandle_t api_mutex = NULL;
+struct mutex api_mutex = { 0 };
 
 void dispatcher_mutex_init(void)
 {
-	api_mutex = xSemaphoreCreateMutexStatic(&api_mutex_data);
+	mutex_create(&api_mutex);
 }
 
 /*
@@ -27,12 +26,9 @@ void vApiDispatcher(void *pvParameters)
 	LOG_DEBUG("dispatcher", "Ready.");
 	while (1) {
 		if (api_dispatcher_poll()) {
-			if (xSemaphoreTake(api_mutex, TIMEOUT) != pdTRUE) {
-				LOG_ERR("dispatcher", "API mutex blocked");
-				continue;
-			}
+			mutex_lock(&api_mutex);
 			api_dispatcher_exec();
-			xSemaphoreGive(api_mutex);
+			mutex_unlock(&api_mutex);
 		}
 		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
 	}
diff --git a/epicardium/modules/lifecycle.c b/epicardium/modules/lifecycle.c
index b655e5779faf5ed4ee07058b1b616194df39a396..25689bd3db224789e5874deedd501fc1bcf8bfea 100644
--- a/epicardium/modules/lifecycle.c
+++ b/epicardium/modules/lifecycle.c
@@ -98,10 +98,7 @@ static int do_load(struct load_info *info)
 		LOG_INFO("lifecycle", "Loading \"%s\" ...", info->name);
 	}
 
-	if (xSemaphoreTake(api_mutex, BLOCK_WAIT) != pdTRUE) {
-		LOG_ERR("lifecycle", "API blocked");
-		return -EBUSY;
-	}
+	mutex_lock(&api_mutex);
 
 	if (info->do_reset) {
 		LOG_DEBUG("lifecycle", "Triggering core 1 reset.");
@@ -119,7 +116,7 @@ static int do_load(struct load_info *info)
 	 */
 	res = hardware_reset();
 	if (res < 0) {
-		return res;
+		goto out_free_api;
 	}
 
 	switch (info->type) {
@@ -133,8 +130,8 @@ static int do_load(struct load_info *info)
 			res = l0der_load_path(info->name, &l0dable);
 			if (res != 0) {
 				LOG_ERR("lifecycle", "l0der failed: %d\n", res);
-				xSemaphoreGive(api_mutex);
-				return -ENOEXEC;
+				res = -ENOEXEC;
+				goto out_free_api;
 			}
 			core1_load(l0dable.isr_vector, "");
 		} else {
@@ -148,12 +145,14 @@ static int do_load(struct load_info *info)
 		LOG_ERR("lifecyle",
 			"Attempted to load invalid payload (%s)",
 			info->name);
-		xSemaphoreGive(api_mutex);
-		return -EINVAL;
+		res = -EINVAL;
+		goto out_free_api;
 	}
 
-	xSemaphoreGive(api_mutex);
-	return 0;
+	res = 0;
+out_free_api:
+	mutex_unlock(&api_mutex);
+	return res;
 }
 
 /*
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index bf59566b3e7ef168977ccf7606d55f445961da86..745ecec230be10e8a97ebd0b181b72eaa8e2fc28 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -2,8 +2,8 @@
 #define MODULES_H
 
 #include "FreeRTOS.h"
-#include "semphr.h"
 #include "gpio.h"
+#include "modules/mutex.h"
 
 #include <stdint.h>
 #include <stdbool.h>
@@ -15,7 +15,7 @@ void panic(const char *format, ...)
 /* ---------- Dispatcher --------------------------------------------------- */
 void vApiDispatcher(void *pvParameters);
 void dispatcher_mutex_init(void);
-extern SemaphoreHandle_t api_mutex;
+extern struct mutex api_mutex;
 extern TaskHandle_t dispatcher_task_id;
 
 /* ---------- Hardware Init & Reset ---------------------------------------- */