diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build
index 1a71ce5a..37c4435a 100644
--- a/include/libcamera/base/meson.build
+++ b/include/libcamera/base/meson.build
@@ -13,6 +13,7 @@ libcamera_base_headers = files([
     'flags.h',
     'log.h',
     'message.h',
+    'mutex.h',
     'object.h',
     'private.h',
     'semaphore.h',
diff --git a/include/libcamera/base/mutex.h b/include/libcamera/base/mutex.h
new file mode 100644
index 00000000..55091eb4
--- /dev/null
+++ b/include/libcamera/base/mutex.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * thread.h - Thread support
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+
+#include <libcamera/base/thread_annotations.h>
+
+namespace libcamera {
+
+class ConditionVariable;
+class LIBCAMERA_TSA_SCOPED_CAPABILITY MutexLocker;
+
+/* \todo using Mutex = std::mutex if lib++ is used. */
+class LIBCAMERA_TSA_CAPABILITY("mutex") Mutex final
+{
+public:
+	constexpr Mutex()
+	{
+	}
+
+	void lock() LIBCAMERA_TSA_ACQUIRE()
+	{
+		mutex_.lock();
+	}
+
+	void unlock() LIBCAMERA_TSA_RELEASE()
+	{
+		mutex_.unlock();
+	}
+
+private:
+	friend MutexLocker;
+
+	std::mutex mutex_;
+};
+
+class LIBCAMERA_TSA_SCOPED_CAPABILITY MutexLocker final
+{
+public:
+	explicit MutexLocker(Mutex &mutex) LIBCAMERA_TSA_ACQUIRE(mutex)
+		: lock_(mutex.mutex_)
+	{
+	}
+
+	MutexLocker(Mutex &mutex, std::defer_lock_t t) noexcept LIBCAMERA_TSA_ACQUIRE(mutex)
+		: lock_(mutex.mutex_, t)
+	{
+	}
+
+	MutexLocker(Mutex &mutex, std::try_to_lock_t t) noexcept LIBCAMERA_TSA_ACQUIRE(mutex)
+		: lock_(mutex.mutex_, t)
+	{
+	}
+
+	MutexLocker(Mutex &mutex, std::adopt_lock_t t) noexcept LIBCAMERA_TSA_ACQUIRE(mutex)
+		: lock_(mutex.mutex_, t)
+	{
+	}
+
+	~MutexLocker() LIBCAMERA_TSA_RELEASE()
+	{
+	}
+
+	void lock() LIBCAMERA_TSA_ACQUIRE()
+	{
+		lock_.lock();
+	}
+
+	void unlock() LIBCAMERA_TSA_RELEASE()
+	{
+		lock_.unlock();
+	}
+
+	bool try_lock() LIBCAMERA_TSA_TRY_ACQUIRE(true)
+	{
+		return lock_.try_lock();
+	}
+
+private:
+	friend ConditionVariable;
+
+	std::unique_lock<std::mutex> lock_;
+};
+
+class ConditionVariable final
+{
+public:
+	ConditionVariable()
+	{
+	}
+
+	void notify_one() noexcept
+	{
+		cv_.notify_one();
+	}
+
+	void notify_all() noexcept
+	{
+		cv_.notify_all();
+	}
+
+	template<class Predicate>
+	void wait(MutexLocker &locker, Predicate stopWaiting)
+	{
+		cv_.wait(locker.lock_, stopWaiting);
+	}
+
+	template<class Rep, class Period, class Predicate>
+	bool wait_for(MutexLocker &locker,
+		      const std::chrono::duration<Rep, Period> &relTime,
+		      Predicate stopWaiting)
+	{
+		return cv_.wait_for(locker.lock_, relTime, stopWaiting);
+	}
+
+private:
+	std::condition_variable cv_;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/base/thread.h b/include/libcamera/base/thread.h
index 1ebf8363..44678c34 100644
--- a/include/libcamera/base/thread.h
+++ b/include/libcamera/base/thread.h
@@ -7,15 +7,14 @@
 
 #pragma once
 
-#include <condition_variable>
 #include <memory>
-#include <mutex>
 #include <sys/types.h>
 #include <thread>
 
 #include <libcamera/base/private.h>
 
 #include <libcamera/base/message.h>
+#include <libcamera/base/mutex.h>
 #include <libcamera/base/signal.h>
 #include <libcamera/base/utils.h>
 
@@ -27,10 +26,6 @@ class Object;
 class ThreadData;
 class ThreadMain;
 
-using ConditionVariable = std::condition_variable;
-using Mutex = std::mutex;
-using MutexLocker = std::unique_lock<std::mutex>;
-
 class Thread
 {
 public:
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index 040954dd..23be995d 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -14,7 +14,8 @@
 #include <sys/types.h>
 #include <vector>
 
-#include <libcamera/base/thread.h>
+#include <libcamera/base/mutex.h>
+#include <libcamera/base/thread_annotations.h>
 
 #include <libcamera/camera.h>
 
@@ -59,7 +60,7 @@ private:
 	int vidioc_querybuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
 	int vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
 	int vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg,
-			 libcamera::Mutex *lock);
+			 libcamera::Mutex *lock) LIBCAMERA_TSA_REQUIRES(*lock);
 	int vidioc_streamon(V4L2CameraFile *file, int *arg);
 	int vidioc_streamoff(V4L2CameraFile *file, int *arg);
 
