diff --git a/devel/qemu/grub.cfg b/devel/qemu/grub.cfg
index fa12792a..ae42ae85 100644
--- a/devel/qemu/grub.cfg
+++ b/devel/qemu/grub.cfg
@@ -34,6 +34,8 @@ linux16 /boot/jinue \
     DEBUG_DUMP_SYSCALL_IMPLEMENTATION=1 \
     DEBUG_DUMP_RAMDISK=1 \
     DEBUG_DO_REBOOT=1 \
+    RUN_TEST_ACPI=1 \
     RUN_TEST_IPC=1
+
 initrd16 /boot/jinue-testapp-initrd.tar.gz
 boot
diff --git a/devel/virtualbox/grub.cfg b/devel/virtualbox/grub.cfg
index 9af1634d..a5ab4137 100644
--- a/devel/virtualbox/grub.cfg
+++ b/devel/virtualbox/grub.cfg
@@ -32,6 +32,8 @@ linux16 /boot/jinue \
     DEBUG_DUMP_MEMORY_MAP=1 \
     DEBUG_DUMP_SYSCALL_IMPLEMENTATION=1 \
     DEBUG_DUMP_RAMDISK=1 \
+    RUN_TEST_ACPI=1 \
     RUN_TEST_IPC=1
+
 initrd16 /boot/jinue-testapp-initrd.tar.gz
 boot
diff --git a/doc/init-process.md b/doc/init-process.md
index 55067b02..51776bda 100644
--- a/doc/init-process.md
+++ b/doc/init-process.md
@@ -171,6 +171,7 @@ The following table lists the auxiliary vectors:
 | 6          | `JINUE_AT_ENTRY`      | Address of program entry point            |
 | 7          | `JINUE_AT_STACKBASE`  | Stack base address                        |
 | 8          | `JINUE_AT_HOWSYSCALL` | System call implementation                |
+| 9          | `JINUE_AT_ACPI_RSDP`  | Physical address of ACPI RSDP             |
 
 The value of the `JINUE_AT_HOWSYSCALL` auxiliary vector identifies the
 system call implementation to use on architectures where there can be
diff --git a/doc/syscalls/README.md b/doc/syscalls/README.md
index 3352db9b..c18ea29d 100644
--- a/doc/syscalls/README.md
+++ b/doc/syscalls/README.md
@@ -28,8 +28,9 @@
 | 19      | [MINT](mint.md)                         | Mint a Descriptor                    |
 | 20      | [START_THREAD](start-thread.md)         | Start a Thread                       |
 | 21      | [AWAIT_THREAD](await-thread.md)         | Wait for a Thread to Exit            |
-| 22      | [REPLY_ERROR](reply-error.md)           | Reply to Message with an Error       !
-| 23-4095 | -                                       | Reserved                             |
+| 22      | [REPLY_ERROR](reply-error.md)           | Reply to Message with an Error       |
+| 23      | [SET_ACPI](set-acpi.md)                 | Provide Mapped ACPI Tables           |
+| 24-4095 | -                                       | Reserved                             |
 | 4096+   | [SEND](send.md)                         | Send Message                         |
 
 #### Reserved Function Numbers
diff --git a/doc/syscalls/set-acpi.md b/doc/syscalls/set-acpi.md
new file mode 100644
index 00000000..cb2bec6e
--- /dev/null
+++ b/doc/syscalls/set-acpi.md
@@ -0,0 +1,59 @@
+# SET_ACPI - Provide Mapped ACPI Tables
+
+## Description
+
+Provide the mapped ACPI tables to the kernel.
+
+This system call is reserved for use by the user space loader. It fails if
+called by another process.
+
+The user space loader must make a best effort to map the relevant ACPI tables
+and must call this system call with the result, unless the `JINUE_AT_ACPI_RSDP`
+auxiliary vector has been set to zero or omitted.
+
+## Arguments
+
+Function number (`arg0`) is 23.
+
+A pointer to a [jinue_acpi_tables_t structure](../../include/jinue/shared/types.h)
+(i.e. the ACPI tables structure) that contains pointers to mapped ACPI tables
+is set in `arg1`.
+
+If the user space loader could not map certain ACPI tables, it should set the
+relevant pointers to zero (C language `NULL` value) and still call this
+function.
+
+```
+    +----------------------------------------------------------------+
+    |                         function = 23                          |  arg0
+    +----------------------------------------------------------------+
+    31                                                               0
+    
+    +----------------------------------------------------------------+
+    |                 pointer to ACPI tables structure               |  arg1
+    +----------------------------------------------------------------+
+    31                                                               0
+    
+    +----------------------------------------------------------------+
+    |                         reserved (0)                           |  arg2
+    +----------------------------------------------------------------+
+    31                                                               0
+
+    +----------------------------------------------------------------+
+    |                         reserved (0)                           |  arg3
+    +----------------------------------------------------------------+
+    31                                                               0
+```
+
+## Return Value
+
+On success, this function returns 0 (in `arg0`). On failure, this function
+returns -1 and an error number is set (in `arg1`).
+
+## Errors
+
+* JINUE_EINVAL if any part of the ACPI tables structure as specified by `arg1`
+belongs to the kernel.
+* JINUE_ENOSYS if this function does not exist on this CPU architecture.
+* JINUE_ENOSYS if this function is called by a process other than the user
+space loader.
diff --git a/header.mk b/header.mk
index adbd6c7b..a3f187f0 100644
--- a/header.mk
+++ b/header.mk
@@ -115,12 +115,12 @@ endif
 #
 # These flags are used when preprocessing C and assembly language files.
 CPPFLAGS.includes    = -I$(includes)
-CPPFLAGS.arch          = -m32 -march=i686
+CPPFLAGS.arch        = -m32 -march=i686
 CPPFLAGS.others      = -nostdinc
 CPPFLAGS             = $(CPPFLAGS.arch) $(CPPFLAGS.includes) $(CPPFLAGS.debug) $(CPPFLAGS.others) $(CPPFLAGS.extra)
 
 # C compiler flags
