diff --git a/drivers/acpi.cc b/drivers/acpi.cc
index 1ccb43d0b1a2b9e7924f13fddf0da666dfcfaaa7..08545dffa66b0ef7cd73c9a20cc42ae0e0f84b54 100644
--- a/drivers/acpi.cc
+++ b/drivers/acpi.cc
@@ -9,6 +9,9 @@ extern "C" {
 #include "drivers/clock.hh"
 #include "processor.hh"
 
+#include <osv/mutex.h>
+#include <osv/semaphore.hh>
+
 ACPI_STATUS AcpiOsInitialize(void)
 {
     return AE_OK;
@@ -51,49 +54,77 @@ ACPI_STATUS AcpiOsPhysicalTableOverride(ACPI_TABLE_HEADER *ExistingTable,
     return AE_OK;
 }
 
+// Note: AcpiOsCreateLock requires a lock which can be used for mutual
+// exclusion of a resources between multiple threads *AND* interrupt handlers.
+// Normally, this requires a spinlock (which disables interrupts), to ensure
+// that while a thread is using the protected resource, an interrupt handler
+// with the same context as the thread doesn't use it.
+// However, in OSV, interrupt handlers are run in ordinary threads, so the
+// mutual exclusion of an ordinary "mutex" is enough.
 ACPI_STATUS AcpiOsCreateLock(ACPI_SPINLOCK *OutHandle)
 {
-    // FIXME: implement
-    return AE_NOT_IMPLEMENTED;
+    *OutHandle = new mutex();
+    return AE_OK;
 }
 
 ACPI_CPU_FLAGS AcpiOsAcquireLock(ACPI_SPINLOCK Handle)
 {
-    // FIXME: implement
+    reinterpret_cast<mutex *>(Handle) -> lock();
     return 0;
 }
 
 void AcpiOsReleaseLock(ACPI_SPINLOCK Handle, ACPI_CPU_FLAGS Flags)
 {
-    // FIXME: implement
+    reinterpret_cast<mutex *>(Handle) -> unlock();;
 }
 
 void AcpiOsDeleteLock(ACPI_SPINLOCK Handle)
 {
-    // FIXME: implement
+    delete reinterpret_cast<mutex *>(Handle);
 }
 
 ACPI_STATUS AcpiOsCreateSemaphore(UINT32 MaxUnits,
         UINT32 InitialUnits, ACPI_SEMAPHORE *OutHandle)
 {
-    // FIXME: implement
-    return AE_NOT_IMPLEMENTED;
+    // Note: we ignore MaxUnits.
+    *OutHandle = new semaphore(InitialUnits);
+    return AE_OK;
 }
 
 ACPI_STATUS AcpiOsDeleteSemaphore(ACPI_SEMAPHORE Handle)
 {
-    return AE_NOT_IMPLEMENTED;
+    if (!Handle)
+        return AE_BAD_PARAMETER;
+    delete reinterpret_cast<semaphore *>(Handle);
+    return AE_OK;
 }
 
 ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE Handle,
         UINT32 Units, UINT16 Timeout)
 {
-    return AE_NOT_IMPLEMENTED;
+    if (!Handle)
+        return AE_BAD_PARAMETER;
+    semaphore *sem = reinterpret_cast<semaphore *>(Handle);
+    switch(Timeout) {
+    case ACPI_DO_NOT_WAIT:
+        return sem->trywait(Units) ? AE_OK : AE_TIME;
+    case ACPI_WAIT_FOREVER:
+        sem->wait(Units);
+        return AE_OK;
+    default:
+        sched::timer timer(*sched::thread::current());
+        timer.set(Timeout * 1_ms);
+        return sem->wait(Units, &timer) ? AE_OK : AE_TIME;
+    }
 }
 
 ACPI_STATUS AcpiOsSignalSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units)
 {
-    return AE_NOT_IMPLEMENTED;
+    if (!Handle)
+        return AE_BAD_PARAMETER;
+    semaphore *sem = reinterpret_cast<semaphore *>(Handle);
+    sem->post(Units);
+    return AE_OK;
 }
 
 void *AcpiOsAllocate(ACPI_SIZE Size)