-CFLAGS.warnings      = -std=c99 -pedantic -Wall -Werror=implicit -Werror=uninitialized -Werror=return-type
+CFLAGS.warnings      = -std=c99 -pedantic -Wall -Wno-array-bounds -Werror=implicit -Werror=uninitialized -Werror=return-type
 CFLAGS.arch          = -m32 -march=i686
 CFLAGS.optimization  = -O3
 CFLAGS.others        = -ffreestanding -fno-pie -fno-common -fno-omit-frame-pointer -fno-delete-null-pointer-checks
diff --git a/include/jinue/jinue.h b/include/jinue/jinue.h
index 596ff574..ffe1834f 100644
--- a/include/jinue/jinue.h
+++ b/include/jinue/jinue.h
@@ -119,4 +119,6 @@ int jinue_await_thread(int fd, int *perrno);
 
 int jinue_reply_error(uintptr_t errcode, int *perrno);
 
+int jinue_set_acpi(jinue_acpi_tables_t *tables, int *perrno);
+
 #endif
diff --git a/include/jinue/shared/asm/auxv.h b/include/jinue/shared/asm/auxv.h
index 5cd921ba..40a5f132 100644
--- a/include/jinue/shared/asm/auxv.h
+++ b/include/jinue/shared/asm/auxv.h
@@ -59,4 +59,7 @@
 /** System call implementation */
 #define JINUE_AT_HOWSYSCALL     8
 
+/** Address of RSDP (ACPI) */
+#define JINUE_AT_ACPI_RSDP      9
+
 #endif
diff --git a/include/jinue/shared/asm/syscalls.h b/include/jinue/shared/asm/syscalls.h
index 1f0d89e4..ad1e82e6 100644
--- a/include/jinue/shared/asm/syscalls.h
+++ b/include/jinue/shared/asm/syscalls.h
@@ -92,6 +92,9 @@
 /** reply to current message with an error code */
 #define JINUE_SYS_REPLY_ERROR           22
 
+/** provide the mapped ACPI tables */
+#define JINUE_SYS_SET_ACPI              23
+
 /** start of function numbers for user space messages */
 #define JINUE_SYS_USER_BASE             4096
 
diff --git a/include/jinue/shared/types.h b/include/jinue/shared/types.h
index 7e0fa8da..2c39e4d8 100644
--- a/include/jinue/shared/types.h
+++ b/include/jinue/shared/types.h
@@ -113,4 +113,10 @@ typedef struct {
     uintptr_t   cookie;
 } jinue_mint_args_t;
 
+typedef struct {
+    const void *rsdt;
+    const void *fadt;
+    const void *madt;
+} jinue_acpi_tables_t;
+
 #endif
diff --git a/include/kernel/application/syscalls.h b/include/kernel/application/syscalls.h
index 51e17547..be6e5412 100644
--- a/include/kernel/application/syscalls.h
+++ b/include/kernel/application/syscalls.h
@@ -72,6 +72,8 @@ int reply_error(uintptr_t errcode);
 
 int send(uintptr_t *errcode, int fd, int function, const jinue_message_t *message);
 
+int set_acpi(const jinue_acpi_tables_t *tables);
+
 void set_thread_local(void *addr, size_t size);
 
 int start_thread(int fd, const thread_params_t *params);
diff --git a/include/kernel/infrastructure/i686/drivers/acpi.h b/include/kernel/infrastructure/i686/drivers/acpi.h
new file mode 100644
index 00000000..e5ae275c
--- /dev/null
+++ b/include/kernel/infrastructure/i686/drivers/acpi.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef JINUE_KERNEL_INFRASTRUCTURE_I686_DRIVERS_ACPI_H
+#define JINUE_KERNEL_INFRASTRUCTURE_I686_DRIVERS_ACPI_H
+
+#include <kernel/infrastructure/i686/drivers/asm/acpi.h>
+#include <stdint.h>
+
+typedef struct {
+    char        signature[8];
+    uint8_t     checksum;
+    char        oemid[6];
+    uint8_t     revision;
+    uint32_t    rsdt_address;
+    uint32_t    length;
+    uint64_t    xsdt_address;
+    uint8_t     extended_checksum;
+    uint8_t     reserved[3];
+} acpi_rsdp_t;
+
+void acpi_init(void);
+
+uint32_t acpi_get_rsdp_paddr(void);
+
+#endif
diff --git a/include/kernel/infrastructure/i686/drivers/asm/acpi.h b/include/kernel/infrastructure/i686/drivers/asm/acpi.h
new file mode 100644
index 00000000..caf1a150
--- /dev/null
+++ b/include/kernel/infrastructure/i686/drivers/asm/acpi.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef JINUE_KERNEL_INFRASTRUCTURE_I686_DRIVERS_ASM_ACPI_H
+#define JINUE_KERNEL_INFRASTRUCTURE_I686_DRIVERS_ASM_ACPI_H
+
+#define ACPI_BDA_EBDA       0x40e
+
+#define ACPI_V1_REVISION    0
+
+#define ACPI_V2_REVISION    2
+
+#define ACPI_V1_RSDP_SIZE   20
+
+#endif
diff --git a/include/kernel/machine/acpi.h b/include/kernel/machine/acpi.h
new file mode 100644
index 00000000..2f95086b
--- /dev/null
+++ b/include/kernel/machine/acpi.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef JINUE_KERNEL_MACHINE_ACPI_H
+#define JINUE_KERNEL_MACHINE_ACPI_H
+
+#include <jinue/shared/types.h>
+#include <stdint.h>
+
+void machine_set_acpi_tables(const jinue_acpi_tables_t *tables);
+
+#endif
+
diff --git a/include/kernel/machine/auxv.h b/include/kernel/machine/auxv.h
index 2ef4add1..b04c6eb0 100644
--- a/include/kernel/machine/auxv.h
+++ b/include/kernel/machine/auxv.h
@@ -36,5 +36,7 @@
 
 uint32_t machine_at_howsyscall(void);
 
+uint32_t machine_at_acpi_rsdp(void);
+
 #endif
 
diff --git a/include/kernel/types.h b/include/kernel/types.h
index 14071b39..19293f2c 100644
--- a/include/kernel/types.h
+++ b/include/kernel/types.h
@@ -87,10 +87,17 @@ struct descriptor_t {
     uintptr_t        cookie;
 };
 
+typedef enum {
+    PROCESS_ID_KERNEL,
+    PROCESS_ID_LOADER,
+    PROCESS_ID_USER
+} process_id;
+
 typedef struct {
     object_header_t header;
     addr_space_t    addr_space;
     int             running_threads_count;
+    process_id      id;
     spinlock_t      descriptors_lock;
     descriptor_t    descriptors[JINUE_DESC_NUM];
 } process_t;
diff --git a/kernel/Makefile b/kernel/Makefile
index e7d1b4cf..92614e36 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -63,6 +63,7 @@ sources.kernel.c = \
 	application/interrupts/hardware.c \
 	application/interrupts/spurious.c \
 	application/interrupts/tick.c \
+	application/syscalls/acpi.c \
 	application/syscalls/close.c \
 	application/syscalls/create_endpoint.c \
 	application/syscalls/create_process.c \
@@ -100,10 +101,11 @@ sources.kernel.c = \
 	domain/services/panic.c \
 	domain/services/scheduler.c \
 	domain/config.c \
-	infrastructure/i686/drivers/vga.c \
+	infrastructure/i686/drivers/acpi.c \
 	infrastructure/i686/drivers/pic8259.c \
 	infrastructure/i686/drivers/pit8253.c \
 	infrastructure/i686/drivers/uart16550a.c \
+	infrastructure/i686/drivers/vga.c \
 	infrastructure/i686/pmap/nopae.c \
 	infrastructure/i686/pmap/pmap.c \
 	infrastructure/i686/pmap/pae.c \
diff --git a/kernel/application/kmain.c b/kernel/application/kmain.c
index 673f7c44..7e93b575 100644
--- a/kernel/application/kmain.c
+++ b/kernel/application/kmain.c
@@ -91,6 +91,7 @@ void kmain(const char *cmdline) {
         panic("Could not create initial process.");
     }
 
+    process->id = PROCESS_ID_LOADER;
     process_switch_to(process);
 
     /* create user space loader main thread */
diff --git a/kernel/application/syscalls/acpi.c b/kernel/application/syscalls/acpi.c
new file mode 100644
index 00000000..8660bd12
--- /dev/null
+++ b/kernel/application/syscalls/acpi.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <jinue/shared/asm/errno.h>
+#include <kernel/application/syscalls.h>
+#include <kernel/domain/entities/process.h>
+#include <kernel/machine/acpi.h>
+
+int set_acpi(const jinue_acpi_tables_t *tables) {
+    process_t *process = get_current_process();
+
+    if(process->id != PROCESS_ID_LOADER) {
+        return -JINUE_ENOSYS;
+    }
+
+    machine_set_acpi_tables(tables);
+
+    return 0;
+}
diff --git a/kernel/domain/entities/process.c b/kernel/domain/entities/process.c
index c3c2032a..b4a11173 100644
--- a/kernel/domain/entities/process.c
+++ b/kernel/domain/entities/process.c
@@ -37,6 +37,7 @@
 #include <kernel/machine/atomic.h>
 #include <kernel/machine/pmap.h>
 #include <kernel/machine/process.h>
+#include <kernel/machine/spinlock.h>
 #include <kernel/machine/thread.h>
 #include <stddef.h>
 #include <string.h>
@@ -83,6 +84,7 @@ static slab_cache_t process_cache;
 static void cache_ctor_op(void *buffer, size_t ignore) {
     process_t *process = buffer;
     object_init_header(&process->header, object_type_process);
+    init_spinlock(&process->descriptors_lock);
 }
 
 /**
@@ -121,6 +123,8 @@ process_t *process_new(void) {
     process_t *process = slab_cache_alloc(&process_cache);
 
     if(process != NULL) {
+        process->running_threads_count  = 0;
+        process->id                     = PROCESS_ID_USER;
         initialize_descriptors(process);
 
         if(!machine_init_process(process)) {
diff --git a/kernel/infrastructure/elf.c b/kernel/infrastructure/elf.c
index 6478ee53..63b92862 100644
--- a/kernel/infrastructure/elf.c
+++ b/kernel/infrastructure/elf.c
@@ -456,7 +456,7 @@ static void initialize_stack(
 
     /* Auxiliary vectors */
     Elf32_auxv_t *auxvp = (Elf32_auxv_t *)sp;
-    sp = (uintptr_t *)(auxvp + 8);
+    sp = (uintptr_t *)(auxvp + 9);
 
     auxvp[0].a_type     = JINUE_AT_PHDR;
     auxvp[0].a_un.a_val = (uint32_t)elf_info->at_phdr;
@@ -479,8 +479,11 @@ static void initialize_stack(
     auxvp[6].a_type     = JINUE_AT_HOWSYSCALL;
     auxvp[6].a_un.a_val = machine_at_howsyscall();
 
-    auxvp[7].a_type     = JINUE_AT_NULL;
-    auxvp[7].a_un.a_val = 0;
+    auxvp[7].a_type     = JINUE_AT_ACPI_RSDP;
+    auxvp[7].a_un.a_val = machine_at_acpi_rsdp();
+
+    auxvp[8].a_type     = JINUE_AT_NULL;
+    auxvp[8].a_un.a_val = 0;
 
     /* Write arguments and environment variables (i.e. the actual strings). */
     char *const args = (char *)sp;
diff --git a/kernel/infrastructure/i686/drivers/acpi.c b/kernel/infrastructure/i686/drivers/acpi.c
new file mode 100644
index 00000000..6d9eb81d
--- /dev/null
+++ b/kernel/infrastructure/i686/drivers/acpi.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <kernel/infrastructure/i686/drivers/asm/vga.h>
+#include <kernel/infrastructure/i686/drivers/acpi.h>
+#include <kernel/machine/acpi.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static uint32_t rsdp_paddr = 0;
+
+/**
+ * Verify the checksum of an ACPI data structure
+ *
+ * @param buffer pointer to ACPI data structure
+ * @param buflen size of ACPI data structure
+ * @return true for correct checksum, false for checksum mismatch
+ */
+static bool verify_checksum(const void *buffer, size_t buflen) {
+    uint8_t sum = 0;
+
+    for(int idx = 0; idx < buflen; ++idx) {
+        sum += ((const uint8_t *)buffer)[idx];
+    }
+
+    return sum == 0;
+}
+
+/**
+ * Validate the ACPI RSDP
+ * 
+ * At the stage of the boot process where this function is called, the memory
+ * where the RSDP is located is mapped 1:1 so a pointer to the RSDP has the
+ * same value as its physical address.
+ *
+ * @param rsdp pointer to ACPI RSDP
+ * @return true is RSDP is valid, false otherwise
+ */
+static bool check_rsdp(const acpi_rsdp_t *rsdp) {
+    const char *const signature = "RSD PTR ";
+
+    if(strncmp(rsdp->signature, signature, strlen(signature)) != 0) {
+        return false;
+    }
+
+    if(!verify_checksum(rsdp, ACPI_V1_RSDP_SIZE)) {
+        return false;
+    }
+
+    if(rsdp->revision == ACPI_V1_REVISION) {
+        return true;
+    }
+
+    if(rsdp->revision != ACPI_V2_REVISION) {
+        return false;
+    }
+
+    return verify_checksum(rsdp, sizeof(acpi_rsdp_t));
+}
+
+/**
+ * Find the RSDP in memory
+ * 
+ * At the stage of the boot process where this function is called, the memory
+ * where the RSDP is located is mapped 1:1 so a pointer to the RSDP has the
+ * same value as its physical address.
+ *
+ * @return pointer to RSDP if found, NULL otherwise
+ */
+static const acpi_rsdp_t *find_rsdp(void) {
+    const char *const start = (const char *)0x0e0000;
+    const char *const end   = (const char *)0x100000;
+
+    for(const char *addr = start; addr < end; addr += 16) {
+        const acpi_rsdp_t *rsdp = (const acpi_rsdp_t *)addr;
+
+        if(check_rsdp(rsdp)) {
+            return rsdp;
+        }
+    }
+
+    const char *bottom  = (const char *)0x10000;
+    const char *top     = (const char *)(0xa0000 - 1024);
+    const char *ebda    = (const char *)(16 * (*(uint16_t *)ACPI_BDA_EBDA));
+
+    if(ebda < bottom || ebda > top) {
+        return NULL;
+    }
+
+    for(const char *addr = ebda; addr < ebda + 1024; addr += 16) {
+        const acpi_rsdp_t *rsdp = (const acpi_rsdp_t *)addr;
+
+        if(check_rsdp(rsdp)) {
+            return rsdp;
+        }
+    }
+
+    return NULL;
+}
+
+/**
+ * Initialize ACPI
+ */
+void acpi_init(void) {
+    /* At the stage of the boot process where this function is called, the memory
+     * where the RSDP is located is mapped 1:1 so a pointer to the RSDP has the
+     * same value as its physical address. */
+    rsdp_paddr = (uint32_t)find_rsdp();
+}
+
+/**
+ * Get the physical address of the ACPI RSDP
+ *
+ * @return physical address of RSDP if found, zero otherwise
+ */
+uint32_t acpi_get_rsdp_paddr(void) {
+    return rsdp_paddr;
+}
+
+/**
+ * Process the ACPI tables mapped by user space
+ *
+ * @param tables structure with pointers to ACPI tables
+ */
+void machine_set_acpi_tables(const jinue_acpi_tables_t *tables) {
+    /* TODO implement this */
+}
diff --git a/kernel/infrastructure/i686/init.c b/kernel/infrastructure/i686/init.c
index f445a668..6534db82 100644
--- a/kernel/infrastructure/i686/init.c
+++ b/kernel/infrastructure/i686/init.c
@@ -35,6 +35,7 @@
 #include <kernel/domain/services/logging.h>
 #include <kernel/domain/services/panic.h>
 #include <kernel/infrastructure/i686/asm/msr.h>
+#include <kernel/infrastructure/i686/drivers/acpi.h>
 #include <kernel/infrastructure/i686/drivers/pic8259.h>
 #include <kernel/infrastructure/i686/drivers/pit8253.h>
 #include <kernel/infrastructure/i686/drivers/uart16550a.h>
@@ -373,6 +374,8 @@ void machine_init(const config_t *config) {
     pit8253_init();
     pic8259_unmask(IRQ_TIMER);
 
+    acpi_init();
+
     exec_file_t kernel;
     get_kernel_exec_file(&kernel, bootinfo);
 
diff --git a/kernel/interface/i686/auxv.c b/kernel/interface/i686/auxv.c
index ae535ce0..cc13ef60 100644
--- a/kernel/interface/i686/auxv.c
+++ b/kernel/interface/i686/auxv.c
@@ -29,9 +29,14 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <kernel/infrastructure/i686/drivers/acpi.h>
 #include <kernel/interface/i686/trap.h>
 #include <kernel/machine/auxv.h>
 
 uint32_t machine_at_howsyscall(void) {
     return syscall_implementation;
 }
+
+uint32_t machine_at_acpi_rsdp(void) {
+    return acpi_get_rsdp_paddr();
+}
diff --git a/kernel/interface/syscalls.c b/kernel/interface/syscalls.c
index 56ca7d07..93e26021 100644
--- a/kernel/interface/syscalls.c
+++ b/kernel/interface/syscalls.c
@@ -554,6 +554,20 @@ static void sys_reply_error(jinue_syscall_args_t *args) {
     set_return_value_or_error(args, retval);
 }
 
+static void sys_acpi(jinue_syscall_args_t *args) {
+    const jinue_acpi_tables_t *userspace_tables = (const void *)args->arg1;
+
+    if(! check_userspace_buffer(userspace_tables, sizeof(jinue_acpi_tables_t))) {
+        set_error(args, JINUE_EINVAL);
+        return;
+    }
+
+    const jinue_acpi_tables_t tables = *userspace_tables;
+
+    int retval = set_acpi(&tables);
+    set_return_value_or_error(args, retval);
+}
+
 /**
  * System call dispatching function
  *
@@ -631,6 +645,9 @@ void handle_syscall(jinue_syscall_args_t *args) {
         case JINUE_SYS_REPLY_ERROR:
             sys_reply_error(args);
             break;
+        case JINUE_SYS_SET_ACPI:
+            sys_acpi(args);
+            break;
         default:
             sys_nosys(args);
         }
diff --git a/userspace/lib/jinue/syscalls.c b/userspace/lib/jinue/syscalls.c
index 06ac1b3d..45ca6522 100644
--- a/userspace/lib/jinue/syscalls.c
+++ b/userspace/lib/jinue/syscalls.c
@@ -336,3 +336,14 @@ int jinue_reply_error(uintptr_t errcode, int *perrno) {
 
     return call_with_usual_convention(&args, perrno);
 }
+
+int jinue_set_acpi(jinue_acpi_tables_t *tables, int *perrno) {
+    jinue_syscall_args_t args;
+
+    args.arg0 = JINUE_SYS_SET_ACPI;
+    args.arg1 = (uintptr_t)tables;
+    args.arg2 = 0;
+    args.arg3 = 0;
+
+    return call_with_usual_convention(&args, perrno);
+}
diff --git a/userspace/loader/Makefile b/userspace/loader/Makefile
index ef48d1f7..ff112b62 100644
--- a/userspace/loader/Makefile
+++ b/userspace/loader/Makefile
@@ -31,6 +31,7 @@ jinue_root = ../..
 include $(jinue_root)/header.mk
 
 sources.c        = \
+	acpi/acpi.c \
 	archives/alloc.c \
 	archives/tar.c \
 	binfmt/elf.c \
diff --git a/userspace/loader/acpi/acpi.c b/userspace/loader/acpi/acpi.c
new file mode 100644
index 00000000..4e6bc6be
--- /dev/null
+++ b/userspace/loader/acpi/acpi.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <jinue/jinue.h>
+#include <jinue/utils.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../debug.h"
+#include "acpi.h"
+
+/**
+ * Verify the checksum of an ACPI data structure
+ *
+ * @param buffer pointer to ACPI data structure
+ * @param buflen size of ACPI data structure
+ * @return true for correct checksum, false for checksum mismatch
+ *
+ * */
+static bool verify_checksum(const void *buffer, size_t buflen) {
+    uint8_t sum = 0;
+
+    for(int idx = 0; idx < buflen; ++idx) {
+        sum += ((const uint8_t *)buffer)[idx];
+    }
+
+    return sum == 0;
+}
+
+/**
+ * Verify the checksum of an ACPI table header
+ *
+ * @param header mapped ACPI table header
+ * @param signature expected signature
+ * @return true if the signature matches, false otherwise
+ *
+ * */
+static bool verify_signature(const acpi_header_t *header, const char *signature) {
+    return strncmp(header->signature, signature, sizeof(header->signature)) == 0;
+}
+
+/**
+ * Map an ACPI data structure
+ *
+ * @param paddr physical memory address of data structure
+ * @param size size of data structure
+ * @return pointer to mapped data on success, NULL on error
+ *
+ * */
+static const void *map_size(uint64_t paddr, size_t size) {
+    size_t offset = paddr % JINUE_PAGE_SIZE;
+
+    size    += offset;
+    paddr   -= offset;
+
+    void *mapped = mmap(NULL, size, PROT_READ, MAP_SHARED, 0, paddr);
+
+    if(mapped == MAP_FAILED) {
+        return NULL;
+    }
+
+    return (const char *)mapped + offset;
+}
+
+/**
+ * Map ACPI RSDP
+ * 
+ * We don't validate the contents (checksum, revision) because we assume the
+ * kernel has done so before setting the address in the auxiliary vector.
+ *
+ * @param paddr physical memory address of ACPI RSDP
+ * @return pointer to mapped RSDP on success, NULL on error
+ *
+ * */
+static const acpi_rsdp_t *map_rsdp(uint64_t paddr) {
+    const acpi_rsdp_t *rsdp = map_size(paddr, ACPI_V1_RSDP_SIZE);
+
+    if(rsdp == NULL || rsdp->revision == ACPI_V1_REVISION) {
+        return rsdp;
+    }
+
+    size_t offset = paddr % JINUE_PAGE_SIZE;
+
+    if(JINUE_PAGE_SIZE - offset >= sizeof(acpi_rsdp_t)) {
+        return rsdp;
+    }
+
+    /* Here, we rely on the fact that our implementation of mmap() allocates
+     * virtual memory sequentially to simply extend the existing mapping. */
+    size_t extsize          = sizeof(acpi_rsdp_t) - (JINUE_PAGE_SIZE - offset);
+    const void *extension   = map_size(paddr - offset + JINUE_PAGE_SIZE, extsize);
+
+    if(extension == NULL) {
+        return NULL;
+    }
+    
+    return rsdp;
+}
+
+/**
+ * Map ACPI table header
+ *
+ * @param paddr physical memory address of ACPI table header
+ * @return pointer to mapped header on success, NULL on error
+ *
+ * */
+static const acpi_header_t *map_header(uint64_t paddr) {
+    return map_size(paddr, sizeof(acpi_header_t));
+}
+
+/**
+ * Extend the existing mapping of a table header to the full table
+ * 
+ * This function relies on the fact that our implementation of mmap() allocates
+ * virtual memory sequentially to extend an existing mapping. It assumes that
+ * mmap() wasn't called since the call to map_header() that mapped the table
+ * header passed as agument.
+ *
+ * @param paddr physical memory address of the ACPI table
+ * @param header mapped ACPI table header
+ * @param name table name, for log messages
+ * @return pointer to mapped table on success, NULL on error
+ *
+ * */
+static const void *map_table(uint64_t paddr, const acpi_header_t *header, const char *name) {
+    if(header->length < sizeof(acpi_header_t)) {
+        jinue_warning("Value of ACPI table length member is too small (%" PRIu32 ", %s)", name);
+        return NULL;
+    }
+
+    if(header->length > ACPI_TABLE_MAX_SIZE) {
+        jinue_warning("Value of ACPI table length member is too large (%" PRIu32 ", %s)", name);
+        return NULL;
+    }
+
+    size_t offset       = paddr % JINUE_PAGE_SIZE;
+    size_t allocated    = JINUE_PAGE_SIZE - offset;
+
+    if(allocated < sizeof(acpi_header_t)) {
+        allocated += JINUE_PAGE_SIZE;
+    }
+
+    if(header->length > allocated) {
+        /* Here, we rely on the fact that our implementation of mmap() allocates
+         * virtual memory sequentially to simply extend the existing mapping. */
+        size_t extsize          = header->length - allocated;
+        const void *extension   = map_size(paddr + allocated, extsize);
+
+        if(extension == NULL) {
+            jinue_warning("Failed mapping ACPI table (%s)", name);
+            return NULL;
+        }
+    }
+
+    if(! verify_checksum(header, header->length)) {
+        jinue_warning("ACPI table checksum mismatch (%s)", name);
+        return NULL;
+    }
+    
+    return header;
+}
+
+/* Size of the fixed part of the RSDT, excluding hte entries. */
+#define RSDT_BASE_SIZE ((size_t)(const char *)&(((const acpi_rsdt_t *)0)->entries))
+
+/**
+ * Map the RSDT/XSDT
+ *
+ * @param paddr physical memory address of RSDT/XSDT
+ * @param is_xsdt whether the table is a XSDT (true) or a RSDT (false)
+ * @return mapped RSDT/XSDT on success, NULL on error
+ *
+ * */
+static const acpi_rsdt_t *map_rsdt(uint64_t paddr, bool is_xsdt) {
+    const acpi_header_t *header = map_header(paddr);
+
+    if(header == NULL) {
+        return NULL;
+    }
+
+    const char *const signature = is_xsdt ? "XSDT" : "RSDT";
+
+    if(! verify_signature(header, signature)) {
+        jinue_warning("Signature mismatch for ACPI %s", signature);
+        return NULL;
+    }
+
+    if(header->length < RSDT_BASE_SIZE) {
+        jinue_warning("ACPI %s table is too small", signature);
+        return NULL;
+    }
+
+    return map_table(paddr, header, signature);
+}
+
+/**
+ * Process the entries of the mapped RSDT/XSDT to find relevant tables
+ *
+ * @param tables tables structure (output)
+ * @param rsdt mapped RSDT/XSDT
+ * @param is_xsdt whether the table is a XSDT (true) or RSDT (false)
+ *
+ * */
+void process_rsdt(jinue_acpi_tables_t *tables, const acpi_rsdt_t *rsdt, bool is_xsdt) {
+    size_t entries = (rsdt->length - RSDT_BASE_SIZE) / sizeof(uint32_t);
+
+    if(is_xsdt && entries % 2 != 0) {
+        --entries;
+    }
+
+    for(int idx = 0; idx < entries; ++idx) {
+        /* x86 is little endian */
+        uint64_t paddr = rsdt->entries[idx];
+
+        if(is_xsdt) {
+            paddr |= ((uint64_t)rsdt->entries[++idx]) << 32;
+        }
+
+        const acpi_header_t *header = map_header(paddr);
+
+        if(header == NULL) {
+            continue;
+        }
+
+        const char *signature = "FACP";
+
+        if(verify_signature(header, signature) && tables->fadt == NULL) {
+            tables->fadt = map_table(paddr, header, "FADT");
+        }
+
+        signature = "APIC";
+
+        if(verify_signature(header, signature) && tables->madt == NULL) {
+            tables->madt = map_table(paddr, header, "MADT");
+        }
+    }
+}
+
+/**
+ * Map the RSDT/XSDT and then iterate over its entries to find relevant tables
+ *
+ * @param tables tables structure (output)
+ * @param paddr physical memory address of RSDT/XSDT
+ * @param is_xsdt whether the table is a XSDT (true) or RSDT (false)
+ *
+ * */
+static void load_rsdt(jinue_acpi_tables_t *tables, uint64_t paddr, bool is_xsdt) {
+    const acpi_rsdt_t *rsdt = map_rsdt(paddr, is_xsdt);
+
+    if(rsdt == NULL) {
+        return;
+    }
+
+    tables->rsdt = rsdt;
+    process_rsdt(tables, rsdt, is_xsdt);
+}
+
+/**
+ * Map the RSDP/XSDT and then call load_rsdt() with the RSDT/XSDT address
+ *
+ * @param tables tables structure (output)
+ * @param rsdp_paddr physical memory address of the RSDP
+ *
+ * */
+static void load_rsdp(jinue_acpi_tables_t *tables, uint32_t rsdp_paddr) {
+    const acpi_rsdp_t *rsdp = map_rsdp(rsdp_paddr);
+
+    if(rsdp == NULL) {
+        return;
+    }
+
+    uint64_t rsdt_paddr;
+    bool is_xsdt;
+
+    if(rsdp->revision == ACPI_V1_REVISION) {
+        rsdt_paddr  = rsdp->rsdt_address;
+        is_xsdt     = false;
+    } else {
+        /* TODO handle the case where the address > 4GB and PAE is disabled. */
+        rsdt_paddr  = rsdp->xsdt_address;
+        is_xsdt     = true;
+    }
+
+    load_rsdt(tables, rsdt_paddr, is_xsdt);
+}
+
+/**
+ * Map relevant ACPI tables and report to kernel
+ * 
+ * Map the ACPI tables needed by the kernel in memory, set the pointers to them
+ * in a tables structure (jinue_acpi_tables_t) and call the kernel with this
+ * information.
+ *
+ * @return EXIT_SUCCESS on success, EXIT_FAILURE on error
+ *
+ * */
+int load_acpi_tables(void) {
+    jinue_acpi_tables_t tables;
+    tables.rsdt = NULL;
+    tables.fadt = NULL;
+    tables.madt = NULL;
+
+    uint32_t rsdp_paddr = getauxval(JINUE_AT_ACPI_RSDP);
+
+    /* If the kernel set this auxiliary vector to zero, it knows the RSDP is
+     * nowhere to be found and doesn't expect to be called. Since this is
+     * expected, it is not a failure (i.e. we return EXIT_SUCCESS).
+     * 
+     * In any other situation, the kernel does expect to be called with our
+     * best effort to map the tables so it can complete its initialization and
+     * it will deal with NULL entries in the tables structure if need be. */
+    if(rsdp_paddr == 0) {
+        return EXIT_SUCCESS;
+    }
+
+    load_rsdp(&tables, rsdp_paddr);
+
+    dump_acpi_tables(&tables);
+
+    int status = jinue_set_acpi(&tables, &errno);
+
+    if(status != 0) {
+        jinue_error("error: ACPI call failed: %s", strerror(errno));
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/userspace/loader/acpi/acpi.h b/userspace/loader/acpi/acpi.h
new file mode 100644
index 00000000..b820b9ef
--- /dev/null
+++ b/userspace/loader/acpi/acpi.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LOADER_ACPI_ACPI_H_
+#define LOADER_ACPI_ACPI_H_
+
+#include <stdint.h>
+
+#define ACPI_V1_REVISION    0
+
+#define ACPI_V1_RSDP_SIZE   20
+
+/* Arbitrary value expected to be large enough to accomodate any real table
+ * while ensuring we don't create arbitrary large mappings because of garbage
+ * data in length members. */
+#define ACPI_TABLE_MAX_SIZE (32 * 1024)
+
+typedef struct {
+    char        signature[8];
+    uint8_t     checksum;
+    char        oemid[6];
+    uint8_t     revision;
+    uint32_t    rsdt_address;
+    uint32_t    length;
+    uint64_t    xsdt_address;
+    uint8_t     extended_checksum;
+    uint8_t     reserved[3];
+} acpi_rsdp_t;
+
+typedef struct {
+    char        signature[4];
+    uint32_t    length;
+    uint8_t     revision;
+    uint8_t     checksum;
+    /* There are more fields but we are not interested in them. */
+} acpi_header_t;
+
+typedef struct {
+    char        signature[4];
+    uint32_t    length;
+    uint8_t     revision;
+    uint8_t     checksum;
+    char        oemid[6];
+    char        oem_table_id[8];
+    uint32_t    oem_revision;
+    uint32_t    creator_id;
+    uint32_t    creator_revision;
+    uint32_t    entries[];
+} acpi_rsdt_t;
+
+int load_acpi_tables(void);
+
+#endif
diff --git a/userspace/loader/binfmt/elf.c b/userspace/loader/binfmt/elf.c
index 386401eb..b60f1b22 100644
--- a/userspace/loader/binfmt/elf.c
+++ b/userspace/loader/binfmt/elf.c
@@ -443,7 +443,7 @@ static void initialize_stack(
 
     /* Auxiliary vectors */
     Elf32_auxv_t *auxvp = (Elf32_auxv_t *)&wlocal[index];
-    index += 8 * sizeof(auxvp[0]) / sizeof(wlocal[0]);
+    index += 9 * sizeof(auxvp[0]) / sizeof(wlocal[0]);
 
     auxvp[0].a_type     = JINUE_AT_PHDR;
     auxvp[0].a_un.a_val = (uint32_t)elf_info->at_phdr;
@@ -466,8 +466,11 @@ static void initialize_stack(
     auxvp[6].a_type     = JINUE_AT_HOWSYSCALL;
     auxvp[6].a_un.a_val = getauxval(JINUE_AT_HOWSYSCALL);
 
-    auxvp[7].a_type     = JINUE_AT_NULL;
-    auxvp[7].a_un.a_val = 0;
+    auxvp[7].a_type     = JINUE_AT_ACPI_RSDP;
+    auxvp[7].a_un.a_val = getauxval(JINUE_AT_ACPI_RSDP);
+
+    auxvp[8].a_type     = JINUE_AT_NULL;
+    auxvp[8].a_un.a_val = 0;
 
     char *const args = (char *)&wlocal[index];
 
diff --git a/userspace/loader/debug.c b/userspace/loader/debug.c
index f800585a..08fa7efc 100644
--- a/userspace/loader/debug.c
+++ b/userspace/loader/debug.c
@@ -29,10 +29,9 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <jinue/jinue.h>
-#include <jinue/loader.h>
 #include <jinue/utils.h>
 #include <inttypes.h>
+#include "acpi/acpi.h"
 #include "debug.h"
 #include "utils.h"
 
@@ -116,3 +115,27 @@ void dump_ramdisk(const jinue_dirent_t *root) {
         dirent = jinue_dirent_get_next(dirent);
     }
 }
+
+static void dump_table(const acpi_header_t *table, const char *name) {
+    jinue_info("  %s:", name);
+    jinue_info("    address:  %#p", table);
+
+    if(table != NULL) {
+        jinue_info("    revision: %" PRIu8, table->revision);
+        jinue_info("    length:   %" PRIu32, table->length);
+    }
+}
+
+void dump_acpi_tables(const jinue_acpi_tables_t *tables) {
+    if(! bool_getenv("DEBUG_DUMP_ACPI_TABLES")) {
+        return;
+    }
+
+    const acpi_header_t *rsdt   = tables->rsdt; 
+    const char *rsdt_name       = (rsdt != NULL && rsdt->signature[0] == 'X') ? "XSDT" : "RSDT";
+
+    jinue_info("ACPI tables:");
+    dump_table(rsdt, rsdt_name);
+    dump_table(tables->fadt, "FADT");
+    dump_table(tables->madt, "MADT");
+}
diff --git a/userspace/loader/debug.h b/userspace/loader/debug.h
index 61c9c568..49f74887 100644
--- a/userspace/loader/debug.h
+++ b/userspace/loader/debug.h
@@ -32,6 +32,11 @@
 #ifndef LOADER_DEBUG_H_
 #define LOADER_DEBUG_H_
 
+#include <jinue/jinue.h>
+#include <jinue/loader.h>
+
 void dump_ramdisk(const jinue_dirent_t *root);
 
+void dump_acpi_tables(const jinue_acpi_tables_t *tables);
+
 #endif
diff --git a/userspace/loader/loader.c b/userspace/loader/loader.c
index 328e1cbb..db8e6ff8 100644
--- a/userspace/loader/loader.c
+++ b/userspace/loader/loader.c
@@ -35,6 +35,7 @@
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
+#include "acpi/acpi.h"
 #include "binfmt/elf.h"
 #include "core/meminfo.h"
 #include "core/server.h"
@@ -176,14 +177,14 @@ static int start_initial_thread(thread_params_t *thread_params) {
         &errno
     );
 
-    if (status != 0) {
+    if(status != 0) {
         jinue_error("error: could not start thread: %s", strerror(errno));
         return EXIT_FAILURE;
     }
 
     status = jinue_close(INIT_THREAD_DESCRIPTOR, &errno);
 
-    if (status != 0) {
+    if(status != 0) {
         jinue_error("error: could not close thread descriptor: %s", strerror(errno));
         return EXIT_FAILURE;
     }
@@ -194,6 +195,12 @@ static int start_initial_thread(thread_params_t *thread_params) {
 int main(int argc, char *argv[]) {
     jinue_info("Jinue user space loader (%s) started.", argv[0]);
 
+    int status = load_acpi_tables();
+
+    if(status != EXIT_SUCCESS) {
+        return status;
+    }
+
     initialize_meminfo();
 
     char map_buffer[MAP_BUFFER_SIZE];
@@ -205,7 +212,7 @@ int main(int argc, char *argv[]) {
 
     ramdisk_t ramdisk;
 
-    int status = map_ramdisk(&ramdisk, map);
+    status = map_ramdisk(&ramdisk, map);
 
     if(status != EXIT_SUCCESS) {
         return status;
diff --git a/userspace/testapp/Makefile b/userspace/testapp/Makefile
index af073dd4..873dde6c 100644
--- a/userspace/testapp/Makefile
+++ b/userspace/testapp/Makefile
@@ -30,7 +30,7 @@
 jinue_root = ../..
 include $(jinue_root)/header.mk
 
-sources.c       	 = tests/ipc.c debug.c testapp.c utils.c
+sources.c       	 = tests/acpi.c tests/ipc.c debug.c testapp.c utils.c
 testapp         	 = testapp
 stripped             = $(testapp)-stripped
 temp_ramdisk_fs		 = ramdisk-tmp
diff --git a/userspace/testapp/debug.c b/userspace/testapp/debug.c
index e7af154a..ca8a94df 100644
--- a/userspace/testapp/debug.c
+++ b/userspace/testapp/debug.c
@@ -88,6 +88,7 @@ static const char *auxv_type_name(int type) {
             {"AT_ENTRY",        JINUE_AT_ENTRY},
             {"AT_STACKBASE",    JINUE_AT_STACKBASE},
             {"AT_HOWSYSCALL",   JINUE_AT_HOWSYSCALL},
+            {"AT_ACPI_RSDP",    JINUE_AT_ACPI_RSDP},
             {NULL, 0}
     };
 
diff --git a/userspace/testapp/testapp.c b/userspace/testapp/testapp.c
index f84b0ec9..80333a56 100644
--- a/userspace/testapp/testapp.c
+++ b/userspace/testapp/testapp.c
@@ -37,6 +37,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
+#include "tests/acpi.h"
 #include "tests/ipc.h"
 #include "debug.h"
 #include "utils.h"
@@ -61,6 +62,7 @@ int main(int argc, char *argv[]) {
         return EXIT_FAILURE;
     }
 
+    run_acpi_test();
     run_ipc_test();
 
     if(bool_getenv("DEBUG_DO_REBOOT")) {
diff --git a/userspace/testapp/tests/acpi.c b/userspace/testapp/tests/acpi.c
new file mode 100644
index 00000000..d1219d83
--- /dev/null
+++ b/userspace/testapp/tests/acpi.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <jinue/jinue.h>
+#include <jinue/utils.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../utils.h"
+#include "acpi.h"
+
+void run_acpi_test(void) {
+    if(! bool_getenv("RUN_TEST_ACPI")) {
+        return;
+    }
+
+    jinue_info("Running ACPI test...");
+
+    jinue_acpi_tables_t tables;
+    tables.rsdt = NULL;
+    tables.fadt = NULL;
+    tables.madt = NULL;
+
+    int status = jinue_set_acpi(&tables, &errno);
+
+    if(status >= 0) {
+        jinue_error("error: jinue_set_acpi() unexpectedly succeeded");
+        return;
+    }
+
+    if(errno != JINUE_ENOSYS) {
+        jinue_error("error: jinue_set_acpi() failed: %s.", strerror(errno));
+        return;
+    }
+
+    jinue_info("expected: jinue_set_acpi() set errno to: %s.", strerror(errno));
+}
diff --git a/userspace/testapp/tests/acpi.h b/userspace/testapp/tests/acpi.h
new file mode 100644
index 00000000..bf7c69a0
--- /dev/null
+++ b/userspace/testapp/tests/acpi.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 Philippe Aubertin.
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TESTAPP_TEST_ACPI_H_
+#define TESTAPP_TEST_ACPI_H_
+
+void run_acpi_test(void);
+
+#endif