diff --git a/.gitmodules b/.gitmodules
index e9b7384016967ee4a8b9e60e6300f3496a4d51de..afeb335bd376cfcbe7dbc363d3b1a9237227dedc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,10 +1,11 @@
 [submodule "syscall"]
 	path = syscall
 	url = https://gitlab.redox-os.org/redox-os/syscall.git
+	branch = aarch64-rebase
 [submodule "slab_allocator"]
 	path = slab_allocator
 	url = https://gitlab.redox-os.org/redox-os/slab_allocator
 [submodule "rmm"]
 	path = rmm
 	url = https://gitlab.redox-os.org/redox-os/rmm.git
-	branch = master
+	branch = aarch64-rebase
diff --git a/Cargo.lock b/Cargo.lock
index 289003f9efeada0c8d2dd6e2bc13c4cfaebaa467..f72e5ac793739d757bad8cc09cc66cb34511ac99 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,234 +4,263 @@
 name = "bit_field"
 version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
+
+[[package]]
+name = "bitfield"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
 
 [[package]]
 name = "bitflags"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "cc"
-version = "1.0.66"
+version = "1.0.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
 
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "fdt"
+version = "0.1.0"
+source = "git+https://gitlab.redox-os.org/thomhuds/fdt.git#baca9b0070c281dc99521ee901efcb10e5f84218"
+dependencies = [
+ "byteorder",
+]
 
 [[package]]
 name = "goblin"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d20fd25aa456527ce4f544271ae4fea65d2eda4a6561ea56f39fb3ee4f7e3884"
 dependencies = [
- "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "scroll 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "plain",
+ "scroll",
 ]
 
 [[package]]
 name = "kernel"
 version = "0.2.5"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "goblin 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "linked_list_allocator 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "raw-cpuid 8.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.2.7",
- "rmm 0.1.0",
- "rustc-demangle 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab_allocator 0.3.1",
- "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "x86 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitfield",
+ "bitflags",
+ "byteorder",
+ "cc",
+ "fdt",
+ "goblin",
+ "linked_list_allocator 0.8.11",
+ "log",
+ "paste",
+ "raw-cpuid 8.1.2",
+ "redox_syscall",
+ "rmm",
+ "rustc-cfg",
+ "rustc-demangle",
+ "slab_allocator",
+ "spin 0.5.2",
+ "x86",
 ]
 
 [[package]]
 name = "linked_list_allocator"
 version = "0.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47de1a43fad0250ee197e9e124e5b5deab3d7b39d4428ae8a6d741ceb340c362"
 dependencies = [
- "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "spin 0.5.2",
 ]
 
 [[package]]
 name = "linked_list_allocator"
 version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "822add9edb1860698b79522510da17bef885171f75aa395cff099d770c609c24"
 dependencies = [
- "spinning_top 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "spinning_top",
 ]
 
 [[package]]
 name = "lock_api"
 version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
 dependencies = [
- "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scopeguard",
 ]
 
 [[package]]
 name = "log"
 version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
 dependencies = [
- "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
 ]
 
 [[package]]
 name = "paste"
 version = "0.1.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
 dependencies = [
- "paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "paste-impl",
+ "proc-macro-hack",
 ]
 
 [[package]]
 name = "paste-impl"
 version = "0.1.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
 dependencies = [
- "proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack",
 ]
 
 [[package]]
 name = "plain"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
 
 [[package]]
 name = "proc-macro-hack"
 version = "0.5.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
 
 [[package]]
 name = "raw-cpuid"
 version = "7.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beb71f708fe39b2c5e98076204c3cc094ee5a4c12c4cdb119a2b72dc34164f41"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "cc",
+ "rustc_version",
 ]
 
 [[package]]
 name = "raw-cpuid"
 version = "8.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fdf7d9dbd43f3d81d94a49c1c3df73cc2b3827995147e6cf7f89d4ec5483e73"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "cc",
+ "rustc_version",
 ]
 
 [[package]]
 name = "redox_syscall"
 version = "0.2.7"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
 ]
 
 [[package]]
 name = "rmm"
 version = "0.1.0"
 
+[[package]]
+name = "rustc-cfg"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56a596b5718bf5e059d59a30af12f7f462a152de147aa462b70892849ee18704"
+
 [[package]]
 name = "rustc-demangle"
-version = "0.1.18"
+version = "0.1.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce"
 
 [[package]]
 name = "rustc_version"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
 dependencies = [
- "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "semver",
 ]
 
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
 name = "scroll"
 version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec"
 
 [[package]]
 name = "semver"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
 dependencies = [
- "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "semver-parser",
 ]
 
 [[package]]
 name = "semver-parser"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
 [[package]]
 name = "slab_allocator"
 version = "0.3.1"
 dependencies = [
- "linked_list_allocator 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "linked_list_allocator 0.6.6",
+ "spin 0.4.10",
 ]
 
 [[package]]
 name = "spin"
 version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f"
 
 [[package]]
 name = "spin"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
 [[package]]
 name = "spinning_top"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "047031d6df5f5ae0092c97aa4f6bb04cfc9c081b4cd4cb9cdb38657994279a00"
 dependencies = [
- "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lock_api",
 ]
 
 [[package]]
 name = "x86"
 version = "0.32.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cc872a9a776500ccc6f49799729858738c946b8865fa7e3d6b47cc5dc3a8a7"
 dependencies = [
- "bit_field 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "raw-cpuid 7.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[metadata]
-"checksum bit_field 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
-"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-"checksum cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
-"checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-"checksum goblin 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d20fd25aa456527ce4f544271ae4fea65d2eda4a6561ea56f39fb3ee4f7e3884"
-"checksum linked_list_allocator 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "47de1a43fad0250ee197e9e124e5b5deab3d7b39d4428ae8a6d741ceb340c362"
-"checksum linked_list_allocator 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "822add9edb1860698b79522510da17bef885171f75aa395cff099d770c609c24"
-"checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
-"checksum log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-"checksum paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
-"checksum paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
-"checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
-"checksum proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
-"checksum raw-cpuid 7.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "beb71f708fe39b2c5e98076204c3cc094ee5a4c12c4cdb119a2b72dc34164f41"
-"checksum raw-cpuid 8.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1fdf7d9dbd43f3d81d94a49c1c3df73cc2b3827995147e6cf7f89d4ec5483e73"
-"checksum rustc-demangle 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
-"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-"checksum scroll 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec"
-"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-"checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f"
-"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-"checksum spinning_top 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "047031d6df5f5ae0092c97aa4f6bb04cfc9c081b4cd4cb9cdb38657994279a00"
-"checksum x86 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8cc872a9a776500ccc6f49799729858738c946b8865fa7e3d6b47cc5dc3a8a7"
+ "bit_field",
+ "bitflags",
+ "raw-cpuid 7.0.4",
+]
diff --git a/Cargo.toml b/Cargo.toml
index c99544d7e4cc0531881bdfad230f7b320209897a..8eaecf624ad305b843d5621cfa5b05a56d42264c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,12 @@ name = "kernel"
 path = "src/lib.rs"
 crate-type = ["staticlib"]
 
+[build-dependencies]
+cc = "1.0.3"
+rustc-cfg = "0.3.0"
+
 [dependencies]
+bitfield = "0.13.1"
 bitflags = "1.2.1"
 linked_list_allocator = "0.8.4"
 log = { version = "0.4" }
@@ -28,12 +33,16 @@ features = ["elf32", "elf64"]
 version = "0.1.16"
 default-features = false
 
+[target.'cfg(target_arch = "aarch64")'.dependencies]
+byteorder = { version = "1", default-features = false }
+fdt = { git = "https://gitlab.redox-os.org/thomhuds/fdt.git", default-features = false }
+
 [target.'cfg(target_arch = "x86_64")'.dependencies]
 raw-cpuid = "8.0.0"
 x86 = { version = "0.32.0", default-features = false }
 
 [features]
-default = ["acpi", "multi_core", "serial_debug"]
+default = ["serial_debug"]
 acpi = []
 doc = []
 graphical_debug = []
diff --git a/build.rs b/build.rs
index a0f1d4d5b9ee1042bfee77d5d42da447b69d007d..249c9e30924a4efedfc826f3afd856198d0507a5 100644
--- a/build.rs
+++ b/build.rs
@@ -1,3 +1,4 @@
+use rustc_cfg::Cfg;
 use std::collections::HashMap;
 use std::env;
 use std::fs;
@@ -158,4 +159,14 @@ mod gen {
 ",
     )
     .unwrap();
+
+    // Build pre kstart init asm code for aarch64
+    let cfg = Cfg::new(env::var_os("TARGET").unwrap()).unwrap();
+    if cfg.target_arch == "aarch64" {
+        println!("cargo:rerun-if-changed=src/arch/aarch64/init/pre_kstart/early_init.S");
+        cc::Build::new()
+            .file("src/arch/aarch64/init/pre_kstart/early_init.S")
+            .target("aarch64-unknown-redox")
+            .compile("early_init");
+    }
 }
diff --git a/linkers/aarch64.ld b/linkers/aarch64.ld
new file mode 100644
index 0000000000000000000000000000000000000000..bb23c805b59b80b04c405a6217b1e83ad9b776d7
--- /dev/null
+++ b/linkers/aarch64.ld
@@ -0,0 +1,60 @@
+ENTRY(early_init)
+OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
+
+KERNEL_OFFSET = 0xffffff0000000000;
+
+SECTIONS {
+    . = KERNEL_OFFSET;
+
+    . += SIZEOF_HEADERS;
+    . = ALIGN(4096);
+
+    .text : AT(ADDR(.text) - KERNEL_OFFSET) {
+        __text_start = .;
+	*(.early_init.text*)
+	. = ALIGN(4096);
+        *(.text*)
+	. = ALIGN(4096);
+        __text_end = .;
+    }
+
+	.rodata : AT(ADDR(.rodata) - KERNEL_OFFSET) {
+        __rodata_start = .;
+        *(.rodata*)
+	. = ALIGN(4096);
+        __rodata_end = .;
+    }
+
+    .data : AT(ADDR(.data) - KERNEL_OFFSET) {
+        __data_start = .;
+        *(.data*)
+	. = ALIGN(4096);
+        __data_end = .;
+        __bss_start = .;
+        *(.bss*)
+	. = ALIGN(4096);
+        __bss_end = .;
+    }
+
+    .tdata : AT(ADDR(.tdata) - KERNEL_OFFSET) {
+        __tdata_start = .;
+        *(.tdata*)
+	. = ALIGN(4096);
+        __tdata_end = .;
+        __tbss_start = .;
+        *(.tbss*)
+        . += 8;
+	. = ALIGN(4096);
+        __tbss_end = .;
+    }
+
+    __end = .;
+
+    /DISCARD/ : {
+        *(.comment*)
+        *(.eh_frame*)
+        *(.gcc_except_table*)
+        *(.note*)
+        *(.rel.eh_frame*)
+    }
+}
diff --git a/src/allocator/linked_list.rs b/src/allocator/linked_list.rs
index 0b18ac72a0116bd410191d727800c1a6dbaf8045..1c8acb601b4cd8d2fc38269d6613b98b23f89058 100644
--- a/src/allocator/linked_list.rs
+++ b/src/allocator/linked_list.rs
@@ -3,7 +3,7 @@ use core::ptr::{self, NonNull};
 use linked_list_allocator::Heap;
 use spin::Mutex;
 
-use crate::paging::ActivePageTable;
+use crate::paging::{ActivePageTable, PageTableType};
 
 static HEAP: Mutex<Option<Heap>> = Mutex::new(None);
 
@@ -32,7 +32,7 @@ unsafe impl GlobalAlloc for Allocator {
                         panic!("__rust_allocate: heap not initialized");
                     };
 
-                    super::map_heap(&mut ActivePageTable::new(), crate::KERNEL_HEAP_OFFSET + size, crate::KERNEL_HEAP_SIZE);
+                    super::map_heap(&mut ActivePageTable::new(PageTableType::Kernel), crate::KERNEL_HEAP_OFFSET + size, crate::KERNEL_HEAP_SIZE);
 
                     if let Some(ref mut heap) = *HEAP.lock() {
                         heap.extend(crate::KERNEL_HEAP_SIZE);
diff --git a/src/arch/aarch64/consts.rs b/src/arch/aarch64/consts.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a34871411079e066c348d30739b0a3cf762f5100
--- /dev/null
+++ b/src/arch/aarch64/consts.rs
@@ -0,0 +1,113 @@
+// Because the memory map is so important to not be aliased, it is defined here, in one place
+// The lower 256 PML4 entries are reserved for userspace
+// Each PML4 entry references up to 512 GB of memory
+// The top (511) PML4 is reserved for recursive mapping
+// The second from the top (510) PML4 is reserved for the kernel
+    /// The size of a single PML4
+    pub const PML4_SIZE: usize = 0x0000_0080_0000_0000;
+    pub const PML4_MASK: usize = 0x0000_ff80_0000_0000;
+
+    /// Size of a page and frame
+    pub const PAGE_SIZE: usize = 4096;
+
+    /// Offset of recursive paging
+    pub const RECURSIVE_PAGE_OFFSET: usize = (-(PML4_SIZE as isize)) as usize;
+    pub const RECURSIVE_PAGE_PML4: usize = (RECURSIVE_PAGE_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset of kernel
+    pub const KERNEL_OFFSET: usize = RECURSIVE_PAGE_OFFSET - PML4_SIZE;
+    pub const KERNEL_PML4: usize = (KERNEL_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Kernel stack size - must be kept in sync with early_init.S. Used by memory::init
+    pub const KERNEL_STACK_SIZE: usize = PAGE_SIZE;
+
+    /// Offset to kernel heap
+    pub const KERNEL_HEAP_OFFSET: usize = KERNEL_OFFSET - PML4_SIZE;
+    pub const KERNEL_HEAP_PML4: usize = (KERNEL_HEAP_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Size of kernel heap
+    pub const KERNEL_HEAP_SIZE: usize = 1 * 1024 * 1024; // 1 MB
+
+    /// Offset of device map region
+    pub const KERNEL_DEVMAP_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE;
+
+    /// Offset of environment region
+    pub const KERNEL_ENV_OFFSET: usize = KERNEL_DEVMAP_OFFSET - PML4_SIZE;
+
+    /// Offset of temporary mapping for misc kernel bring-up actions
+    pub const KERNEL_TMP_MISC_OFFSET: usize = KERNEL_ENV_OFFSET - PML4_SIZE;
+
+    /// Offset of FDT DTB image
+    pub const KERNEL_DTB_OFFSET: usize = KERNEL_TMP_MISC_OFFSET - PML4_SIZE;
+    pub const KERNEL_DTB_MAX_SIZE: usize = 2 * 1024 * 1024; // 2 MB
+
+    /// Offset to kernel percpu variables
+    //TODO: Use 64-bit fs offset to enable this pub const KERNEL_PERCPU_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE;
+    pub const KERNEL_PERCPU_OFFSET: usize = KERNEL_DTB_OFFSET - PML4_SIZE;
+
+    /// Size of kernel percpu variables
+    pub const KERNEL_PERCPU_SIZE: usize = 64 * 1024; // 64 KB
+
+    /// Offset to user image
+    pub const USER_OFFSET: usize = 0;
+    pub const USER_PML4: usize = (USER_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user TCB
+    pub const USER_TCB_OFFSET: usize = 0xB000_0000;
+
+    /// Offset to user arguments
+    pub const USER_ARG_OFFSET: usize = USER_OFFSET + PML4_SIZE/2;
+
+    /// Offset to user heap
+    pub const USER_HEAP_OFFSET: usize = USER_OFFSET + PML4_SIZE;
+    pub const USER_HEAP_PML4: usize = (USER_HEAP_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user grants
+    pub const USER_GRANT_OFFSET: usize = USER_HEAP_OFFSET + PML4_SIZE;
+    pub const USER_GRANT_PML4: usize = (USER_GRANT_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user stack
+    pub const USER_STACK_OFFSET: usize = USER_GRANT_OFFSET + PML4_SIZE;
+    pub const USER_STACK_PML4: usize = (USER_STACK_OFFSET & PML4_MASK)/PML4_SIZE;
+    /// Size of user stack
+    pub const USER_STACK_SIZE: usize = 1024 * 1024; // 1 MB
+
+    /// Offset to user sigstack
+    pub const USER_SIGSTACK_OFFSET: usize = USER_STACK_OFFSET + PML4_SIZE;
+    pub const USER_SIGSTACK_PML4: usize = (USER_SIGSTACK_OFFSET & PML4_MASK)/PML4_SIZE;
+    /// Size of user sigstack
+    pub const USER_SIGSTACK_SIZE: usize = 256 * 1024; // 256 KB
+
+    /// Offset to user TLS
+    pub const USER_TLS_OFFSET: usize = USER_SIGSTACK_OFFSET + PML4_SIZE;
+    pub const USER_TLS_PML4: usize = (USER_TLS_OFFSET & PML4_MASK)/PML4_SIZE;
+    // Maximum TLS allocated to each PID, should be approximately 8 MB
+    pub const USER_TLS_SIZE: usize = PML4_SIZE / 65536;
+
+    /// Offset to user temporary image (used when cloning)
+    pub const USER_TMP_OFFSET: usize = USER_TLS_OFFSET + PML4_SIZE;
+    pub const USER_TMP_PML4: usize = (USER_TMP_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user temporary heap (used when cloning)
+    pub const USER_TMP_HEAP_OFFSET: usize = USER_TMP_OFFSET + PML4_SIZE;
+    pub const USER_TMP_HEAP_PML4: usize = (USER_TMP_HEAP_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user temporary page for grants
+    pub const USER_TMP_GRANT_OFFSET: usize = USER_TMP_HEAP_OFFSET + PML4_SIZE;
+    pub const USER_TMP_GRANT_PML4: usize = (USER_TMP_GRANT_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user temporary stack (used when cloning)
+    pub const USER_TMP_STACK_OFFSET: usize = USER_TMP_GRANT_OFFSET + PML4_SIZE;
+    pub const USER_TMP_STACK_PML4: usize = (USER_TMP_STACK_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user temporary sigstack (used when cloning)
+    pub const USER_TMP_SIGSTACK_OFFSET: usize = USER_TMP_STACK_OFFSET + PML4_SIZE;
+    pub const USER_TMP_SIGSTACK_PML4: usize = (USER_TMP_SIGSTACK_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset to user temporary tls (used when cloning)
+    pub const USER_TMP_TLS_OFFSET: usize = USER_TMP_SIGSTACK_OFFSET + PML4_SIZE;
+    pub const USER_TMP_TLS_PML4: usize = (USER_TMP_TLS_OFFSET & PML4_MASK)/PML4_SIZE;
+
+    /// Offset for usage in other temporary pages
+    pub const USER_TMP_MISC_OFFSET: usize = USER_TMP_TLS_OFFSET + PML4_SIZE;
+    pub const USER_TMP_MISC_PML4: usize = (USER_TMP_MISC_OFFSET & PML4_MASK)/PML4_SIZE;
diff --git a/src/arch/aarch64/debug.rs b/src/arch/aarch64/debug.rs
new file mode 100644
index 0000000000000000000000000000000000000000..24ff375291bea2bfc65c63b3886ae6b088bfd111
--- /dev/null
+++ b/src/arch/aarch64/debug.rs
@@ -0,0 +1,48 @@
+use core::fmt;
+use spin::MutexGuard;
+
+use crate::log::{LOG, Log};
+
+#[cfg(feature = "serial_debug")]
+use super::device::{
+    serial::COM1,
+    uart_pl011::SerialPort,
+};
+
+pub struct Writer<'a> {
+    log: MutexGuard<'a, Option<Log>>,
+    #[cfg(feature = "serial_debug")]
+    serial: MutexGuard<'a, Option<SerialPort>>,
+}
+
+impl<'a> Writer<'a> {
+    pub fn new() -> Writer<'a> {
+        Writer {
+            log: LOG.lock(),
+            #[cfg(feature = "serial_debug")]
+            serial: COM1.lock(),
+        }
+    }
+
+    pub fn write(&mut self, buf: &[u8]) {
+        {
+            if let Some(ref mut log) = *self.log {
+                log.write(buf);
+            }
+        }
+
+        #[cfg(feature = "serial_debug")]
+        {
+            if let Some(ref mut serial) = *self.serial {
+                serial.write(buf);
+            }
+        }
+    }
+}
+
+impl<'a> fmt::Write for Writer<'a> {
+    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+        self.write(s.as_bytes());
+        Ok(())
+    }
+}
diff --git a/src/arch/aarch64/device/cpu/mod.rs b/src/arch/aarch64/device/cpu/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d13e46afcc9ef0e3414e3a44c0ed5277846d9d9d
--- /dev/null
+++ b/src/arch/aarch64/device/cpu/mod.rs
@@ -0,0 +1,205 @@
+use core::fmt::{Result, Write};
+
+use crate::device::cpu::registers::{control_regs};
+
+pub mod registers;
+
+bitfield! {
+    pub struct MachineId(u32);
+    get_implementer, _: 31, 24;
+    get_variant, _: 23, 20;
+    get_architecture, _: 19, 16;
+    get_part_number, _: 15, 4;
+    get_revision, _: 3, 0;
+}
+
+enum ImplementerID {
+    Unknown,
+    Arm,
+    Broadcom,
+    Cavium,
+    Digital,
+    Infineon,
+    Motorola,
+    Nvidia,
+    AMCC,
+    Qualcomm,
+    Marvell,
+    Intel,
+}
+
+const IMPLEMENTERS: [&'static str; 12] = [
+    "Unknown",
+    "Arm",
+    "Broadcom",
+    "Cavium",
+    "Digital",
+    "Infineon",
+    "Motorola",
+    "Nvidia",
+    "AMCC",
+    "Qualcomm",
+    "Marvell",
+    "Intel",
+];
+
+
+enum VariantID {
+    Unknown,
+}
+
+const VARIANTS: [&'static str; 1] = [
+    "Unknown",
+];
+
+enum ArchitectureID {
+    Unknown,
+    V4,
+    V4T,
+    V5,
+    V5T,
+    V5TE,
+    V5TEJ,
+    V6,
+}
+
+const ARCHITECTURES: [&'static str; 8] = [
+    "Unknown",
+    "v4",
+    "v4T",
+    "v5",
+    "v5T",
+    "v5TE",
+    "v5TEJ",
+    "v6",
+];
+
+enum PartNumberID {
+    Unknown,
+    Thunder,
+    Foundation,
+    CortexA35,
+    CortexA53,
+    CortexA55,
+    CortexA57,
+    CortexA72,
+    CortexA73,
+    CortexA75,
+}
+
+const PART_NUMBERS: [&'static str; 10] = [
+    "Unknown",
+    "Thunder",
+    "Foundation",
+    "Cortex-A35",
+    "Cortex-A53",
+    "Cortex-A55",
+    "Cortex-A57",
+    "Cortex-A72",
+    "Cortex-A73",
+    "Cortex-A75",
+];
+
+enum RevisionID {
+    Unknown,
+    Thunder1_0,
+    Thunder1_1,
+}
+
+const REVISIONS: [&'static str; 3] = [
+    "Unknown",
+    "Thunder-1.0",
+    "Thunder-1.1",
+];
+
+struct CpuInfo {
+    implementer: &'static str,
+    variant: &'static str,
+    architecture: &'static str,
+    part_number: &'static str,
+    revision: &'static str,
+}
+
+impl CpuInfo {
+    fn new() -> CpuInfo {
+        let midr = unsafe { control_regs::midr() };
+        println!("MIDR: 0x{:x}", midr);
+        let midr = MachineId(midr);
+
+        let implementer = match midr.get_implementer() {
+            0x41 => IMPLEMENTERS[ImplementerID::Arm as usize],
+            0x42 => IMPLEMENTERS[ImplementerID::Broadcom as usize],
+            0x43 => IMPLEMENTERS[ImplementerID::Cavium as usize],
+            0x44 => IMPLEMENTERS[ImplementerID::Digital as usize],
+            0x49 => IMPLEMENTERS[ImplementerID::Infineon as usize],
+            0x4d => IMPLEMENTERS[ImplementerID::Motorola as usize],
+            0x4e => IMPLEMENTERS[ImplementerID::Nvidia as usize],
+            0x50 => IMPLEMENTERS[ImplementerID::AMCC as usize],
+            0x51 => IMPLEMENTERS[ImplementerID::Qualcomm as usize],
+            0x56 => IMPLEMENTERS[ImplementerID::Marvell as usize],
+            0x69 => IMPLEMENTERS[ImplementerID::Intel as usize],
+            _ => IMPLEMENTERS[ImplementerID::Unknown as usize],
+        };
+
+        let variant = match midr.get_variant() {
+            _ => VARIANTS[VariantID::Unknown as usize],
+        };
+
+        let architecture = match midr.get_architecture() {
+            0b0001 => ARCHITECTURES[ArchitectureID::V4 as usize],
+            0b0010 => ARCHITECTURES[ArchitectureID::V4T as usize],
+            0b0011 => ARCHITECTURES[ArchitectureID::V5 as usize],
+            0b0100 => ARCHITECTURES[ArchitectureID::V5T as usize],
+            0b0101 => ARCHITECTURES[ArchitectureID::V5TE as usize],
+            0b0110 => ARCHITECTURES[ArchitectureID::V5TEJ as usize],
+            0b0111 => ARCHITECTURES[ArchitectureID::V6 as usize],
+            _ => ARCHITECTURES[ArchitectureID::Unknown as usize],
+        };
+
+        let part_number = match midr.get_part_number() {
+            0x0a1 => PART_NUMBERS[PartNumberID::Thunder as usize],
+            0xd00 => PART_NUMBERS[PartNumberID::Foundation as usize],
+            0xd04 => PART_NUMBERS[PartNumberID::CortexA35 as usize],
+            0xd03 => PART_NUMBERS[PartNumberID::CortexA53 as usize],
+            0xd05 => PART_NUMBERS[PartNumberID::CortexA55 as usize],
+            0xd07 => PART_NUMBERS[PartNumberID::CortexA57 as usize],
+            0xd08 => PART_NUMBERS[PartNumberID::CortexA72 as usize],
+            0xd09 => PART_NUMBERS[PartNumberID::CortexA73 as usize],
+            0xd0a => PART_NUMBERS[PartNumberID::CortexA75 as usize],
+            _ => PART_NUMBERS[PartNumberID::Unknown as usize],
+        };
+
+        let revision = match part_number {
+            "Thunder" => {
+                let val = match midr.get_revision() {
+                    0x00 => REVISIONS[RevisionID::Thunder1_0 as usize],
+                    0x01 => REVISIONS[RevisionID::Thunder1_1 as usize],
+                    _ => REVISIONS[RevisionID::Unknown as usize],
+                };
+                val
+            },
+            _ => REVISIONS[RevisionID::Unknown as usize],
+        };
+
+        CpuInfo {
+            implementer,
+            variant,
+            architecture,
+            part_number,
+            revision,
+        }
+    }
+}
+
+pub fn cpu_info<W: Write>(w: &mut W) -> Result {
+    let cpuinfo = CpuInfo::new();
+
+    write!(w, "Implementer: {}\n", cpuinfo.implementer)?;
+    write!(w, "Variant: {}\n", cpuinfo.variant)?;
+    write!(w, "Architecture version: {}\n", cpuinfo.architecture)?;
+    write!(w, "Part Number: {}\n", cpuinfo.part_number)?;
+    write!(w, "Revision: {}\n", cpuinfo.revision)?;
+    write!(w, "\n")?;
+
+    Ok(())
+}
diff --git a/src/arch/aarch64/device/cpu/registers/control_regs.rs b/src/arch/aarch64/device/cpu/registers/control_regs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5c02d431e4ef8c797bc3f15776ecf5d8ca14a9a4
--- /dev/null
+++ b/src/arch/aarch64/device/cpu/registers/control_regs.rs
@@ -0,0 +1,85 @@
+//! Functions to read and write control registers.
+
+bitflags! {
+    pub struct MairEl1: u64 {
+        const DEVICE_MEMORY = 0x00;
+        const NORMAL_UNCACHED_MEMORY = 0x44 << 8;
+        const NORMAL_WRITEBACK_MEMORY = 0xff << 16;
+    }
+}
+
+pub unsafe fn ttbr0_el1() -> u64 {
+    let ret: u64;
+    llvm_asm!("mrs $0, ttbr0_el1" : "=r" (ret));
+    ret
+}
+
+pub unsafe fn ttbr0_el1_write(val: u64) {
+    llvm_asm!("msr ttbr0_el1, $0" :: "r" (val) : "memory");
+}
+
+pub unsafe fn ttbr1_el1() -> u64 {
+    let ret: u64;
+    llvm_asm!("mrs $0, ttbr1_el1" : "=r" (ret));
+    ret
+}
+
+pub unsafe fn ttbr1_el1_write(val: u64) {
+    llvm_asm!("msr ttbr1_el1, $0" :: "r" (val) : "memory");
+}
+
+pub unsafe fn mair_el1() -> MairEl1 {
+    let ret: u64;
+    llvm_asm!("mrs $0, mair_el1" : "=r" (ret));
+    MairEl1::from_bits_truncate(ret)
+}
+
+pub unsafe fn mair_el1_write(val: MairEl1) {
+    llvm_asm!("msr mair_el1, $0" :: "r" (val.bits()) : "memory");
+}
+
+pub unsafe fn tpidr_el0_write(val: u64) {
+    llvm_asm!("msr tpidr_el0, $0" :: "r" (val) : "memory");
+}
+
+pub unsafe fn tpidr_el1_write(val: u64) {
+    llvm_asm!("msr tpidr_el1, $0" :: "r" (val) : "memory");
+}
+
+pub unsafe fn esr_el1() -> u32 {
+    let ret: u32;
+    llvm_asm!("mrs $0, esr_el1" : "=r" (ret));
+    ret
+}
+
+pub unsafe fn cntfreq_el0() -> u32 {
+    let ret: u32;
+    llvm_asm!("mrs $0, cntfrq_el0" : "=r" (ret));
+    ret
+}
+
+pub unsafe fn tmr_ctrl() -> u32 {
+    let ret: u32;
+    llvm_asm!("mrs $0, cntp_ctl_el0" : "=r" (ret));
+    ret
+}
+
+pub unsafe fn tmr_ctrl_write(val: u32) {
+    llvm_asm!("msr cntp_ctl_el0, $0" :: "r" (val) : "memory");
+}
+
+pub unsafe fn tmr_tval() -> u32 {
+    let ret: u32;
+    llvm_asm!("mrs $0, cntp_tval_el0" : "=r" (ret));
+    ret
+}
+
+pub unsafe fn tmr_tval_write(val: u32) {
+    llvm_asm!("msr cntp_tval_el0, $0" :: "r" (val) : "memory");
+}
+
+pub unsafe fn midr() -> u32 {
+    let ret: u32;
+    llvm_asm!("mrs $0, midr_el1" : "=r" (ret));
+    ret
+}
diff --git a/src/arch/aarch64/device/cpu/registers/mod.rs b/src/arch/aarch64/device/cpu/registers/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9d09aabbd0e37504214d1d78a4f43f76ee410c3b
--- /dev/null
+++ b/src/arch/aarch64/device/cpu/registers/mod.rs
@@ -0,0 +1,2 @@
+pub mod control_regs;
+pub mod tlb;
diff --git a/src/arch/aarch64/device/cpu/registers/tlb.rs b/src/arch/aarch64/device/cpu/registers/tlb.rs
new file mode 100644
index 0000000000000000000000000000000000000000..863057415cb34c53e9702bda718bef43be9fd339
--- /dev/null
+++ b/src/arch/aarch64/device/cpu/registers/tlb.rs
@@ -0,0 +1,9 @@
+//! Functions to flush the translation lookaside buffer (TLB).
+
+pub unsafe fn flush(_addr: usize) {
+    llvm_asm!("tlbi vmalle1is");
+}
+
+pub unsafe fn flush_all() {
+    llvm_asm!("tlbi vmalle1is");
+}
diff --git a/src/arch/aarch64/device/generic_timer.rs b/src/arch/aarch64/device/generic_timer.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d8e6b6b9dd98b0e7176dbc78d5713c61187b8a4e
--- /dev/null
+++ b/src/arch/aarch64/device/generic_timer.rs
@@ -0,0 +1,80 @@
+use crate::arch::device::gic;
+use crate::device::cpu::registers::{control_regs};
+
+bitflags! {
+    struct TimerCtrlFlags: u32 {
+        const ENABLE = 1 << 0;
+        const IMASK = 1 << 1;
+        const ISTATUS = 1 << 2;
+    }
+}
+
+pub static mut GENTIMER: GenericTimer = GenericTimer {
+    clk_freq: 0,
+    reload_count: 0,
+};
+
+pub unsafe fn init() {
+    GENTIMER.init();
+}
+
+/*
+pub unsafe fn clear_irq() {
+    GENTIMER.clear_irq();
+}
+
+pub unsafe fn reload() {
+    GENTIMER.reload_count();
+}
+*/
+
+pub struct GenericTimer {
+    pub clk_freq: u32,
+    pub reload_count: u32,
+}
+
+impl GenericTimer {
+    pub fn init(&mut self) {
+        let clk_freq = unsafe { control_regs::cntfreq_el0() };
+        self.clk_freq = clk_freq;;
+        self.reload_count = clk_freq / 100;
+
+        unsafe { control_regs::tmr_tval_write(self.reload_count) };
+
+        let mut ctrl = TimerCtrlFlags::from_bits_truncate(unsafe { control_regs::tmr_ctrl() });
+        ctrl.insert(TimerCtrlFlags::ENABLE);
+        ctrl.remove(TimerCtrlFlags::IMASK);
+        unsafe { control_regs::tmr_ctrl_write(ctrl.bits()) };
+
+        gic::irq_enable(30);
+    }
+
+    fn disable() {
+        let mut ctrl = TimerCtrlFlags::from_bits_truncate(unsafe { control_regs::tmr_ctrl() });
+        ctrl.remove(TimerCtrlFlags::ENABLE);
+        unsafe { control_regs::tmr_ctrl_write(ctrl.bits()) };
+    }
+
+    pub fn set_irq(&mut self) {
+        let mut ctrl = TimerCtrlFlags::from_bits_truncate(unsafe { control_regs::tmr_ctrl() });
+        ctrl.remove(TimerCtrlFlags::IMASK);
+        unsafe { control_regs::tmr_ctrl_write(ctrl.bits()) };
+    }
+
+    pub fn clear_irq(&mut self) {
+        let mut ctrl = TimerCtrlFlags::from_bits_truncate(unsafe { control_regs::tmr_ctrl() });
+
+        if ctrl.contains(TimerCtrlFlags::ISTATUS) {
+            ctrl.insert(TimerCtrlFlags::IMASK);
+            unsafe { control_regs::tmr_ctrl_write(ctrl.bits()) };
+        }
+    }
+
+    pub fn reload_count(&mut self) {
+        let mut ctrl = TimerCtrlFlags::from_bits_truncate(unsafe { control_regs::tmr_ctrl() });
+        ctrl.insert(TimerCtrlFlags::ENABLE);
+        ctrl.remove(TimerCtrlFlags::IMASK);
+        unsafe { control_regs::tmr_tval_write(self.reload_count) };
+        unsafe { control_regs::tmr_ctrl_write(ctrl.bits()) };
+    }
+}
diff --git a/src/arch/aarch64/device/gic.rs b/src/arch/aarch64/device/gic.rs
new file mode 100644
index 0000000000000000000000000000000000000000..92c37248c76b19b0cff64ec7b8afe9ac1ba6d697
--- /dev/null
+++ b/src/arch/aarch64/device/gic.rs
@@ -0,0 +1,182 @@
+use core::intrinsics::{volatile_load, volatile_store};
+
+use crate::memory::Frame;
+use crate::paging::{ActivePageTable, PhysicalAddress, Page, PageTableType, VirtualAddress};
+use crate::paging::entry::EntryFlags;
+
+static GICD_CTLR: u32 = 0x000;
+static GICD_TYPER: u32 = 0x004;
+static GICD_ISENABLER: u32 = 0x100;
+static GICD_ICENABLER: u32 = 0x180;
+static GICD_IPRIORITY: u32 = 0x400;
+static GICD_ITARGETSR: u32 = 0x800;
+static GICD_ICFGR: u32 = 0xc00;
+
+static GICC_EOIR: u32 = 0x0010;
+static GICC_IAR: u32 = 0x000c;
+static GICC_CTLR: u32 = 0x0000;
+static GICC_PMR: u32 = 0x0004;
+
+static mut GIC_DIST_IF: GicDistIf = GicDistIf {
+    address: 0,
+    ncpus: 0,
+    nirqs: 0,
+};
+
+static mut GIC_CPU_IF: GicCpuIf = GicCpuIf {
+    address: 0,
+};
+
+pub unsafe fn init() {
+    GIC_DIST_IF.init();
+    GIC_CPU_IF.init();
+}
+
+pub fn irq_enable(irq_num: u32) {
+    unsafe { GIC_DIST_IF.irq_enable(irq_num) };
+}
+
+pub fn irq_disable(irq_num: u32) {
+    unsafe { GIC_DIST_IF.irq_disable(irq_num) };
+}
+
+pub unsafe fn irq_ack() -> u32 {
+    GIC_CPU_IF.irq_ack()
+}
+
+pub unsafe fn irq_eoi(irq_num: u32) {
+    GIC_CPU_IF.irq_eoi(irq_num);
+}
+
+pub struct GicDistIf {
+    pub address: usize,
+    pub ncpus: u32,
+    pub nirqs: u32,
+}
+
+impl GicDistIf {
+    unsafe fn init(&mut self) {
+        // Map in the Distributor interface
+        let mut active_table = ActivePageTable::new(PageTableType::Kernel);
+
+        let start_frame = Frame::containing_address(PhysicalAddress::new(0x08000000));
+        let end_frame = Frame::containing_address(PhysicalAddress::new(0x08000000 + 0x10000 - 1));
+        for frame in Frame::range_inclusive(start_frame, end_frame) {
+            let page = Page::containing_address(VirtualAddress::new(frame.start_address().data() + crate::KERNEL_DEVMAP_OFFSET));
+            let result = active_table.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::WRITABLE | EntryFlags::NO_EXECUTE);
+            result.flush(&mut active_table);
+        }
+
+        self.address = crate::KERNEL_DEVMAP_OFFSET + 0x08000000;
+
+        // Map in CPU0's interface
+        let start_frame = Frame::containing_address(PhysicalAddress::new(0x08010000));
+        let end_frame = Frame::containing_address(PhysicalAddress::new(0x08010000 + 0x10000 - 1));
+        for frame in Frame::range_inclusive(start_frame, end_frame) {
+            let page = Page::containing_address(VirtualAddress::new(frame.start_address().data() + crate::KERNEL_DEVMAP_OFFSET));
+            let result = active_table.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::WRITABLE | EntryFlags::NO_EXECUTE);
+            result.flush(&mut active_table);
+        }
+
+        GIC_CPU_IF.address = crate::KERNEL_DEVMAP_OFFSET + 0x08010000;
+
+        // Disable IRQ Distribution
+        self.write(GICD_CTLR, 0);
+
+        let typer = self.read(GICD_TYPER);
+        self.ncpus = ((typer & (0x7 << 5)) >> 5) + 1;
+        self.nirqs = ((typer & 0x1f) + 1) * 32;
+        println!("gic: Distributor supports {:?} CPUs and {:?} IRQs", self.ncpus, self.nirqs);
+
+        // Set all SPIs to level triggered
+        for irq in (32..self.nirqs).step_by(16) {
+            self.write(GICD_ICFGR + ((irq / 16) * 4), 0);
+        }
+
+        // Disable all SPIs
+        for irq in (32..self.nirqs).step_by(32) {
+            self.write(GICD_ICENABLER + ((irq / 32) * 4), 0xffff_ffff);
+        }
+
+        // Affine all SPIs to CPU0 and set priorities for all IRQs
+        for irq in 0..self.nirqs {
+            if irq > 31 {
+                let ext_offset = GICD_ITARGETSR + (4 * (irq / 4));
+                let int_offset = irq % 4;
+                let mut val = self.read(ext_offset);
+                val |= 0b0000_0001 << (8 * int_offset);
+                self.write(ext_offset, val);
+            }
+
+            let ext_offset = GICD_IPRIORITY + (4 * (irq / 4));
+            let int_offset = irq % 4;
+            let mut val = self.read(ext_offset);
+            val |= 0b0000_0000 << (8 * int_offset);
+            self.write(ext_offset, val);
+        }
+
+        // Enable CPU0's GIC interface
+        GIC_CPU_IF.write(GICC_CTLR, 1);
+
+        // Set CPU0's Interrupt Priority Mask
+        GIC_CPU_IF.write(GICC_PMR, 0xff);
+
+        // Enable IRQ distribution
+        self.write(GICD_CTLR, 0x1);
+    }
+
+    unsafe fn irq_enable(&mut self, irq: u32) {
+        let offset = GICD_ISENABLER + (4 * (irq / 32));
+        let shift = 1 << (irq % 32);
+        let mut val = self.read(offset);
+        val |= shift;
+        self.write(offset, val);
+    }
+
+    unsafe fn irq_disable(&mut self, irq: u32) {
+        let offset = GICD_ICENABLER + (4 * (irq / 32));
+        let shift = 1 << (irq % 32);
+        let mut val = self.read(offset);
+        val |= shift;
+        self.write(offset, val);
+    }
+
+    unsafe fn read(&self, reg: u32) -> u32 {
+        let val = volatile_load((self.address + reg as usize) as *const u32);
+        val
+    }
+
+    unsafe fn write(&mut self, reg: u32, value: u32) {
+        volatile_store((self.address + reg as usize) as *mut u32, value);
+    }
+}
+
+pub struct GicCpuIf {
+    pub address: usize,
+}
+
+impl GicCpuIf {
+    unsafe fn init(&mut self) {
+    }
+
+    unsafe fn irq_ack(&mut self) -> u32 {
+        let irq = self.read(GICC_IAR) & 0x1ff;
+        if irq == 1023 {
+            panic!("irq_ack: got ID 1023!!!");
+        }
+        irq
+    }
+
+    unsafe fn irq_eoi(&mut self, irq: u32) {
+        self.write(GICC_EOIR, irq);
+    }
+
+    unsafe fn read(&self, reg: u32) -> u32 {
+        let val = volatile_load((self.address + reg as usize) as *const u32);
+        val
+    }
+
+    unsafe fn write(&mut self, reg: u32, value: u32) {
+        volatile_store((self.address + reg as usize) as *mut u32, value);
+    }
+}
diff --git a/src/arch/aarch64/device/mod.rs b/src/arch/aarch64/device/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bdd3169c300834515420d09ecb392af4c4732079
--- /dev/null
+++ b/src/arch/aarch64/device/mod.rs
@@ -0,0 +1,25 @@
+use crate::paging::ActivePageTable;
+
+pub mod cpu;
+pub mod gic;
+pub mod generic_timer;
+pub mod serial;
+pub mod rtc;
+pub mod uart_pl011;
+
+pub unsafe fn init(_active_table: &mut ActivePageTable) {
+    println!("GIC INIT");
+    gic::init();
+    println!("GIT INIT");
+    generic_timer::init();
+}
+
+pub unsafe fn init_noncore() {
+    println!("SERIAL INIT");
+    serial::init();
+    println!("RTC INIT");
+    rtc::init();
+}
+
+pub unsafe fn init_ap() {
+}
diff --git a/src/arch/aarch64/device/rtc.rs b/src/arch/aarch64/device/rtc.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e547523dd19a09df1efead9c82659b4f3e1240cd
--- /dev/null
+++ b/src/arch/aarch64/device/rtc.rs
@@ -0,0 +1,59 @@
+use core::intrinsics::{volatile_load, volatile_store};
+
+use crate::memory::Frame;
+use crate::paging::{ActivePageTable, PhysicalAddress, Page, PageTableType, VirtualAddress};
+use crate::paging::entry::EntryFlags;
+use crate::time;
+
+static RTC_DR: u32 = 0x000;
+static RTC_MR: u32 = 0x004;
+static RTC_LR: u32 = 0x008;
+static RTC_CR: u32 = 0x00c;
+static RTC_IMSC: u32 = 0x010;
+static RTC_RIS: u32 = 0x014;
+static RTC_MIS: u32 = 0x018;
+static RTC_ICR: u32 = 0x01c;
+
+static mut PL031_RTC: Pl031rtc = Pl031rtc {
+    address: 0,
+};
+
+pub unsafe fn init() {
+    PL031_RTC.init();
+    time::START.lock().0 = PL031_RTC.time();
+}
+
+struct Pl031rtc {
+    pub address: usize,
+}
+
+impl Pl031rtc {
+    unsafe fn init(&mut self) {
+        let mut active_table = ActivePageTable::new(PageTableType::Kernel);
+
+        let start_frame = Frame::containing_address(PhysicalAddress::new(0x09010000));
+        let end_frame = Frame::containing_address(PhysicalAddress::new(0x09010000 + 0x1000 - 1));
+
+        for frame in Frame::range_inclusive(start_frame, end_frame) {
+            let page = Page::containing_address(VirtualAddress::new(frame.start_address().data() + crate::KERNEL_DEVMAP_OFFSET));
+            let result = active_table.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::WRITABLE | EntryFlags::NO_EXECUTE);
+            result.flush(&mut active_table);
+        }
+
+        self.address = crate::KERNEL_DEVMAP_OFFSET + 0x09010000;
+    }
+
+    unsafe fn read(&self, reg: u32) -> u32 {
+        let val = volatile_load((self.address + reg as usize) as *const u32);
+        val
+    }
+
+    unsafe fn write(&mut self, reg: u32, value: u32) {
+        volatile_store((self.address + reg as usize) as *mut u32, value);
+    }
+
+    pub fn time(&mut self) -> u64 {
+        let seconds = unsafe { self.read(RTC_DR) } as u64;
+        seconds
+    }
+}
diff --git a/src/arch/aarch64/device/serial.rs b/src/arch/aarch64/device/serial.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1bba91eac8c378dcee61546a44b0f93958a32d7c
--- /dev/null
+++ b/src/arch/aarch64/device/serial.rs
@@ -0,0 +1,38 @@
+use core::sync::atomic::{Ordering};
+use spin::Mutex;
+
+use crate::device::uart_pl011::SerialPort;
+use crate::init::device_tree;
+use crate::memory::Frame;
+use crate::paging::mapper::{MapperFlushAll, MapperType};
+use crate::paging::{ActivePageTable, Page, PageTableType, PhysicalAddress, VirtualAddress};
+use crate::paging::entry::EntryFlags;
+
+pub static COM1: Mutex<Option<SerialPort>> = Mutex::new(None);
+
+pub unsafe fn init() {
+    if let Some(ref mut serial_port) = *COM1.lock() {
+        return;
+    }
+    let (base, size) = device_tree::diag_uart_range(crate::KERNEL_DTB_OFFSET, crate::KERNEL_DTB_MAX_SIZE).unwrap();
+
+    let mut active_ktable = unsafe { ActivePageTable::new(PageTableType::Kernel) };
+    let mut flush_all = MapperFlushAll::new();
+
+    let start_frame = Frame::containing_address(PhysicalAddress::new(base));
+    let end_frame = Frame::containing_address(PhysicalAddress::new(base + size - 1));
+    for frame in Frame::range_inclusive(start_frame, end_frame) {
+        let page = Page::containing_address(VirtualAddress::new(frame.start_address().data() + crate::KERNEL_DEVMAP_OFFSET));
+        let result = active_ktable.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::NO_EXECUTE | EntryFlags::WRITABLE);
+        flush_all.consume(result);
+    };
+    flush_all.flush(&mut active_ktable);
+
+    let start_frame = Frame::containing_address(PhysicalAddress::new(base));
+    let vaddr = start_frame.start_address().data() + crate::KERNEL_DEVMAP_OFFSET;
+
+    *COM1.lock() = Some(SerialPort::new(vaddr));
+    if let Some(ref mut serial_port) = *COM1.lock() {
+        serial_port.init(true);
+    }
+}
diff --git a/src/arch/aarch64/device/uart_pl011.rs b/src/arch/aarch64/device/uart_pl011.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ef9f77811d60b37c60da007a54849094c8da553d
--- /dev/null
+++ b/src/arch/aarch64/device/uart_pl011.rs
@@ -0,0 +1,170 @@
+use core::fmt::{self, Write};
+use core::ptr;
+
+use crate::device::gic;
+use crate::scheme::debug::debug_input;
+
+bitflags! {
+    /// UARTFR
+    struct UartFrFlags: u16 {
+        const TXFE = 1 << 7;
+        const RXFF = 1 << 6;
+        const TXFF = 1 << 5;
+        const RXFE = 1 << 4;
+        const BUSY = 1 << 3;
+    }
+}
+
+bitflags! {
+    /// UARTCR
+    struct UartCrFlags: u16 {
+        const RXE = 1 << 9;
+        const TXE = 1 << 8;
+        const UARTEN = 1 << 0;
+    }
+}
+
+bitflags! {
+    // UARTIMSC
+    struct UartImscFlags: u16 {
+        const RTIM = 1 << 6;
+        const TXIM = 1 << 5;
+        const RXIM = 1 << 4;
+    }
+}
+
+bitflags! {
+    // UARTICR
+    struct UartIcrFlags: u16 {
+        const RTIC = 1 << 6;
+        const TXIC = 1 << 5;
+        const RXIC = 1 << 4;
+    }
+}
+
+bitflags! {
+    //UARTMIS
+    struct UartMisFlags: u16 {
+        const TXMIS = 1 << 5;
+        const RXMIS = 1 << 4;
+    }
+}
+
+bitflags! {
+    //UARTLCR_H
+    struct UartLcrhFlags: u16 {
+        const FEN = 1 << 4;
+    }
+}
+
+#[allow(dead_code)]
+pub struct SerialPort {
+    base: usize,
+    data_reg: u8,
+    rcv_stat_reg: u8,
+    flag_reg: u8,
+    int_baud_reg: u8,
+    frac_baud_reg: u8,
+    line_ctrl_reg: u8,
+    ctrl_reg: u8,
+    intr_fifo_ls_reg: u8,
+    intr_mask_setclr_reg: u8,
+    raw_intr_stat_reg: u8,
+    masked_intr_stat_reg: u8,
+    intr_clr_reg: u8,
+    dma_ctrl_reg: u8
+}
+
+impl SerialPort {
+    pub const fn new(base: usize) -> SerialPort {
+        SerialPort {
+            base: base,
+            data_reg: 0x00,
+            rcv_stat_reg: 0x04,
+            flag_reg: 0x18,
+            int_baud_reg: 0x24,
+            frac_baud_reg: 0x28,
+            line_ctrl_reg: 0x2c,
+            ctrl_reg: 0x30,
+            intr_fifo_ls_reg: 0x34,
+            intr_mask_setclr_reg: 0x38,
+            raw_intr_stat_reg: 0x3c,
+            masked_intr_stat_reg: 0x40,
+            intr_clr_reg: 0x44,
+            dma_ctrl_reg: 0x48,
+        }
+    }
+
+    pub fn read_reg(&self, register: u8) -> u16 {
+        unsafe { ptr::read_volatile((self.base + register as usize) as *mut u16) }
+    }
+
+    pub fn write_reg(&self, register: u8, data: u16) {
+        unsafe { ptr::write_volatile((self.base + register as usize) as *mut u16, data); }
+    }
+
+    pub fn init(&mut self, with_irq: bool) {
+        // Enable RX, TX, UART
+        let flags = UartCrFlags::RXE | UartCrFlags::TXE | UartCrFlags::UARTEN;
+        self.write_reg(self.ctrl_reg, flags.bits());
+
+        // Disable FIFOs (use character mode instead)
+        let mut flags = UartLcrhFlags::from_bits_truncate(self.read_reg(self.line_ctrl_reg));
+        flags.remove(UartLcrhFlags::FEN);
+        self.write_reg(self.line_ctrl_reg, flags.bits());
+
+        if with_irq {
+            // Enable IRQs
+            let flags = UartImscFlags::RXIM;
+            self.write_reg(self.intr_mask_setclr_reg, flags.bits);
+
+            // Clear pending interrupts
+            self.write_reg(self.intr_clr_reg, 0x7ff);
+
+            // Enable interrupt at GIC distributor
+            gic::irq_enable(33);
+        }
+    }
+
+    fn line_sts(&self) -> UartFrFlags {
+        UartFrFlags::from_bits_truncate(self.read_reg(self.flag_reg))
+    }
+
+    pub fn receive(&mut self) {
+        while self.line_sts().contains(UartFrFlags::RXFF) {
+            debug_input(self.read_reg(self.data_reg) as u8);
+        }
+    }
+
+    pub fn send(&mut self, data: u8) {
+        while ! self.line_sts().contains(UartFrFlags::TXFE) {}
+        self.write_reg(self.data_reg, data as u16);
+    }
+
+    pub fn send_dbg(&mut self, data: u16) {
+        if self.base != 0 {
+            self.write_reg(self.data_reg, data);
+        }
+    }
+
+    pub fn clear_all_irqs(&mut self) {
+        let flags = UartIcrFlags::RXIC;
+        self.write_reg(self.intr_clr_reg, flags.bits());
+    }
+
+    pub fn disable_irq(&mut self) {
+        self.write_reg(self.intr_mask_setclr_reg, 0);
+    }
+
+    pub fn enable_irq(&mut self) {
+        let flags = UartImscFlags::RXIM;
+        self.write_reg(self.intr_mask_setclr_reg, flags.bits());
+    }
+
+    pub fn write(&mut self, buf: &[u8]) {
+        //TODO: some character conversion like in uart_16550.rs
+        for &b in buf {
+            self.send_dbg(b as u16);
+        }
+    }
+}
diff --git a/src/arch/aarch64/init/device_tree/mod.rs b/src/arch/aarch64/init/device_tree/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cff4047d69b800040382cd71bf20e5cacf6a47d1
--- /dev/null
+++ b/src/arch/aarch64/init/device_tree/mod.rs
@@ -0,0 +1,127 @@
+extern crate fdt;
+extern crate byteorder;
+
+use alloc::vec::Vec;
+use core::slice;
+use crate::memory::MemoryArea;
+use self::byteorder::{ByteOrder, BE};
+
+pub static mut MEMORY_MAP: [MemoryArea; 512] = [MemoryArea {
+    base_addr: 0,
+    length: 0,
+    _type: 0,
+    acpi: 0,
+}; 512];
+
+fn root_cell_sz(dt: &fdt::DeviceTree) -> Option<(u32, u32)> {
+    let root_node = dt.nodes().nth(0).unwrap();
+    let address_cells = root_node.properties().find(|p| p.name.contains("#address-cells")).unwrap();
+    let size_cells = root_node.properties().find(|p| p.name.contains("#size-cells")).unwrap();
+
+    Some((BE::read_u32(&size_cells.data), BE::read_u32(&size_cells.data)))
+}
+
+fn memory_ranges(dt: &fdt::DeviceTree, address_cells: usize, size_cells: usize, ranges: &mut [(usize, usize); 10]) -> usize {
+
+    let memory_node = dt.find_node("/memory").unwrap();
+    let reg = memory_node.properties().find(|p| p.name.contains("reg")).unwrap();
+    let chunk_sz = (address_cells + size_cells) * 4;
+    let chunk_count = (reg.data.len() / chunk_sz);
+    let mut index = 0;
+    for chunk in reg.data.chunks(chunk_sz as usize) {
+        if index == chunk_count {
+            return index;
+        }
+        let (base, size) = chunk.split_at((address_cells * 4) as usize);
+        let mut b = 0;
+        for base_chunk in base.rchunks(4) {
+            b += BE::read_u32(base_chunk);
+        }
+        let mut s = 0;
+        for sz_chunk in size.rchunks(4) {
+            s += BE::read_u32(sz_chunk);
+        }
+        ranges[index] = (b as usize, s as usize);
+        index += 1;
+    }
+    index
+}
+
+pub fn diag_uart_range(dtb_base: usize, dtb_size: usize) -> Option<(usize, usize)> {
+    let data = unsafe { slice::from_raw_parts(dtb_base as *const u8, dtb_size) };
+    let dt = fdt::DeviceTree::new(data).unwrap();
+
+    let chosen_node = dt.find_node("/chosen").unwrap();
+    let stdout_path = chosen_node.properties().find(|p| p.name.contains("stdout-path")).unwrap();
+    let uart_node_name = core::str::from_utf8(stdout_path.data).unwrap()
+        .split('/')
+        .collect::<Vec<&str>>()[1].trim_end();
+    let len = uart_node_name.len();
+    let uart_node_name = &uart_node_name[0..len-1];
+    let uart_node = dt.nodes().find(|n| n.name.contains(uart_node_name)).unwrap();
+    let reg = uart_node.properties().find(|p| p.name.contains("reg")).unwrap();
+
+    let (address_cells, size_cells) = root_cell_sz(&dt).unwrap();
+    let chunk_sz = (address_cells + size_cells) * 4;
+    let (base, size) = reg.data.split_at((address_cells * 4) as usize);
+    let mut b = 0;
+    for base_chunk in base.rchunks(4) {
+        b += BE::read_u32(base_chunk);
+    }
+    let mut s = 0;
+    for sz_chunk in size.rchunks(4) {
+        s += BE::read_u32(sz_chunk);
+    }
+    Some((b as usize, s as usize))
+}
+
+fn compatible_node_present<'a>(dt: &fdt::DeviceTree<'a>, compat_string: &str) -> bool {
+    for node in dt.nodes() {
+        if let Some(compatible) = node.properties().find(|p| p.name.contains("compatible")) {
+            let s = core::str::from_utf8(compatible.data).unwrap();
+            if s.contains(compat_string) {
+                return true;
+            }
+        }
+    }
+    false
+}
+
+pub fn fill_env_data(dtb_base: usize, dtb_size: usize, env_base: usize) -> usize {
+    let data = unsafe { slice::from_raw_parts(dtb_base as *const u8, dtb_size) };
+    let dt = fdt::DeviceTree::new(data).unwrap();
+
+    let chosen_node = dt.find_node("/chosen").unwrap();
+    if let Some(bootargs) = chosen_node.properties().find(|p| p.name.contains("bootargs")) {
+        let bootargs_len = bootargs.data.len();
+
+        let env_base_slice = unsafe { slice::from_raw_parts_mut(env_base as *mut u8, bootargs_len) };
+        env_base_slice[..bootargs_len].clone_from_slice(bootargs.data);
+
+        bootargs_len
+    } else {
+        0
+    }
+}
+
+pub fn fill_memory_map(dtb_base: usize, dtb_size: usize) {
+    let data = unsafe { slice::from_raw_parts(dtb_base as *const u8, dtb_size) };
+    let dt = fdt::DeviceTree::new(data).unwrap();
+
+    let (address_cells, size_cells) = root_cell_sz(&dt).unwrap();
+    let mut ranges: [(usize, usize); 10] = [(0,0); 10];
+
+    let nranges = memory_ranges(&dt, address_cells as usize, size_cells as usize, &mut ranges);
+
+    for index in (0..nranges) {
+        let (base, size) = ranges[index];
+        unsafe {
+            MEMORY_MAP[index] = MemoryArea {
+                base_addr: base as u64,
+                length: size as u64,
+                _type: 1,
+                acpi: 0,
+            };
+        }
+    }
+}
diff --git a/src/arch/aarch64/init/mod.rs b/src/arch/aarch64/init/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..16a16cdb31ad6e8ff856e4b5cb332c2321b92b34
--- /dev/null
+++ b/src/arch/aarch64/init/mod.rs
@@ -0,0 +1 @@
+pub mod device_tree;
diff --git a/src/arch/aarch64/init/pre_kstart/early_init.S b/src/arch/aarch64/init/pre_kstart/early_init.S
new file mode 100644
index 0000000000000000000000000000000000000000..0718d1620f3acd749ef44e01a3016321c24d9736
--- /dev/null
+++ b/src/arch/aarch64/init/pre_kstart/early_init.S
@@ -0,0 +1,51 @@
+// Early initialisation for AArch64 systems.
+//
+// This code is responsible for taking over control of the boot CPU from
+// the bootloader and setting up enough of the CPU so Rust code can take
+// over (in kstart).
+//
+// Readers are recommended to refer to the Arm Architecture Reference Manual
+// when studying this code. The latest version of the Arm Arm can be found at:
+//
+// https://developer.arm.com/products/architecture/cpu-architecture/a-profile/docs
+//
+// The code is structured such that different phases/functionality are
+// in separate files included by this central one.
+//
+// This is hopefully easier to grok and study than one gigantic file.
+//
+// The emphasis is on clarity and not optimisation. Clarity is hard without
+// a decent understanding of the Arm architecture.
+//
+// Optimisation is not too much of a concern given that this is boot code.
+// That said, future revisions will aim to optimise.
+
+#include "helpers/consts.h"
+
+#include "helpers/pre_mmu_enabled.S"
+#include "helpers/build_page_tables.S"
+#include "helpers/post_mmu_enabled.S"
+#include "helpers/vectors.S"
+
+//  Entry point for the boot CPU. We assume that x0 contains the physical address of a DTB image
+//  passed in by the bootloader.
+//
+//  Note that the kernel linker script arranges for this code to lie at the start of the kernel
+//  image.
+
+    .text
+    .align 2
+    .pushsection ".early_init.text", "ax"
+    .globl early_init
+early_init:
+    bl      early_setup
+    bl      disable_mmu
+    bl      create_page_tables
+    bl      enable_mmu
+    b       mmu_on_trampoline               // With the mmu now on, this returns below to
+                                            // mmu_on using Virtual Addressing
+
+mmu_on:
+    bl      setup_kstart_context            // Setup environment for kstart
+    b       kstart                          // Let the show begin! :)
+    .popsection
diff --git a/src/arch/aarch64/init/pre_kstart/helpers/build_page_tables.S b/src/arch/aarch64/init/pre_kstart/helpers/build_page_tables.S
new file mode 100644
index 0000000000000000000000000000000000000000..5e141eda858b61b72304d2e0f5942fa3518b8f2d
--- /dev/null
+++ b/src/arch/aarch64/init/pre_kstart/helpers/build_page_tables.S
@@ -0,0 +1,249 @@
+    //  Creates the following MMU mappings:
+    //
+    //  1. Identity mapping for the kernel (VA == PA) to be able to switch on the MMU
+    //  2. Mapping for the kernel with high VAs from KERNEL_OFFSET onwards
+    //  3. Mapping for the kernel stack
+    //  4. Mapping for the DTB Image
+    //  5. Optional Mapping for a diagnostic UART
+
+create_page_tables:
+    mov     x22, x30
+    adr     x0, addr_marker                 // x0: Physical address of addr_marker
+    ldr     x1, [x0]                        // x1: Virtual address of addr_marker
+    ldr     x2, =KERNEL_OFFSET              // x2: Virtual address of kernel base
+    sub     x3, x1, x2                      // x3: 'Distance' of addr_marker from kernel base
+    sub     x0, x0, x3                      // x0: Physical address of kernel base
+    mov     x11,x0                          // x11: Stash away the Physical address of the kernel image base
+
+    ldr     x1, =KERNEL_OFFSET              // x1: Virtual address of kernel start addr
+    ldr     x2, =__end                      // x2: Virtual address of kernel end addr
+    sub     x12, x2, x1                     // x12: Size of the kernel image
+    add     x12, x12, #(0x200000)           // x12: Align to 2MB (Add 2MB, then clear low bits if any)
+    and     x3, x12, #0xffffffffffe00000
+    cmp     x12, #0x200, lsl #12
+    csel    x12, x3, x12, hi
+    add     x13, x1, x12                    // x13: Stack top vaddr (kbase.vaddr + ksize)
+    mov     x14, #(EARLY_KSTACK_SIZE)       // x14: Stack size
+    ldr     x15, =KERNEL_OFFSET             // x15: Kernel base vaddr
+
+    //  From this point on, the following registers are not to be modified for convenience:
+    //  x11: PA of kernel image base
+    //  x12: Kernel image size (2MB aligned)
+    //  x13: VA of stack top
+    //  x14: Stack size
+    //  x15: VA of kernel Base
+
+    //  Zero out all the tables
+zero_tables:
+    adr     x0, identkmap_l0_ptable
+    mov     x1, #(PAGE_SIZE)
+    mov     x2, #(NUM_TABLES)            // There are normally 12 tables to clear (2 L0, 5 L1, 5 L2, 1 env)
+    mul     x1, x1, x2
+    lsr     x1, x1, #3
+    mov     x2, xzr
+zero_loop:
+    str     xzr, [x0, x2]
+    add     x2, x2, #8
+    cmp     x1, x2
+    b.ne    zero_loop
+
+    //  Identity map the kernel
+    mov     x0, x11                         // x0: Paddr of kernel image base
+    mov     x1, x11                         // x1: Paddr of kernel image base
+    mov     x2, x12                         // x2: Kernel image size
+    mov     x3, #(NORMAL_UNCACHED_MEM)      // x3: Attributes to apply
+    adr     x4, identkmap_l0_ptable         // x5: Ptr to L0 table for identity mapping the kernel
+    adr     x5, identkmap_l1_ptable         // x6: Ptr to L1 table for identity mapping the kernel
+    adr     x6, identkmap_l2_ptable         // x7: Ptr to L2 table for identity mapping the kernel
+    bl      build_map
+
+    //  Map the kernel
+    ldr     x0, =KERNEL_OFFSET              // x0: Vaddr of kernel base
+    mov     x1, x11                         // x1: Paddr of kernel base
+    mov     x2, x12                         // x2: Kernel image size
+    mov     x3, #(NORMAL_CACHED_MEM)        // x3: Attributes to apply
+    adr     x4, kernmap_l0_ptable           // x5: Ptr to L0 table for mapping the kernel
+    adr     x5, kernmap_l1_ptable           // x6: Ptr to L1 table for mapping the kernel
+    adr     x6, kernmap_l2_ptable           // x7: Ptr to L2 table for mapping the kernel
+    bl      build_map
+
+    //  Map the kernel stack
+    ldr     x0, =KERNEL_OFFSET              // x0: Vaddr of kernel stack top
+    add     x0, x0, x12
+    sub     x1, x11, x14                    // x1: Paddr of kernel stack top (kbase.paddr - kstack size)
+    mov     x2, #(EARLY_KSTACK_SIZE)        // x2: Size of kernel stack
+    mov     x3, #(NORMAL_CACHED_MEM)        // x3: Attributes to apply
+    adr     x4, kernmap_l0_ptable           // x5: Ptr to the kernel L0 table
+    adr     x5, kstack_l1_ptable            // x6: Ptr to L1 table for mapping the kernel stack
+    adr     x6, kstack_l2_ptable            // x7: Ptr to L2 table for mapping the kernel stack
+    bl      build_map
+
+    // Map first GIGABYTE at PHYS_OFFSET
+    mov     x1, #0                          // x1: Physical address
+    adr     x6, physmap_1gb_l2_ptable       // x7: Ptr to L2 table
+    bl      build_physmap
+
+    // Map second GIGABYTE at PHYS_OFFSET + GIGABYTE
+    mov     x1, #(GIGABYTE)                 // x1: Physical address
+    adr     x6, physmap_2gb_l2_ptable       // x7: Ptr to L2 table
+    bl      build_physmap
+
+    // Map third GIGABYTE at PHYS_OFFSET + 2*GIGABYTE
+    mov     x1, #(2*GIGABYTE)               // x1: Physical address
+    adr     x6, physmap_3gb_l2_ptable       // x7: Ptr to L2 table
+    bl      build_physmap
+
+    // Map fourth GIGABYTE at PHYS_OFFSET + 3*GIGABYTE
+    mov     x1, #(3*GIGABYTE)               // x1: Physical address
+    adr     x6, physmap_4gb_l2_ptable       // x7: Ptr to L2 table
+    bl      build_physmap
+
+    //  Set up recursive paging for TTBR1
+
+    adr     x0, kernmap_l0_ptable
+    add     x1, x0, #(511 * 8)
+    orr     x0, x0, #((DESC_TYPE_TABLE << DESC_TYPE_BIT) | (DESC_VALID << DESC_VALID_BIT))
+    orr     x0, x0, #(ACCESS_FLAG_BIT)
+    str     x0, [x1]
+
+    //  Set up recursive paging for TTBR0
+
+    adr     x0, identkmap_l0_ptable
+    add     x1, x0, #(511 * 8)
+    orr     x0, x0, #((DESC_TYPE_TABLE << DESC_TYPE_BIT) | (DESC_VALID << DESC_VALID_BIT))
+    orr     x0, x0, #(ACCESS_FLAG_BIT)
+    str     x0, [x1]
+
+    mov     x30, x22
+
+    ret
+
+// Add a physmap entry
+//   x1: physical address, a multiple of GIGABYTE
+//   x6: address of l2 page table
+build_physmap:
+    ldr     x0, =DEVMAP_VBASE               // x0: Virtual address
+    add     x0, x0, x1
+    mov     x2, #(GIGABYTE - 1)             // x2: Size (minus one to work around errors)
+    mov     x3, #(DEVICE_MEM)               // x3: Attributes to apply
+    adr     x4, kernmap_l0_ptable           // x5: Ptr to L0 table
+    adr     x5, physmap_l1_ptable           // x6: Ptr to L1 table
+    b       build_map
+
+    //  Generic routine to build mappings. Requires the following inputs:
+    //
+    //  x0:  Vaddr to map to Paddr
+    //  x1:  Paddr to map Vaddr to
+    //  x2:  Length (in bytes) of region to map
+    //  x3:  Region attributes
+    //  x4:  Paddr of L0 table to use for mapping
+    //  x5:  Paddr of L1 table to use for mapping
+    //  x6:  Paddr of L2 table to use for mapping
+    //
+    //  To keep things simple everything is mapped using 2MB blocks. This implies that the length
+    //  is explicitly aligned to 2MB to prevent any translation aliases. Since block translations
+    //  at L2 cover 2MB blocks, that suits us nicely so everything uses 2MB L2 blocks. Wasteful
+    //  perhaps but at this stage it's convenient and in any case will get ripped out and
+    //  reprogrammed in kstart.
+
+build_map:
+    lsr     x8, x0, #39                     // First group of 9 bits of VA
+    and     x8, x8, #0x1ff
+    lsl     x8, x8, #3                      // x8: Index into L0 table
+    ldr     x9, [x4, x8]
+    cbnz    x9, l1_idx_prefilled
+
+    mov     x9, x5                          // Get L1 base
+    bfm     w9, wzr, #0, #11
+    orr     x9, x9, #((DESC_TYPE_TABLE << DESC_TYPE_BIT) | (DESC_VALID << DESC_VALID_BIT))
+    orr     x9, x9, #(ACCESS_FLAG_BIT)
+    str     x9, [x4, x8]                    // L0[Index]: L1
+
+l1_idx_prefilled:
+    lsr     x8, x0, #30                     // Second group of 9 bits of VA
+    and     x8, x8, #0x1ff
+    lsl     x8, x8, #3                      // x8: Index into L1 table
+    ldr     x9, [x5, x8]
+    cbnz    x9, l2_idx_prefilled
+
+build_map_l2:
+    mov     x9, x6                          // Get L2 base
+    bfm     w9, wzr, #0, #11
+    orr     x9, x9, #((DESC_TYPE_TABLE << DESC_TYPE_BIT) | (DESC_VALID << DESC_VALID_BIT))
+    orr     x9, x9, #(ACCESS_FLAG_BIT)
+    lsl     x4, x3, #2
+    orr     x9, x9, x4
+    str     x9, [x5, x8]                    // L1[Index]: Base of L2 table
+
+l2_idx_prefilled:
+    lsr     x2, x2, #21                     // Number of 2MB blocks needed */
+    add     x2, x2, #1                      //TODO: remove this and remove workarounds
+
+    lsr     x8, x0, #21                     // Third group of 9 bits of VA
+    and     x8, x8, #0x1ff
+    lsl     x8, x8, #3                      // x8: Index into L2 table
+    ldr     x9, [x6, x8]
+    cbnz    x9, build_map_error
+
+build_map_l2_loop:
+    mov     x9, x1
+    bfm     w9, wzr, #0, #11
+    orr     x9, x9, #((DESC_TYPE_BLOCK << DESC_TYPE_BIT) | (DESC_VALID << DESC_VALID_BIT))
+    orr     x9, x9, #(ACCESS_FLAG_BIT)
+    lsl     x4, x3, #2
+    orr     x9, x9, x4
+    ldr     x10, [x6, x8]
+    mov     x7, #(DESC_VALID << DESC_VALID_BIT)
+    and     x10, x10, x7
+    cmp     x10, x7
+    b.eq    build_map_error
+    str     x9, [x6, x8]                    // L2[Index]: PA of 2MB region to map to
+
+    mov     x9, #1
+    add     x1, x1, x9, lsl #21
+    add     x8, x8, #8
+    sub     x2, x2, #1
+    cbnz    x2, build_map_l2_loop
+
+    ret
+
+build_map_error:
+    wfi
+    b       build_map_error
+
+    //  Statically allocated tables consumed by build_map.
+
+    .align 12
+identkmap_l0_ptable:
+    .space PAGE_SIZE
+identkmap_l1_ptable:
+    .space PAGE_SIZE
+identkmap_l2_ptable:
+    .space PAGE_SIZE
+kernmap_l0_ptable:
+    .space PAGE_SIZE
+kernmap_l1_ptable:
+    .space PAGE_SIZE
+kernmap_l2_ptable:
+    .space PAGE_SIZE
+kstack_l1_ptable:
+    .space PAGE_SIZE
+kstack_l2_ptable:
+    .space PAGE_SIZE
+physmap_l1_ptable:
+    .space PAGE_SIZE
+physmap_1gb_l2_ptable:
+    .space PAGE_SIZE
+physmap_2gb_l2_ptable:
+    .space PAGE_SIZE
+physmap_3gb_l2_ptable:
+    .space PAGE_SIZE
+physmap_4gb_l2_ptable:
+    .space PAGE_SIZE
+env_region:
+    .space PAGE_SIZE
+
+    //  Misc scratch memory used by this file
+
+addr_marker:
+    .quad addr_marker
diff --git a/src/arch/aarch64/init/pre_kstart/helpers/consts.h b/src/arch/aarch64/init/pre_kstart/helpers/consts.h
new file mode 100644
index 0000000000000000000000000000000000000000..d64ac8c742fb03d10f88f7aa24aa9b6f9dd5a78b
--- /dev/null
+++ b/src/arch/aarch64/init/pre_kstart/helpers/consts.h
@@ -0,0 +1,26 @@
+#define PAGE_SIZE               4096
+#define GIGABYTE                0x40000000
+#define VIRT_BITS               48
+#define NUM_TABLES              14
+
+#define EARLY_KSTACK_SIZE       (PAGE_SIZE)                         // Initial stack
+
+#define DEVMAP_VBASE            0xfffffe0000000000
+
+#define SCTLR_M                 0x00000001                          // SCTLR_M bit used to control MMU on/off
+
+#define DEVICE_MEM              0                                   // Memory type specifiers
+#define NORMAL_UNCACHED_MEM     1
+#define NORMAL_CACHED_MEM       2
+
+#define DESC_VALID_BIT          0                                   // Descriptor validity setting
+#define DESC_VALID              1
+#define DESC_INVALID            0
+
+#define DESC_TYPE_BIT           1                                   // Descriptor type
+#define DESC_TYPE_TABLE         1
+#define DESC_TYPE_PAGE          1
+#define DESC_TYPE_BLOCK         0
+
+#define BLOCK_DESC_MASK         (~((0xffff << 48) | (0xffff)))      // Convenience mask for block desciptors
+#define ACCESS_FLAG_BIT         (1 << 10)
diff --git a/src/arch/aarch64/init/pre_kstart/helpers/post_mmu_enabled.S b/src/arch/aarch64/init/pre_kstart/helpers/post_mmu_enabled.S
new file mode 100644
index 0000000000000000000000000000000000000000..cc81b9e96326f60a1d277cafdecbdb43e099aa6c
--- /dev/null
+++ b/src/arch/aarch64/init/pre_kstart/helpers/post_mmu_enabled.S
@@ -0,0 +1,95 @@
+    //  Populates misc arguments, sets up the stack, clears all other registers.
+
+setup_kstart_context:
+    adr  x0, args.kernel_base               // Physical address of kernel base
+    str  x11, [x0]
+
+    adr  x0, args.kernel_size               // Size of kernel image
+    str  x12, [x0]
+
+    adr  x0, args.stack_base                // Virtual address of kernel stack base
+    ldr  x1, =KERNEL_OFFSET
+    add  x1, x1, x12
+    str  x1, [x0]
+
+    adr  x0, args.stack_size                // Size of kernel stack
+    mov  x1, #(EARLY_KSTACK_SIZE)
+    str  x1, [x0]
+
+    adr  x0, args.env_base                  // Virtual address of environment base
+    adr  x1, env_region_marker
+    ldr  x1, [x1]
+    str  x1, [x0]
+
+    adr  x0, args.env_size                  // Size of environment (populated later in kstart)
+    ldr  x1, =PAGE_SIZE
+    str  x1, [x0]
+
+    adr  x0, args.dtb_base                  // Physical address of DTB Image's base
+    str  x19, [x0]
+
+    adr  x0, args.dtb_size                  // Size of DTB image
+    mov  w1, w21
+    str  w1, [x0]
+
+    add  x1, x15, x12                       // Initialize the stack pointer, everything is 2MB aligned
+    add  x1, x1, x14                        // sp = (kbase.vaddr + ksize + stksize) - sizeof(word)
+    sub  x1, x1, #16
+    mov  sp, x1
+
+    adr  x0, tmp_zero                       // Store a zero at tmp_zero
+    str  xzr, [x0]                          // Note: x0 points to addr_marker so we use it below as-is
+
+    ldp  x2, x3, [x0, #0]!                  // Zero x1:x31
+    ldp  x4, x5, [x0, #0]!
+    ldp  x6, x7, [x0, #0]!
+    ldp  x8, x9, [x0, #0]!
+    ldp  x10, x11, [x0, #0]!
+    ldp  x12, x13, [x0, #0]!
+    ldp  x14, x15, [x0, #0]!
+    ldp  x16, x17, [x0, #0]!
+    ldp  x18, x19, [x0, #0]!
+    ldp  x20, x21, [x0, #0]!
+    ldp  x22, x23, [x0, #0]!
+    ldp  x24, x25, [x0, #0]!
+    ldp  x26, x27, [x0, #0]!
+    ldp  x28, x29, [x0, #0]!
+
+    ldr  x0, =args.kernel_base              // x0 = Start of argument block
+    mov  x1, #0
+
+    ret
+
+mmu_on_trampoline:
+    adr     x0, mmu_on_marker               // x0: paddr of mmu_on_marker
+    ldr     x0, [x0]                        // x0: vaddr of mmu_on
+    br      x0                              // MMU now On. Jump to mmu_on using it's vaddr
+
+    // Statically allocated space to hold misc arguments for kstart.
+
+    .align 3
+args.kernel_base:
+    .space 8
+args.kernel_size:
+    .space 8
+args.stack_base:
+    .space 8
+args.stack_size:
+    .space 8
+args.env_base:
+    .space 8
+args.env_size:
+    .space 8
+args.dtb_base:
+    .space 8
+args.dtb_size:
+    .space 8
+
+    //  Misc scratch memory used by this file
+
+env_region_marker:
+    .quad env_region
+mmu_on_marker:
+    .quad mmu_on
+tmp_zero:
+    .quad tmp_zero
diff --git a/src/arch/aarch64/init/pre_kstart/helpers/pre_mmu_enabled.S b/src/arch/aarch64/init/pre_kstart/helpers/pre_mmu_enabled.S
new file mode 100644
index 0000000000000000000000000000000000000000..4fabb484faa3de582ab7308f9ac90efd18bb40db
--- /dev/null
+++ b/src/arch/aarch64/init/pre_kstart/helpers/pre_mmu_enabled.S
@@ -0,0 +1,66 @@
+    //  Stashes DTB size for use later
+    //  Sets up the exception vectors
+early_setup:
+    mov     x19, x0                         // Store paddr of DTB in x19
+    ldr     w21, [x0, #4]                   // x0[4] has the DTB size in Big Endian Format
+    rev     w21, w21                        // Swizzle to little endian
+
+    msr     contextidr_el1, xzr             // Set contextID reg
+    dsb     sy
+
+    ldr     x0, =exception_vector_base
+    msr     vbar_el1, x0
+
+    ret
+
+disable_mmu:
+    mrs     x0, sctlr_el1
+    bic     x0, x0, SCTLR_M
+    msr     sctlr_el1, x0
+    isb
+
+    ret
+
+
+    //  Programs the TTBR registers, MAIR registers, TCR and SCTLR registers.
+enable_mmu:
+    dsb     sy
+
+    adr     x0, identkmap_l0_ptable         // Setup TTBRx_EL1
+    msr     ttbr0_el1, x0                   // ttbr0_el1: Lower vaddrs
+    adr     x1, kernmap_l0_ptable
+    msr     ttbr1_el1, x1                   // ttbr1_el1: Higher vaddrs
+    isb
+
+    tlbi    vmalle1is                       // Invalidate the TLB
+
+    ldr     x2, mair                        // Setup MAIR
+    msr     mair_el1, x2
+
+    ldr     x2, tcr                         // Setup TCR ()ID_AA64MMFR0_EL1)
+    mrs     x3, id_aa64mmfr0_el1
+    bfi     x2, x3, #32, #3
+    msr     tcr_el1, x2
+    isb
+
+    ldr     x2, sctlr_set_bits              // Setup SCTLR
+    ldr     x3, sctlr_clr_bits
+    mrs     x1, sctlr_el1
+    bic     x1, x1, x3
+    orr     x1, x1, x2
+    msr     sctlr_el1, x1
+    isb
+    mrs     x1, sctlr_el1
+
+    ret
+
+    //  Magic config runes (Too much detail to enumerate here: grep the ARM ARM for details)
+    .align 3
+mair:
+    .quad   0xff4400                        // MAIR: Arrange for Device, Normal Non-Cache, Normal Write-Back access types
+tcr:
+    .quad   0x1085100510                    // Setup TCR: (TxSZ, ASID_16, TG1_4K, Cache Attrs, SMP Attrs)
+sctlr_set_bits:
+    .quad   0x3485d13d                      // Set SCTLR bits: (LSMAOE, nTLSMD, UCI, SPAN, nTWW, nTWI, UCT, DZE, I, SED, SA0, SA, C, M, CP15BEN)
+sctlr_clr_bits:
+    .quad   0x32802c2                       // Clear SCTLR bits: (EE, EOE, IESB, WXN, UMA, ITD, THEE, A)
diff --git a/src/arch/aarch64/init/pre_kstart/helpers/vectors.S b/src/arch/aarch64/init/pre_kstart/helpers/vectors.S
new file mode 100644
index 0000000000000000000000000000000000000000..0406506f2731d7980cb41dbecb19a39cd61000c5
--- /dev/null
+++ b/src/arch/aarch64/init/pre_kstart/helpers/vectors.S
@@ -0,0 +1,123 @@
+    //  Exception vector stubs
+    //
+    //  The hex values in x18 are to aid debugging
+    //  Unhandled exceptions spin in a wfi loop for the moment
+    //  This can be macro-ified
+
+    .align 11
+exception_vector_base:
+
+    // Synchronous
+    .align 7
+__vec_00:
+    mov     x18, #0xb0b0
+    b       synchronous_exception_at_el1_with_sp0
+    b       __vec_00
+
+    // IRQ
+    .align 7
+__vec_01:
+    mov     x18, #0xb0b1
+    b       irq_at_el1
+    b       __vec_01
+
+    // FIQ
+    .align 7
+__vec_02:
+    mov     x18, #0xb0b2
+    b       unhandled_exception
+    b       __vec_02
+
+    // SError
+    .align 7
+__vec_03:
+    mov     x18, #0xb0b3
+    b       unhandled_exception
+    b       __vec_03
+
+    // Synchronous
+    .align 7
+__vec_04:
+    mov     x18, #0xb0b4
+    b       synchronous_exception_at_el1_with_spx
+    b       __vec_04
+
+    // IRQ
+    .align 7
+__vec_05:
+    mov     x18, #0xb0b5
+    b       irq_at_el1
+    b       __vec_05
+
+    // FIQ
+    .align 7
+__vec_06:
+    mov     x18, #0xb0b6
+    b       unhandled_exception
+    b       __vec_06
+
+    // SError
+    .align 7
+__vec_07:
+    mov     x18, #0xb0b7
+    b       unhandled_exception
+    b       __vec_07
+
+    // Synchronous
+    .align 7
+__vec_08:
+    mov     x18, #0xb0b8
+    b       synchronous_exception_at_el0
+    b       __vec_08
+
+    // IRQ
+    .align 7
+__vec_09:
+    mov     x18, #0xb0b9
+    b       irq_at_el0
+    b       __vec_09
+
+    // FIQ
+    .align 7
+__vec_10:
+    mov     x18, #0xb0ba
+    b       unhandled_exception
+    b       __vec_10
+
+    // SError
+    .align 7
+__vec_11:
+    mov     x18, #0xb0bb
+    b       unhandled_exception
+    b       __vec_11
+
+    // Synchronous
+    .align 7
+__vec_12:
+    mov     x18, #0xb0bc
+    b       unhandled_exception
+    b       __vec_12
+
+    // IRQ
+    .align 7
+__vec_13:
+    mov     x18, #0xb0bd
+    b       unhandled_exception
+    b       __vec_13
+
+    // FIQ
+    .align 7
+__vec_14:
+    mov     x18, #0xb0be
+    b       unhandled_exception
+    b       __vec_14
+
+    // SError
+    .align 7
+__vec_15:
+    mov     x18, #0xb0bf
+    b       unhandled_exception
+    b       __vec_15
+    
+    .align 7
+exception_vector_end:
diff --git a/src/arch/aarch64/interrupt/exception.rs b/src/arch/aarch64/interrupt/exception.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b481591f42c59a266c1c1b4b2d5fbd78132adb7d
--- /dev/null
+++ b/src/arch/aarch64/interrupt/exception.rs
@@ -0,0 +1,62 @@
+use crate::{
+    context,
+    cpu_id,
+    interrupt::stack_trace,
+    syscall,
+    syscall::flag::*,
+
+    with_exception_stack,
+    exception_stack,
+};
+
+exception_stack!(synchronous_exception_at_el1_with_sp0, |stack| {
+    println!("Synchronous exception at EL1 with SP0");
+    stack.dump();
+    stack_trace();
+    loop {}
+});
+
+exception_stack!(synchronous_exception_at_el1_with_spx, |stack| {
+    println!("Synchronous exception at EL1 with SPx");
+    stack.dump();
+    stack_trace();
+    loop {}
+});
+
+exception_stack!(synchronous_exception_at_el0, |stack| {
+    with_exception_stack!(|stack| {
+        let fp;
+        asm!("mov {}, fp", out(reg) fp);
+
+        let exception_code = (stack.iret.esr_el1 & (0x3f << 26)) >> 26;
+        if exception_code != 0b010101 {
+            println!("FATAL: Not an SVC induced synchronous exception");
+            stack.dump();
+            stack_trace();
+
+            println!("CPU {}, PID {:?}", cpu_id(), context::context_id());
+
+            // This could deadlock, but at this point we are going to halt anyways
+            {
+                let contexts = context::contexts();
+                if let Some(context_lock) = contexts.current() {
+                    let context = context_lock.read();
+                    println!("NAME: {}", *context.name.read());
+                }
+            }
+
+            // Halt
+            loop {}
+        }
+
+        let scratch = &stack.scratch;
+        syscall::syscall(scratch.x8, scratch.x0, scratch.x1, scratch.x2, scratch.x3, scratch.x4, fp, stack)
+    });
+});
+
+exception_stack!(unhandled_exception, |stack| {
+    println!("Unhandled exception");
+    stack.dump();
+    stack_trace();
+    loop {}
+});
diff --git a/src/arch/aarch64/interrupt/handler.rs b/src/arch/aarch64/interrupt/handler.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9be3973a5ebcbad009d6b98c1f38bd5cac3563cd
--- /dev/null
+++ b/src/arch/aarch64/interrupt/handler.rs
@@ -0,0 +1,330 @@
+use crate::syscall::IntRegisters;
+
+#[derive(Default)]
+#[repr(packed)]
+pub struct ScratchRegisters {
+    pub x0: usize,
+    pub x1: usize,
+    pub x2: usize,
+    pub x3: usize,
+    pub x4: usize,
+    pub x5: usize,
+    pub x6: usize,
+    pub x7: usize,
+    pub x8: usize,
+    pub x9: usize,
+    pub x10: usize,
+    pub x11: usize,
+    pub x12: usize,
+    pub x13: usize,
+    pub x14: usize,
+    pub x15: usize,
+    pub x16: usize,
+    pub x17: usize,
+    pub x18: usize,
+    pub padding: usize,
+}
+
+impl ScratchRegisters {
+    pub fn dump(&self) {
+        println!("X0:    {:>016X}", { self.x0 });
+        println!("X1:    {:>016X}", { self.x1 });
+        println!("X2:    {:>016X}", { self.x2 });
+        println!("X3:    {:>016X}", { self.x3 });
+        println!("X4:    {:>016X}", { self.x4 });
+        println!("X5:    {:>016X}", { self.x5 });
+        println!("X6:    {:>016X}", { self.x6 });
+        println!("X7:    {:>016X}", { self.x7 });
+        println!("X8:    {:>016X}", { self.x8 });
+        println!("X9:    {:>016X}", { self.x9 });
+        println!("X10:   {:>016X}", { self.x10 });
+        println!("X11:   {:>016X}", { self.x11 });
+        println!("X12:   {:>016X}", { self.x12 });
+        println!("X13:   {:>016X}", { self.x13 });
+        println!("X14:   {:>016X}", { self.x14 });
+        println!("X15:   {:>016X}", { self.x15 });
+        println!("X16:   {:>016X}", { self.x16 });
+        println!("X17:   {:>016X}", { self.x17 });
+        println!("X18:   {:>016X}", { self.x18 });
+    }
+}
+
+#[derive(Default)]
+#[repr(packed)]
+pub struct PreservedRegisters {
+    //TODO: is X30 a preserved register?
+    pub x19: usize,
+    pub x20: usize,
+    pub x21: usize,
+    pub x22: usize,
+    pub x23: usize,
+    pub x24: usize,
+    pub x25: usize,
+    pub x26: usize,
+    pub x27: usize,
+    pub x28: usize,
+    pub x29: usize,
+    pub x30: usize,
+}
+
+impl PreservedRegisters {
+    pub fn dump(&self) {
+        println!("X19:   {:>016X}", { self.x19 });
+        println!("X20:   {:>016X}", { self.x20 });
+        println!("X21:   {:>016X}", { self.x21 });
+        println!("X22:   {:>016X}", { self.x22 });
+        println!("X23:   {:>016X}", { self.x23 });
+        println!("X24:   {:>016X}", { self.x24 });
+        println!("X25:   {:>016X}", { self.x25 });
+        println!("X26:   {:>016X}", { self.x26 });
+        println!("X27:   {:>016X}", { self.x27 });
+        println!("X28:   {:>016X}", { self.x28 });
+        println!("X29:   {:>016X}", { self.x29 });
+        println!("X30:   {:>016X}", { self.x30 });
+    }
+}
+
+#[derive(Default)]
+#[repr(packed)]
+pub struct IretRegisters {
+                            // occurred
+                            // The exception vector disambiguates at which EL the interrupt
+    pub sp_el0: usize,      // Shouldn't be used if interrupt occurred at EL1
+    pub esr_el1: usize,
+    pub spsr_el1: usize,
+    pub tpidrro_el0: usize,
+    pub tpidr_el0: usize,
+    pub elr_el1: usize,
+}
+
+impl IretRegisters {
+    pub fn dump(&self) {
+        println!("ELR_EL1: {:>016X}", { self.elr_el1 });
+        println!("TPIDR_EL0: {:>016X}", { self.tpidr_el0 });
+        println!("TPIDRRO_EL0: {:>016X}", { self.tpidrro_el0 });
+        println!("SPSR_EL1: {:>016X}", { self.spsr_el1 });
+        println!("ESR_EL1: {:>016X}", { self.esr_el1 });
+        println!("SP_EL0: {:>016X}", { self.sp_el0 });
+    }
+}
+
+#[derive(Default)]
+#[repr(packed)]
+pub struct InterruptStack {
+    pub iret: IretRegisters,
+    pub scratch: ScratchRegisters,
+    pub preserved: PreservedRegisters,
+}
+
+impl InterruptStack {
+    pub fn dump(&self) {
+        self.iret.dump();
+        self.scratch.dump();
+        self.preserved.dump();
+    }
+
+    /// Saves all registers to a struct used by the proc:
+    /// scheme to read/write registers.
+    pub fn save(&self, all: &mut IntRegisters) {
+        all.elr_el1 = self.iret.elr_el1;
+        all.tpidr_el0 = self.iret.tpidr_el0;
+        all.tpidrro_el0 = self.iret.tpidrro_el0;
+        all.spsr_el1 = self.iret.spsr_el1;
+        all.esr_el1 = self.iret.esr_el1;
+        all.sp_el0 = self.iret.sp_el0;
+        all.padding = 0;
+        all.x30 = self.preserved.x30;
+        all.x29 = self.preserved.x29;
+        all.x28 = self.preserved.x28;
+        all.x27 = self.preserved.x27;
+        all.x26 = self.preserved.x26;
+        all.x25 = self.preserved.x25;
+        all.x24 = self.preserved.x24;
+        all.x23 = self.preserved.x23;
+        all.x22 = self.preserved.x22;
+        all.x21 = self.preserved.x21;
+        all.x20 = self.preserved.x20;
+        all.x19 = self.preserved.x19;
+        all.x18 = self.scratch.x18;
+        all.x17 = self.scratch.x17;
+        all.x16 = self.scratch.x16;
+        all.x15 = self.scratch.x15;
+        all.x14 = self.scratch.x14;
+        all.x13 = self.scratch.x13;
+        all.x12 = self.scratch.x12;
+        all.x11 = self.scratch.x11;
+        all.x10 = self.scratch.x10;
+        all.x9 = self.scratch.x9;
+        all.x8 = self.scratch.x8;
+        all.x7 = self.scratch.x7;
+        all.x6 = self.scratch.x6;
+        all.x5 = self.scratch.x5;
+        all.x4 = self.scratch.x4;
+        all.x3 = self.scratch.x3;
+        all.x2 = self.scratch.x2;
+        all.x1 = self.scratch.x1;
+        all.x0 = self.scratch.x0;
+    }
+
+    //TODO
+    pub fn is_singlestep(&self) -> bool { false }
+    pub fn set_singlestep(&mut self, singlestep: bool) {}
+}
+
+#[macro_export]
+macro_rules! aarch64_asm {
+    ($($strings:expr,)+) => {
+        global_asm!(concat!(
+            $($strings),+,
+        ));
+    };
+}
+
+#[macro_export]
+macro_rules! function {
+    ($name:ident => { $($body:expr,)+ }) => {
+        aarch64_asm!(
+            ".global ", stringify!($name), "\n",
+            ".type ", stringify!($name), ", @function\n",
+            ".section .text.", stringify!($name), ", \"ax\", @progbits\n",
+            stringify!($name), ":\n",
+            $($body),+,
+            ".size ", stringify!($name), ", . - ", stringify!($name), "\n",
+            ".text\n",
+        );
+        extern "C" {
+            pub fn $name();
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! push_scratch {
+    () => { "
+        // Push scratch registers
+        stp     x18, x18, [sp, #-16]!
+        stp     x16, x17, [sp, #-16]!
+        stp     x14, x15, [sp, #-16]!
+        stp     x12, x13, [sp, #-16]!
+        stp     x10, x11, [sp, #-16]!
+        stp     x8, x9, [sp, #-16]!
+        stp     x6, x7, [sp, #-16]!
+        stp     x4, x5, [sp, #-16]!
+        stp     x2, x3, [sp, #-16]!
+        stp     x0, x1, [sp, #-16]!
+    " };
+}
+
+#[macro_export]
+macro_rules! pop_scratch {
+    () => { "
+        // Pop scratch registers
+        ldp     x0, x1, [sp], #16
+        ldp     x2, x3, [sp], #16
+        ldp     x4, x5, [sp], #16
+        ldp     x6, x7, [sp], #16
+        ldp     x8, x9, [sp], #16
+        ldp     x10, x11, [sp], #16
+        ldp     x12, x13, [sp], #16
+        ldp     x14, x15, [sp], #16
+        ldp     x16, x17, [sp], #16
+        ldp     x18, x18, [sp], #16
+    " };
+}
+
+#[macro_export]
+macro_rules! push_preserved {
+    () => { "
+        // Push preserved registers
+        stp     x29, x30, [sp, #-16]!
+        stp     x27, x28, [sp, #-16]!
+        stp     x25, x26, [sp, #-16]!
+        stp     x23, x24, [sp, #-16]!
+        stp     x21, x22, [sp, #-16]!
+        stp     x19, x20, [sp, #-16]!
+    " };
+}
+
+#[macro_export]
+macro_rules! pop_preserved {
+    () => { "
+        // Pop preserved registers
+        ldp     x19, x20, [sp], #16
+        ldp     x21, x22, [sp], #16
+        ldp     x23, x24, [sp], #16
+        ldp     x25, x26, [sp], #16
+        ldp     x27, x28, [sp], #16
+        ldp     x29, x30, [sp], #16
+    " };
+}
+
+#[macro_export]
+macro_rules! push_special {
+    () => { "
+        mrs     x14, tpidr_el0
+        mrs     x15, elr_el1
+        stp     x14, x15, [sp, #-16]!
+
+        mrs     x14, spsr_el1
+        mrs     x15, tpidrro_el0
+        stp     x14, x15, [sp, #-16]!
+
+        mrs     x14, sp_el0
+        mrs     x15, esr_el1
+        stp     x14, x15, [sp, #-16]!
+    " };
+}
+
+#[macro_export]
+macro_rules! pop_special {
+    () => { "
+        ldp     x14, x15, [sp], 16
+        msr     esr_el1, x15
+        msr     sp_el0, x14
+
+        ldp     x14, x15, [sp], 16
+        msr     tpidrro_el0, x15
+        msr     spsr_el1, x14
+        
+        ldp     x14, x15, [sp], 16
+        msr     elr_el1, x15
+        msr     tpidr_el0, x14
+    " };
+}
+
+#[macro_export]
+macro_rules! exception_stack {
+    ($name:ident, |$stack:ident| $code:block) => {
+        paste::item! {
+            #[no_mangle]
+            unsafe extern "C" fn [<__exception_ $name>](stack: *mut $crate::arch::aarch64::interrupt::InterruptStack) {
+                // This inner function is needed because macros are buggy:
+                // https://github.com/dtolnay/paste/issues/7
+                #[inline(always)]
+                unsafe fn inner($stack: &mut $crate::arch::aarch64::interrupt::InterruptStack) {
+                    $code
+                }
+                inner(&mut *stack);
+            }
+
+            function!($name => {
+                // Backup all userspace registers to stack
+                push_preserved!(),
+                push_scratch!(),
+                push_special!(),
+
+                // Call inner function with pointer to stack
+                "mov x29, sp\n",
+                "mov x0, sp\n",
+                "bl __exception_", stringify!($name), "\n",
+
+                // Restore all userspace registers
+                pop_special!(),
+                pop_scratch!(),
+                pop_preserved!(),
+
+                "eret\n",
+            });
+        }
+    };
+}
diff --git a/src/arch/aarch64/interrupt/irq.rs b/src/arch/aarch64/interrupt/irq.rs
new file mode 100644
index 0000000000000000000000000000000000000000..27636e4278a4e7d56513b83b5169fc0abb144d96
--- /dev/null
+++ b/src/arch/aarch64/interrupt/irq.rs
@@ -0,0 +1,74 @@
+use core::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
+
+use crate::context;
+use crate::context::timeout;
+use crate::device::generic_timer::{GENTIMER};
+use crate::device::{gic};
+use crate::device::serial::{COM1};
+use crate::time;
+
+use crate::{exception_stack};
+
+//resets to 0 in context::switch()
+pub static PIT_TICKS: AtomicUsize = ATOMIC_USIZE_INIT;
+
+exception_stack!(irq_at_el0, |stack| {
+    match gic::irq_ack() {
+        30 => irq_handler_gentimer(30),
+        33 => irq_handler_com1(33),
+        _ => panic!("irq_demux: unregistered IRQ"),
+    }
+});
+
+exception_stack!(irq_at_el1, |stack| {
+    match gic::irq_ack() {
+        30 => irq_handler_gentimer(30),
+        33 => irq_handler_com1(33),
+        _ => panic!("irq_demux: unregistered IRQ"),
+    }
+});
+
+unsafe fn trigger(irq: u32) {
+    extern {
+        fn irq_trigger(irq: u32);
+    }
+
+    irq_trigger(irq);
+    gic::irq_eoi(irq);
+}
+
+pub unsafe fn acknowledge(_irq: usize) {
+}
+
+pub unsafe fn irq_handler_com1(irq: u32) {
+    if let Some(ref mut serial_port) = *COM1.lock() {
+        serial_port.receive();
+    };
+    trigger(irq);
+}
+
+pub unsafe fn irq_handler_gentimer(irq: u32) {
+    GENTIMER.clear_irq();
+    {
+        let mut offset = time::OFFSET.lock();
+        let sum = offset.1 + GENTIMER.clk_freq as u64;
+        offset.1 = sum % 1_000_000_000;
+        offset.0 += sum / 1_000_000_000;
+    }
+
+    timeout::trigger();
+
+    if PIT_TICKS.fetch_add(1, Ordering::SeqCst) >= 10 {
+        let _ = context::switch();
+    }
+    trigger(irq);
+    GENTIMER.reload_count();
+}
+
+unsafe fn irq_demux() {
+    match gic::irq_ack() {
+        30 => irq_handler_gentimer(30),
+        33 => irq_handler_com1(33),
+        _ => panic!("irq_demux: unregistered IRQ"),
+    }
+}
diff --git a/src/arch/aarch64/interrupt/mod.rs b/src/arch/aarch64/interrupt/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4639332f0bcfa84f272305aaa3718862cd2d6edb
--- /dev/null
+++ b/src/arch/aarch64/interrupt/mod.rs
@@ -0,0 +1,75 @@
+//! Interrupt instructions
+
+#[macro_use]
+pub mod handler;
+
+pub mod exception;
+pub mod irq;
+pub mod syscall;
+pub mod trace;
+
+pub use self::handler::InterruptStack;
+pub use self::trace::stack_trace;
+
+/// Clear interrupts
+#[inline(always)]
+pub unsafe fn disable() {
+    llvm_asm!("msr daifset, #2");
+}
+
+/// Set interrupts
+#[inline(always)]
+pub unsafe fn enable() {
+    llvm_asm!("msr daifclr, #2");
+}
+
+/// Set interrupts and halt
+/// This will atomically wait for the next interrupt
+/// Performing enable followed by halt is not guaranteed to be atomic, use this instead!
+#[inline(always)]
+pub unsafe fn enable_and_halt() {
+    llvm_asm!("msr daifclr, #2");
+    llvm_asm!("wfi");
+}
+
+/// Set interrupts and nop
+/// This will enable interrupts and allow the IF flag to be processed
+/// Simply enabling interrupts does not gurantee that they will trigger, use this instead!
+#[inline(always)]
+pub unsafe fn enable_and_nop() {
+    llvm_asm!("msr daifclr, #2");
+    llvm_asm!("nop");
+}
+
+/// Halt instruction
+#[inline(always)]
+pub unsafe fn halt() {
+    llvm_asm!("wfi");
+}
+
+/// Pause instruction
+/// Safe because it is similar to a NOP, and has no memory effects
+#[inline(always)]
+pub fn pause() {
+    unsafe { llvm_asm!("nop") };
+}
+
+pub fn available_irqs_iter(cpu_id: usize) -> impl Iterator<Item = u8> + 'static {
+    0..0
+}
+
+pub fn bsp_apic_id() -> Option<u32> {
+    //TODO
+    None
+}
+
+#[inline]
+pub fn is_reserved(cpu_id: usize, index: u8) -> bool {
+    //TODO
+    true
+}
+
+#[inline]
+pub fn set_reserved(cpu_id: usize, index: u8, reserved: bool) {
+    //TODO
+}
diff --git a/src/arch/aarch64/interrupt/syscall.rs b/src/arch/aarch64/interrupt/syscall.rs
new file mode 100644
index 0000000000000000000000000000000000000000..948cd5d4581b63ecc1dab568c9ed434d3a5a3ba7
--- /dev/null
+++ b/src/arch/aarch64/interrupt/syscall.rs
@@ -0,0 +1,69 @@
+use crate::{
+    arch::{interrupt::InterruptStack},
+    context,
+    syscall,
+    syscall::flag::{PTRACE_FLAG_IGNORE, PTRACE_STOP_PRE_SYSCALL, PTRACE_STOP_POST_SYSCALL},
+};
+
+#[no_mangle]
+pub unsafe extern fn do_exception_unhandled() {}
+
+#[no_mangle]
+pub unsafe extern fn do_exception_synchronous() {}
+
+#[allow(dead_code)]
+#[repr(packed)]
+pub struct SyscallStack {
+    pub elr_el1: usize,
+    pub padding: usize,
+    pub tpidr: usize,
+    pub tpidrro: usize,
+    pub rflags: usize,
+    pub esr: usize,
+    pub sp: usize,
+    pub lr: usize,
+    pub fp: usize,
+    pub x28: usize,
+    pub x27: usize,
+    pub x26: usize,
+    pub x25: usize,
+    pub x24: usize,
+    pub x23: usize,
+    pub x22: usize,
+    pub x21: usize,
+    pub x20: usize,
+    pub x19: usize,
+    pub x18: usize,
+    pub x17: usize,
+    pub x16: usize,
+    pub x15: usize,
+    pub x14: usize,
+    pub x13: usize,
+    pub x12: usize,
+    pub x11: usize,
+    pub x10: usize,
+    pub x9: usize,
+    pub x8: usize,
+    pub x7: usize,
+    pub x6: usize,
+    pub x5: usize,
+    pub x4: usize,
+    pub x3: usize,
+    pub x2: usize,
+    pub x1: usize,
+    pub x0: usize,
+}
+
+#[macro_export]
+macro_rules! with_exception_stack {
+    (|$stack:ident| $code:block) => {{
+            let $stack = &mut *$stack;
+            (*$stack).scratch.x0 = $code;
+    }}
+}
+
+function!(clone_ret => {
+    "ldp x29, x30, [sp], #16\n",
+    "mov sp, x29\n",
+    "ret\n",
+});
diff --git a/src/arch/aarch64/interrupt/trace.rs b/src/arch/aarch64/interrupt/trace.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ca1d47290be39b5d9fca4434eb8e2911afc9927f
--- /dev/null
+++ b/src/arch/aarch64/interrupt/trace.rs
@@ -0,0 +1,149 @@
+use core::mem;
+use goblin::elf::sym;
+
+use crate::paging::{ActivePageTable, PageTableType, VirtualAddress};
+
+/// Get a stack trace
+//TODO: Check for stack being mapped before dereferencing
+#[inline(never)]
+pub unsafe fn stack_trace() {
+    let mut fp: usize;
+    llvm_asm!("" : "={fp}"(fp) : : : "volatile");
+
+    println!("TRACE: {:>016x}", fp);
+    //Maximum 64 frames
+    let active_ktable = ActivePageTable::new(PageTableType::Kernel);
+    let active_utable = ActivePageTable::new(PageTableType::User);
+    let in_kernel_or_user_table = |ptr| {
+        active_ktable.translate(VirtualAddress::new(ptr)).is_some() ||
+        active_utable.translate(VirtualAddress::new(ptr)).is_some()
+    };
+    for _frame in 0..64 {
+        if let Some(pc_fp) = fp.checked_add(mem::size_of::<usize>()) {
+            if in_kernel_or_user_table(fp) && in_kernel_or_user_table(pc_fp) {
+                let pc = *(pc_fp as *const usize);
+                if pc == 0 {
+                    println!(" {:>016x}: EMPTY RETURN", fp);
+                    break;
+                }
+                println!("  FP {:>016x}: PC {:>016x}", fp, pc);
+                fp = *(fp as *const usize);
+                //TODO symbol_trace(pc);
+            } else {
+                println!("  {:>016x}: GUARD PAGE", fp);
+                break;
+            }
+        } else {
+            println!("  {:>016x}: fp OVERFLOW", fp);
+        }
+    }
+}
+///
+/// Get a symbol
+//TODO: Do not create Elf object for every symbol lookup
+#[inline(never)]
+pub unsafe fn symbol_trace(addr: usize) {
+    use core::slice;
+    use core::sync::atomic::Ordering;
+
+    use crate::elf::Elf;
+    use crate::start::{KERNEL_BASE, KERNEL_SIZE};
+
+    let kernel_ptr = (KERNEL_BASE.load(Ordering::SeqCst) + crate::KERNEL_OFFSET) as *const u8;
+    let kernel_slice = slice::from_raw_parts(kernel_ptr, KERNEL_SIZE.load(Ordering::SeqCst));
+
+    println!("symbol_trace: 0, kernel_ptr = 0x{:x}", kernel_ptr as usize);
+
+    match Elf::from(kernel_slice) {
+        Ok(elf) => {
+            println!("symbol_trace: 1");
+            let mut strtab_opt = None;
+            for section in elf.sections() {
+                if section.sh_type == ::goblin::elf::section_header::SHT_STRTAB {
+                    strtab_opt = Some(section);
+                    break;
+                }
+            }
+
+            println!("symbol_trace: 2");
+
+            if let Some(symbols) = elf.symbols() {
+                println!("symbol_trace: 3");
+                for sym in symbols {
+                    if sym::st_type(sym.st_info) == sym::STT_FUNC
+                        && addr >= sym.st_value as usize
+                            && addr < (sym.st_value + sym.st_size) as usize
+                            {
+                                println!("    {:>016X}+{:>04X}", sym.st_value, addr - sym.st_value as usize);
+
+                                if let Some(strtab) = strtab_opt {
+                                    let start = strtab.sh_offset as usize + sym.st_name as usize;
+                                    let mut end = start;
+                                    while end < elf.data.len() {
+                                        let b = elf.data[end];
+                                        end += 1;
+                                        if b == 0 {
+                                            break;
+                                        }
+                                    }
+
+                                    if end > start {
+                                        let sym_name = &elf.data[start .. end];
+
+                                        print!("    ");
+
+                                        if sym_name.starts_with(b"_ZN") {
+                                            // Skip _ZN
+                                            let mut i = 3;
+                                            let mut first = true;
+                                            while i < sym_name.len() {
+                                                // E is the end character
+                                                if sym_name[i] == b'E' {
+                                                    break;
+                                                }
+
+                                                // Parse length string
+                                                let mut len = 0;
+                                                while i < sym_name.len() {
+                                                    let b = sym_name[i];
+                                                    if b >= b'0' && b <= b'9' {
+                                                        i += 1;
+                                                        len *= 10;
+                                                        len += (b - b'0') as usize;
+                                                    } else {
+                                                        break;
+                                                    }
+                                                }
+
+                                                // Print namespace seperator, if required
+                                                if first {
+                                                    first = false;
+                                                } else {
+                                                    print!("::");
+                                                }
+
+                                                // Print name string
+                                                let end = i + len;
+                                                while i < sym_name.len() && i < end {
+                                                    print!("{}", sym_name[i] as char);
+                                                    i += 1;
+                                                }
+                                            }
+                                        } else {
+                                            for &b in sym_name.iter() {
+                                                print!("{}", b as char);
+                                            }
+                                        }
+
+                                        println!("");
+                                    }
+                                }
+                            }
+                }
+            }
+        },
+        Err(_e) => {
+            println!("WTF ?");
+        }
+    }
+}
diff --git a/src/arch/aarch64/ipi.rs b/src/arch/aarch64/ipi.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3f8e5cd779ece9f5a32554897652c41d062cbd05
--- /dev/null
+++ b/src/arch/aarch64/ipi.rs
@@ -0,0 +1,24 @@
+#[derive(Clone, Copy, Debug)]
+#[repr(u8)]
+pub enum IpiKind {
+    Wakeup = 0x40,
+    Tlb = 0x41,
+    Switch = 0x42,
+    Pit = 0x43,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(u8)]
+pub enum IpiTarget {
+    Current = 1,
+    All = 2,
+    Other = 3,
+}
+
+#[cfg(not(feature = "multi_core"))]
+#[inline(always)]
+pub fn ipi(_kind: IpiKind, _target: IpiTarget) {}
+
+#[cfg(feature = "multi_core")]
+#[inline(always)]
+pub fn ipi(kind: IpiKind, target: IpiTarget) {}
diff --git a/src/arch/aarch64/macros.rs b/src/arch/aarch64/macros.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4e3566fc55b8d94aeed5546f646afedf3cb6c8fc
--- /dev/null
+++ b/src/arch/aarch64/macros.rs
@@ -0,0 +1,16 @@
+/// Print to console
+#[macro_export]
+macro_rules! print {
+    ($($arg:tt)*) => ({
+        use core::fmt::Write;
+        let _ = write!($crate::arch::debug::Writer::new(), $($arg)*);
+    });
+}
+
+/// Print with new line to console
+#[macro_export]
+macro_rules! println {
+    () => (print!("\n"));
+    ($fmt:expr) => (print!(concat!($fmt, "\n")));
+    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
+}
diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dd140e6a562f31102a97b9e7c9f2af21c227e23c
--- /dev/null
+++ b/src/arch/aarch64/mod.rs
@@ -0,0 +1,31 @@
+#[macro_use]
+pub mod macros;
+
+/// Constants like memory locations
+pub mod consts;
+
+/// Debugging support
+pub mod debug;
+
+/// Devices
+pub mod device;
+
+/// Interrupt instructions
+pub mod interrupt;
+
+/// Inter-processor interrupts
+pub mod ipi;
+
+/// Paging
+pub mod paging;
+
+pub mod rmm;
+
+/// Initialization and start function
+pub mod start;
+
+/// Stop function
+pub mod stop;
+
+/// Early init support
+pub mod init;
diff --git a/src/arch/aarch64/paging/entry.rs b/src/arch/aarch64/paging/entry.rs
new file mode 100644
index 0000000000000000000000000000000000000000..31f38edd0d80ebc3600ff444d3a851d1564264bd
--- /dev/null
+++ b/src/arch/aarch64/paging/entry.rs
@@ -0,0 +1,163 @@
+//! # Page table entry
+//! Some code borrowed from [Phil Opp's Blog](http://os.phil-opp.com/modifying-page-tables.html)
+
+use crate::memory::Frame;
+
+use super::PhysicalAddress;
+
+/// A page table entry
+#[derive(Debug)]
+pub struct Entry(u64);
+
+/// A page descriptor
+#[derive(Debug)]
+pub struct PageDescriptor(u64);
+
+bitflags! {
+    pub struct TableDescriptorFlags: u64 {
+        const PRESENT =                     1 << 0;
+        const VALID =                       1 << 0;
+        const TABLE =                       1 << 1;
+        const AF =                          1 << 10;    /* NOTE: TableDescriptors don't actually have an AF bit! */
+        const PXNTABLE =                    1 << 59;
+        const UXNTABLE =                    1 << 60;
+        const APTABLE_0 =                   1 << 61;
+        const APTABLE_1 =                   1 << 62;
+        const SUBLEVEL_NO_EL0_ACCESS =      (0 << 62) | (1 << 61);
+        const SUBLEVEL_NO_WANY_ACCESS =     (1 << 62) | (0 << 61);
+        const SUBLEVEL_NO_WANY_NO_REL0 =    (1 << 62) | (1 << 61);
+        const NSTABLE =                     1 << 63;
+    }
+}
+
+bitflags! {
+    pub struct PageDescriptorFlags: u64 {
+        const PRESENT =             1 << 0;
+        const VALID =               1 << 0;
+        const PAGE =                1 << 1;
+        const ATTR_INDEX_0 =        1 << 2;
+        const ATTR_INDEX_1 =        1 << 3;
+        const ATTR_INDEX_2 =        1 << 4;
+        const NS =                  1 << 5;
+        const AP_1 =                1 << 6;
+        const AP_2 =                1 << 7;
+        const SH_0 =                1 << 8;
+        const SH_1 =                1 << 9;
+        const AF =                  1 << 10;
+        const NG =                  1 << 11;
+        const DBM =                 1 << 51;
+        const CONTIGUOUS =          1 << 52;
+        const PXN =                 1 << 53;
+        const UXN =                 1 << 54;
+    }
+}
+
+// These are 'virtual' flags that are used to minimise changes to the generic paging code.
+// These are translated to AArch64 specific Page and Table descriptors as and when needed.
+bitflags! {
+    #[derive(Default)]
+    pub struct EntryFlags: u64 {
+        const PRESENT =             1 << 0;
+        const HUGE_PAGE =           1 << 1;
+        const GLOBAL =              1 << 2;
+        const NO_EXECUTE =          1 << 3;
+        const USER_ACCESSIBLE =     1 << 4;
+        const WRITABLE =            1 << 5;
+        const TLS =                 1 << 6;
+        const AF =                  1 << 10;
+    }
+}
+
+pub const ADDRESS_MASK: usize = 0x0000_ffff_ffff_f000;
+pub const COUNTER_MASK: u64 = 0x0008_0000_0000_0000;
+
+impl Entry {
+    /// Clear entry
+    pub fn set_zero(&mut self) {
+        self.0 = 0;
+    }
+
+    /// Is the entry unused?
+    pub fn is_unused(&self) -> bool {
+        self.0 == (self.0 & COUNTER_MASK)
+    }
+
+    /// Make the entry unused
+    pub fn set_unused(&mut self) {
+        self.0 &= COUNTER_MASK;
+    }
+
+    /// Get the address this page references
+    pub fn address(&self) -> PhysicalAddress {
+        PhysicalAddress::new(self.0 as usize & ADDRESS_MASK)
+    }
+
+    /// Get the current entry flags
+    pub fn page_table_entry_flags(&self) -> TableDescriptorFlags {
+        TableDescriptorFlags::from_bits_truncate(self.0)
+    }
+
+    pub fn page_descriptor_entry_flags(&self) -> PageDescriptorFlags {
+        PageDescriptorFlags::from_bits_truncate(self.0)
+    }
+
+    /// Get the current entry flags
+    pub fn flags(&self) -> EntryFlags {
+        EntryFlags::from_bits_truncate(self.0)
+    }
+
+    /// Get the associated frame, if available, for a level 4, 3, or 2 page
+    pub fn pointed_frame(&self) -> Option<Frame> {
+        if self.page_table_entry_flags().contains(TableDescriptorFlags::VALID) {
+            Some(Frame::containing_address(self.address()))
+        } else {
+            None
+        }
+    }
+
+    /// Get the associated frame, if available, for a level 1 page
+    pub fn pointed_frame_at_l1(&self) -> Option<Frame> {
+        if self.page_descriptor_entry_flags().contains(PageDescriptorFlags::VALID) {
+            Some(Frame::containing_address(self.address()))
+        } else {
+            None
+        }
+    }
+
+    pub fn page_table_entry_set(&mut self, frame: Frame, flags: TableDescriptorFlags) {
+        debug_assert!(frame.start_address().data() & !ADDRESS_MASK == 0);
+        // ODDNESS Alert: We need to set the AF bit - despite this being a TableDescriptor!!!
+        // The Arm ARM says this bit (bit 10) is IGNORED in Table Descriptors so hopefully this is OK
+        let access_flag = TableDescriptorFlags::AF;
+        self.0 = (frame.start_address().data() as u64) | flags.bits() | access_flag.bits() | (self.0 & COUNTER_MASK);
+    }
+
+    pub fn page_descriptor_entry_set(&mut self, frame: Frame, flags: PageDescriptorFlags) {
+        debug_assert!(frame.start_address().data() & !ADDRESS_MASK == 0);
+        let access_flag = PageDescriptorFlags::AF;
+        self.0 = (frame.start_address().data() as u64) | flags.bits() | access_flag.bits() | (self.0 & COUNTER_MASK);
+    }
+
+    pub fn set(&mut self, frame: Frame, flags: EntryFlags) {
+        debug_assert!(frame.start_address().data() & !ADDRESS_MASK == 0);
+        // ODDNESS Alert: We need to set the AF bit - despite this being a TableDescriptor!!!
+        // The Arm ARM says this bit (bit 10) is IGNORED in Table Descriptors so hopefully this is OK
+        let mut translated_flags = TableDescriptorFlags::AF | TableDescriptorFlags::TABLE;
+
+        if flags.contains(EntryFlags::PRESENT) {
+            translated_flags.insert(TableDescriptorFlags::VALID);
+        }
+
+        self.0 = (frame.start_address().data() as u64) | translated_flags.bits() | (self.0 & COUNTER_MASK);
+    }
+
+    /// Get bit 51 in entry, used as 1 of 9 bits (in 9 entries) used as a counter for the page table
+    pub fn counter_bits(&self) -> u64 {
+        (self.0 & COUNTER_MASK) >> 51
+    }
+
+    /// Set bit 51 in entry, used as 1 of 9 bits (in 9 entries) used as a counter for the page table
+    pub fn set_counter_bits(&mut self, count: u64) {
+        self.0 = (self.0 & !COUNTER_MASK) | ((count & 0x1) << 51);
+    }
+}
diff --git a/src/arch/aarch64/paging/mapper.rs b/src/arch/aarch64/paging/mapper.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4768452d63e10b9a051b3e84dc99359c355b4c23
--- /dev/null
+++ b/src/arch/aarch64/paging/mapper.rs
@@ -0,0 +1,346 @@
+use core::mem;
+use core::ptr::Unique;
+
+use crate::memory::{allocate_frames, deallocate_frames, Frame};
+
+use super::{ActivePageTable, Page, PAGE_SIZE, PhysicalAddress, VirtualAddress, VirtualAddressType};
+use super::entry::{EntryFlags, PageDescriptorFlags};
+use super::table::{self, Table, Level4};
+
+/// In order to enforce correct paging operations in the kernel, these types
+/// are returned on any mapping operation to get the code involved to specify
+/// how it intends to flush changes to a page table
+#[must_use = "The page table must be flushed, or the changes unsafely ignored"]
+pub struct MapperFlush(Page);
+
+impl MapperFlush {
+    /// Create a new page flush promise
+    pub fn new(page: Page) -> MapperFlush {
+        MapperFlush(page)
+    }
+
+    /// Flush this page in the active table
+    pub fn flush(self, table: &mut ActivePageTable) {
+        table.flush(self.0);
+        mem::forget(self);
+    }
+
+    /// Ignore the flush. This is unsafe, and a reason should be provided for use
+    pub unsafe fn ignore(self) {
+        mem::forget(self);
+    }
+}
+
+/// A flush cannot be dropped, it must be consumed
+impl Drop for MapperFlush {
+    fn drop(&mut self) {
+        panic!("Mapper flush was not utilized");
+    }
+}
+
+/// To allow for combining multiple flushes into one, we have a way of flushing
+/// the active table, which can consume `MapperFlush` structs
+#[must_use = "The page table must be flushed, or the changes unsafely ignored"]
+pub struct MapperFlushAll(bool);
+
+impl MapperFlushAll {
+    /// Create a new promise to flush all mappings
+    pub fn new() -> MapperFlushAll {
+        MapperFlushAll(false)
+    }
+
+    /// Consume a single page flush
+    pub fn consume(&mut self, flush: MapperFlush) {
+        self.0 = true;
+        mem::forget(flush);
+    }
+
+    /// Flush the active page table
+    pub fn flush(self, table: &mut ActivePageTable) {
+        if self.0 {
+            table.flush_all();
+        }
+        mem::forget(self);
+    }
+
+    /// Ignore the flush. This is unsafe, and a reason should be provided for use
+    pub unsafe fn ignore(self) {
+        mem::forget(self);
+    }
+}
+
+/// A flush cannot be dropped, it must be consumed
+impl Drop for MapperFlushAll {
+    fn drop(&mut self) {
+        panic!("Mapper flush all was not utilized");
+    }
+}
+
+pub struct Mapper {
+    p4: Unique<Table<Level4>>,
+    pub mapper_type: MapperType
+}
+
+pub enum MapperType {
+    User,
+    Kernel
+}
+
+impl Mapper {
+    /// Create a new page table
+    pub unsafe fn new(mapper_type: MapperType) -> Mapper {
+        match mapper_type {
+            MapperType::User => Mapper { p4: Unique::new_unchecked(table::U4), mapper_type },
+            MapperType::Kernel => Mapper { p4: Unique::new_unchecked(table::P4), mapper_type }
+        }
+    }
+
+    pub fn p4(&self) -> &Table<Level4> {
+        unsafe { self.p4.as_ref() }
+    }
+
+    pub fn p4_mut(&mut self) -> &mut Table<Level4> {
+        unsafe { self.p4.as_mut() }
+    }
+
+    /// Map a page to a frame
+    pub fn map_to(&mut self, page: Page, frame: Frame, flags: EntryFlags) -> MapperFlush {
+        let p3 = self.p4_mut().next_table_create(page.p4_index());
+        let p2 = p3.next_table_create(page.p3_index());
+        let p1 = p2.next_table_create(page.p2_index());
+        let mut translated_flags: PageDescriptorFlags = PageDescriptorFlags::VALID | PageDescriptorFlags::PAGE | PageDescriptorFlags::AF;
+
+        if flags.contains(EntryFlags::NO_EXECUTE) {
+            match page.start_address().get_type() {
+                VirtualAddressType::User => {
+                    translated_flags.insert(PageDescriptorFlags::UXN);
+                },
+                VirtualAddressType::Kernel => {
+                    translated_flags.insert(PageDescriptorFlags::PXN);
+                },
+            }
+        }
+
+        if flags.contains(EntryFlags::WRITABLE) {
+            if flags.contains(EntryFlags::USER_ACCESSIBLE) {
+                translated_flags.remove(PageDescriptorFlags::AP_2);
+                translated_flags.insert(PageDescriptorFlags::AP_1);
+            } else {
+                translated_flags.remove(PageDescriptorFlags::AP_2);
+                translated_flags.remove(PageDescriptorFlags::AP_1);
+            }
+        } else {
+            if flags.contains(EntryFlags::USER_ACCESSIBLE) {
+                translated_flags.insert(PageDescriptorFlags::AP_2);
+                translated_flags.insert(PageDescriptorFlags::AP_1);
+            } else {
+                translated_flags.insert(PageDescriptorFlags::AP_2);
+                translated_flags.remove(PageDescriptorFlags::AP_1);
+            }
+        }
+
+        assert!(p1[page.p1_index()].is_unused(),
+            "{:X}: Set to {:X}: {:?}, requesting {:X}: {:?}",
+            page.start_address().data(),
+            p1[page.p1_index()].address().data(), p1[page.p1_index()].page_descriptor_entry_flags(),
+            frame.start_address().data(), translated_flags);
+        p1.increment_entry_count();
+        p1[page.p1_index()].page_descriptor_entry_set(frame, translated_flags);
+        MapperFlush::new(page)
+    }
+
+    /// Map a page to the next free frame
+    pub fn map(&mut self, page: Page, flags: EntryFlags) -> MapperFlush {
+        let frame = allocate_frames(1).expect("out of frames");
+        self.map_to(page, frame, flags)
+    }
+
+    /// Update flags for a page
+    pub fn remap(&mut self, page: Page, flags: EntryFlags) -> MapperFlush {
+        let p3 = self.p4_mut().next_table_mut(page.p4_index()).expect("failed to remap: no p3");
+        let p2 = p3.next_table_mut(page.p3_index()).expect("failed to remap: no p2");
+        let p1 = p2.next_table_mut(page.p2_index()).expect("failed to remap: no p1");
+        let frame = p1[page.p1_index()].pointed_frame_at_l1().expect("failed to remap: not mapped");
+        let mut translated_flags: PageDescriptorFlags = PageDescriptorFlags::VALID | PageDescriptorFlags::PAGE | PageDescriptorFlags::AF;
+
+        if flags.contains(EntryFlags::NO_EXECUTE) {
+            match page.start_address().get_type() {
+                VirtualAddressType::User => {
+                    translated_flags.insert(PageDescriptorFlags::UXN);
+                },
+                VirtualAddressType::Kernel => {
+                    translated_flags.insert(PageDescriptorFlags::PXN);
+                },
+            }
+        }
+
+        if flags.contains(EntryFlags::WRITABLE) {
+            if flags.contains(EntryFlags::USER_ACCESSIBLE) {
+                translated_flags.remove(PageDescriptorFlags::AP_2);
+                translated_flags.insert(PageDescriptorFlags::AP_1);
+            } else {
+                translated_flags.remove(PageDescriptorFlags::AP_2);
+                translated_flags.remove(PageDescriptorFlags::AP_1);
+            }
+        } else {
+            if flags.contains(EntryFlags::USER_ACCESSIBLE) {
+                translated_flags.insert(PageDescriptorFlags::AP_2);
+                translated_flags.insert(PageDescriptorFlags::AP_1);
+            } else {
+                translated_flags.insert(PageDescriptorFlags::AP_2);
+                translated_flags.remove(PageDescriptorFlags::AP_1);
+            }
+        }
+
+        p1[page.p1_index()].page_descriptor_entry_set(frame, translated_flags);
+        MapperFlush::new(page)
+    }
+
+    /// Identity map a frame
+    pub fn identity_map(&mut self, frame: Frame, flags: EntryFlags) -> MapperFlush {
+        let page = Page::containing_address(VirtualAddress::new(frame.start_address().data()));
+        self.map_to(page, frame, flags)
+    }
+
+    fn unmap_inner(&mut self, page: &Page, keep_parents: bool) -> Frame {
+        let frame;
+
+        let p4 = self.p4_mut();
+        if let Some(p3) = p4.next_table_mut(page.p4_index()) {
+            if let Some(p2) = p3.next_table_mut(page.p3_index()) {
+                if let Some(p1) = p2.next_table_mut(page.p2_index()) {
+                    frame = if let Some(frame) = p1[page.p1_index()].pointed_frame_at_l1() {
+                        frame
+                    } else {
+                        panic!("unmap_inner({:X}): frame not found", page.start_address().data())
+                    };
+
+                    p1.decrement_entry_count();
+                    p1[page.p1_index()].set_unused();
+
+                    if keep_parents || ! p1.is_unused() {
+                        return frame;
+                    }
+                } else {
+                    panic!("unmap_inner({:X}): p1 not found", page.start_address().data());
+                }
+
+                if let Some(p1_frame) = p2[page.p2_index()].pointed_frame() {
+                    //println!("unmap_inner: Free p1 {:?}", p1_frame);
+                    p2.decrement_entry_count();
+                    p2[page.p2_index()].set_unused();
+                    deallocate_frames(p1_frame, 1);
+                } else {
+                    panic!("unmap_inner({:X}): p1_frame not found", page.start_address().data());
+                }
+
+                if ! p2.is_unused() {
+                    return frame;
+                }
+            } else {
+                panic!("unmap_inner({:X}): p2 not found", page.start_address().data());
+            }
+
+            if let Some(p2_frame) = p3[page.p3_index()].pointed_frame() {
+                //println!("unmap_inner: Free p2 {:?}", p2_frame);
+                p3.decrement_entry_count();
+                p3[page.p3_index()].set_unused();
+                deallocate_frames(p2_frame, 1);
+            } else {
+                panic!("unmap_inner({:X}): p2_frame not found", page.start_address().data());
+            }
+
+            if ! p3.is_unused() {
+                return frame;
+            }
+        } else {
+            panic!("unmap_inner({:X}): p3 not found", page.start_address().data());
+        }
+
+        if let Some(p3_frame) = p4[page.p4_index()].pointed_frame() {
+            //println!("unmap_inner: Free p3 {:?}", p3_frame);
+            p4.decrement_entry_count();
+            p4[page.p4_index()].set_unused();
+            deallocate_frames(p3_frame, 1);
+        } else {
+            panic!("unmap_inner({:X}): p3_frame not found", page.start_address().data());
+        }
+
+        frame
+    }
+
+    /// Unmap a page
+    pub fn unmap(&mut self, page: Page) -> MapperFlush {
+        let frame = self.unmap_inner(&page, false);
+        deallocate_frames(frame, 1);
+        MapperFlush::new(page)
+    }
+
+    /// Unmap a page, return frame without free
+    pub fn unmap_return(&mut self, page: Page, keep_parents: bool) -> (MapperFlush, Frame) {
+        let frame = self.unmap_inner(&page, keep_parents);
+        (MapperFlush::new(page), frame)
+    }
+
+    pub fn translate_page(&self, page: Page) -> Option<Frame> {
+        self.p4().next_table(page.p4_index())
+            .and_then(|p3| p3.next_table(page.p3_index()))
+            .and_then(|p2| p2.next_table(page.p2_index()))
+            .and_then(|p1| p1[page.p1_index()].pointed_frame())
+    }
+
+    pub fn translate_page_flags(&self, page: Page) -> Option<EntryFlags> {
+        let mut translated_flags: EntryFlags = Default::default();
+
+        if let Some(flags) = self.p4().next_table(page.p4_index())
+            .and_then(|p3| p3.next_table(page.p3_index()))
+            .and_then(|p2| p2.next_table(page.p2_index()))
+            .and_then(|p1| Some(p1[page.p1_index()].page_descriptor_entry_flags())) {
+
+                if flags.contains(PageDescriptorFlags::VALID) {
+                    translated_flags.insert(EntryFlags::PRESENT);
+                }
+
+                if flags.contains(PageDescriptorFlags::AF) {
+                    translated_flags.insert(EntryFlags::AF);
+                }
+                translated_flags.insert(EntryFlags::AF);
+
+                if flags.contains(PageDescriptorFlags::UXN) || flags.contains(PageDescriptorFlags::PXN) {
+                    translated_flags.insert(EntryFlags::NO_EXECUTE);
+                }
+
+                if !flags.contains(PageDescriptorFlags::AP_2) && !flags.contains(PageDescriptorFlags::AP_1) {
+                    translated_flags.insert(EntryFlags::WRITABLE);
+                    translated_flags.remove(EntryFlags::USER_ACCESSIBLE);
+                }
+
+                if !flags.contains(PageDescriptorFlags::AP_2) && flags.contains(PageDescriptorFlags::AP_1) {
+                    translated_flags.insert(EntryFlags::WRITABLE);
+                    translated_flags.insert(EntryFlags::USER_ACCESSIBLE);
+                }
+
+                if flags.contains(PageDescriptorFlags::AP_2) && !flags.contains(PageDescriptorFlags::AP_1) {
+                    translated_flags.remove(EntryFlags::WRITABLE);
+                    translated_flags.remove(EntryFlags::USER_ACCESSIBLE);
+                }
+
+                if flags.contains(PageDescriptorFlags::AP_2) && flags.contains(PageDescriptorFlags::AP_1) {
+                    translated_flags.remove(EntryFlags::WRITABLE);
+                    translated_flags.insert(EntryFlags::USER_ACCESSIBLE);
+                }
+
+                Some(translated_flags)
+            }
+        else {
+            None
+        }
+    }
+
+    /// Translate a virtual address to a physical one
+    pub fn translate(&self, virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
+        let offset = virtual_address.data() % PAGE_SIZE;
+        self.translate_page(Page::containing_address(virtual_address))
+            .map(|frame| PhysicalAddress::new(frame.start_address().data() + offset))
+    }
+}
diff --git a/src/arch/aarch64/paging/mod.rs b/src/arch/aarch64/paging/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e0e53847153e3050abc0d28b98533849d8ea1e6d
--- /dev/null
+++ b/src/arch/aarch64/paging/mod.rs
@@ -0,0 +1,470 @@
+//! # Paging
+//! Some code was borrowed from [Phil Opp's Blog](http://os.phil-opp.com/modifying-page-tables.html)
+
+use core::{mem, ptr};
+use core::ops::{Deref, DerefMut};
+use spin::Mutex;
+
+use crate::device::cpu::registers::{control_regs, tlb};
+use crate::memory::{allocate_frames, Frame};
+
+use self::entry::{EntryFlags, TableDescriptorFlags};
+use self::mapper::{Mapper, MapperFlushAll, MapperType};
+use self::temporary_page::TemporaryPage;
+
+pub use rmm::PhysicalAddress;
+
+pub mod entry;
+pub mod mapper;
+pub mod table;
+pub mod temporary_page;
+
+/// Number of entries per page table
+pub const ENTRY_COUNT: usize = 512;
+
+/// Size of pages
+pub const PAGE_SIZE: usize = 4096;
+
+//TODO: This is a rudimentary recursive mutex used to naively fix multi_core issues, replace it!
+pub struct PageTableLock {
+    cpu_id: usize,
+    count: usize,
+}
+
+pub static PAGE_TABLE_LOCK: Mutex<PageTableLock> = Mutex::new(PageTableLock {
+    cpu_id: 0,
+    count: 0,
+});
+
+fn page_table_lock() {
+    let cpu_id = crate::cpu_id();
+    loop {
+        {
+            let mut lock = PAGE_TABLE_LOCK.lock();
+            if lock.count == 0 || lock.cpu_id == cpu_id {
+                lock.cpu_id = cpu_id;
+                lock.count += 1;
+                return;
+            }
+        }
+        crate::arch::interrupt::pause();
+    }
+}
+
+fn page_table_unlock() {
+    let mut lock = PAGE_TABLE_LOCK.lock();
+    lock.count -= 1;
+}
+
+/// Setup Memory Access Indirection Register
+unsafe fn init_mair() {
+    let mut val: control_regs::MairEl1 = control_regs::mair_el1();
+
+    val.insert(control_regs::MairEl1::DEVICE_MEMORY);
+    val.insert(control_regs::MairEl1::NORMAL_UNCACHED_MEMORY);
+    val.insert(control_regs::MairEl1::NORMAL_WRITEBACK_MEMORY);
+
+    control_regs::mair_el1_write(val);
+}
+
+/// Map TSS
+unsafe fn map_tss(cpu_id: usize, mapper: &mut Mapper) -> MapperFlushAll {
+    extern "C" {
+        /// The starting byte of the thread data segment
+        static mut __tdata_start: u8;
+        /// The ending byte of the thread data segment
+        static mut __tdata_end: u8;
+        /// The starting byte of the thread BSS segment
+        static mut __tbss_start: u8;
+        /// The ending byte of the thread BSS segment
+        static mut __tbss_end: u8;
+    }
+
+    let size = &__tbss_end as *const _ as usize - &__tdata_start as *const _ as usize;
+    let start = crate::KERNEL_PERCPU_OFFSET + crate::KERNEL_PERCPU_SIZE * cpu_id;
+    let end = start + size;
+
+    let mut flush_all = MapperFlushAll::new();
+    let start_page = Page::containing_address(VirtualAddress::new(start));
+    let end_page = Page::containing_address(VirtualAddress::new(end - 1));
+    for page in Page::range_inclusive(start_page, end_page) {
+        let result = mapper.map(
+            page,
+            EntryFlags::PRESENT
+                | EntryFlags::GLOBAL
+                | EntryFlags::NO_EXECUTE
+                | EntryFlags::WRITABLE,
+        );
+        flush_all.consume(result);
+    }
+    flush_all
+}
+
+/// Copy tdata, clear tbss, set TCB self pointer
+unsafe fn init_tcb(cpu_id: usize) -> usize {
+    extern "C" {
+        /// The starting byte of the thread data segment
+        static mut __tdata_start: u8;
+        /// The ending byte of the thread data segment
+        static mut __tdata_end: u8;
+        /// The starting byte of the thread BSS segment
+        static mut __tbss_start: u8;
+        /// The ending byte of the thread BSS segment
+        static mut __tbss_end: u8;
+    }
+
+    let tcb_offset;
+    {
+        let size = &__tbss_end as *const _ as usize - &__tdata_start as *const _ as usize;
+        let tbss_offset = &__tbss_start as *const _ as usize - &__tdata_start as *const _ as usize;
+
+        let start = crate::KERNEL_PERCPU_OFFSET + crate::KERNEL_PERCPU_SIZE * cpu_id;
+        println!("SET TPIDR_EL1 TO {:X}", start - 0x10);
+        // FIXME: Empirically initializing tpidr to 16 bytes below start works. I do not know
+        // whether this is the correct way to handle TLS. Will need to revisit.
+        control_regs::tpidr_el1_write((start - 0x10) as u64);
+        println!("SET TPIDR_EL1 DONE");
+
+        let end = start + size;
+        tcb_offset = end - mem::size_of::<usize>();
+
+        ptr::copy(&__tdata_start as *const u8, start as *mut u8, tbss_offset);
+        ptr::write_bytes((start + tbss_offset) as *mut u8, 0, size - tbss_offset);
+
+        *(tcb_offset as *mut usize) = end;
+    }
+    tcb_offset
+}
+
+/// Initialize paging
+///
+/// Returns page table and thread control block offset
+pub unsafe fn init(
+    cpu_id: usize,
+) -> (ActivePageTable, usize) {
+    extern "C" {
+        /// The starting byte of the text (code) data segment.
+        static mut __text_start: u8;
+        /// The ending byte of the text (code) data segment.
+        static mut __text_end: u8;
+        /// The starting byte of the _.rodata_ (read-only data) segment.
+        static mut __rodata_start: u8;
+        /// The ending byte of the _.rodata_ (read-only data) segment.
+        static mut __rodata_end: u8;
+        /// The starting byte of the _.data_ segment.
+        static mut __data_start: u8;
+        /// The ending byte of the _.data_ segment.
+        static mut __data_end: u8;
+        /// The starting byte of the thread data segment
+        static mut __tdata_start: u8;
+        /// The ending byte of the thread data segment
+        static mut __tdata_end: u8;
+        /// The starting byte of the thread BSS segment
+        static mut __tbss_start: u8;
+        /// The ending byte of the thread BSS segment
+        static mut __tbss_end: u8;
+        /// The starting byte of the _.bss_ (uninitialized data) segment.
+        static mut __bss_start: u8;
+        /// The ending byte of the _.bss_ (uninitialized data) segment.
+        static mut __bss_end: u8;
+    }
+
+    init_mair();
+
+    let mut active_table = ActivePageTable::new_unlocked(PageTableType::Kernel);
+
+    let flush_all = map_tss(cpu_id, &mut active_table);
+    flush_all.flush(&mut active_table);
+
+    return (active_table, init_tcb(cpu_id));
+}
+
+pub unsafe fn init_ap(
+    cpu_id: usize,
+    bsp_table: usize,
+) -> usize {
+    init_mair();
+
+    let mut active_table = ActivePageTable::new_unlocked(PageTableType::Kernel);
+
+    let mut new_table = InactivePageTable::from_address(bsp_table);
+
+    let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(
+        crate::KERNEL_TMP_MISC_OFFSET,
+    )));
+
+    active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+        let flush_all = map_tss(cpu_id, mapper);
+        // The flush can be ignored as this is not the active table. See later active_table.switch
+        flush_all.ignore();
+    });
+
+    // This switches the active table, which is setup by the bootloader, to a correct table
+    // setup by the lambda above. This will also flush the TLB
+    active_table.switch(new_table);
+
+    init_tcb(cpu_id)
+}
+
+pub struct ActivePageTable {
+    mapper: Mapper,
+    locked: bool,
+}
+
+pub enum PageTableType {
+    User,
+    Kernel
+}
+
+impl Deref for ActivePageTable {
+    type Target = Mapper;
+
+    fn deref(&self) -> &Mapper {
+        &self.mapper
+    }
+}
+
+impl DerefMut for ActivePageTable {
+    fn deref_mut(&mut self) -> &mut Mapper {
+        &mut self.mapper
+    }
+}
+
+impl ActivePageTable {
+    //TODO: table_type argument
+    pub unsafe fn new(table_type: PageTableType) -> ActivePageTable {
+        page_table_lock();
+        ActivePageTable {
+            mapper: Mapper::new(match table_type {
+                PageTableType::User => MapperType::User,
+                PageTableType::Kernel => MapperType::Kernel,
+            }),
+            locked: true,
+        }
+    }
+
+    //TODO: table_type argument
+    pub unsafe fn new_unlocked(table_type: PageTableType) -> ActivePageTable {
+        ActivePageTable {
+            mapper: Mapper::new(match table_type {
+                PageTableType::User => MapperType::User,
+                PageTableType::Kernel => MapperType::Kernel,
+            }),
+            locked: false,
+        }
+    }
+
+    pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable {
+        let old_table: InactivePageTable;
+
+        match self.mapper.mapper_type {
+            MapperType::User => {
+                old_table = InactivePageTable { p4_frame: Frame::containing_address(PhysicalAddress::new(unsafe { control_regs::ttbr0_el1() } as usize)) };
+                unsafe { control_regs::ttbr0_el1_write(new_table.p4_frame.start_address().data() as u64) };
+            },
+            MapperType::Kernel =>  {
+                old_table = InactivePageTable { p4_frame: Frame::containing_address(PhysicalAddress::new(unsafe { control_regs::ttbr1_el1() } as usize)) };
+                unsafe { control_regs::ttbr1_el1_write(new_table.p4_frame.start_address().data() as u64) };
+            }
+        }
+
+        unsafe { tlb::flush_all() };
+        old_table
+    }
+
+    pub fn flush(&mut self, page: Page) {
+        unsafe {
+            tlb::flush(page.start_address().data());
+        }
+    }
+
+    pub fn flush_all(&mut self) {
+        unsafe {
+            tlb::flush_all();
+        }
+    }
+
+    pub fn with<F>(&mut self, table: &mut InactivePageTable, temporary_page: &mut TemporaryPage, f: F)
+        where F: FnOnce(&mut Mapper)
+    {
+        {
+            let backup: Frame;
+
+            match self.mapper.mapper_type {
+                MapperType::User => backup = Frame::containing_address(PhysicalAddress::new(unsafe { control_regs::ttbr0_el1() as usize })),
+                MapperType::Kernel => backup = Frame::containing_address(PhysicalAddress::new(unsafe { control_regs::ttbr1_el1() as usize }))
+            }
+
+            // map temporary_kpage to current p4 table
+            let p4_table = temporary_page.map_table_frame(backup.clone(), EntryFlags::PRESENT | EntryFlags::WRITABLE | EntryFlags::NO_EXECUTE, self);
+
+            // overwrite recursive mapping
+            self.p4_mut()[crate::RECURSIVE_PAGE_PML4].page_table_entry_set(
+                table.p4_frame.clone(),
+                TableDescriptorFlags::VALID | TableDescriptorFlags::TABLE,
+            );
+            self.flush_all();
+
+            // execute f in the new context
+            f(self);
+
+            // restore recursive mapping to original p4 table
+            p4_table[crate::RECURSIVE_PAGE_PML4].page_table_entry_set(
+                backup,
+                TableDescriptorFlags::VALID | TableDescriptorFlags::TABLE,
+            );
+            self.flush_all();
+        }
+
+        temporary_page.unmap(self);
+    }
+
+    pub unsafe fn address(&self) -> usize {
+        match self.mapper.mapper_type {
+            MapperType::User => control_regs::ttbr0_el1() as usize,
+            MapperType::Kernel => control_regs::ttbr1_el1() as usize,
+        }
+    }
+}
+
+impl Drop for ActivePageTable {
+    fn drop(&mut self) {
+        if self.locked {
+            page_table_unlock();
+            self.locked = false;
+        }
+    }
+}
+
+pub struct InactivePageTable {
+    p4_frame: Frame,
+}
+
+impl InactivePageTable {
+    pub fn new(
+        frame: Frame,
+        active_table: &mut ActivePageTable,
+        temporary_page: &mut TemporaryPage,
+    ) -> InactivePageTable {
+        {
+            let table = temporary_page.map_table_frame(
+                frame.clone(),
+                EntryFlags::PRESENT | EntryFlags::WRITABLE | EntryFlags::NO_EXECUTE,
+                active_table,
+            );
+            // now we are able to zero the table
+            table.zero();
+            // set up recursive mapping for the table
+            table[crate::RECURSIVE_PAGE_PML4].page_table_entry_set(
+                frame.clone(),
+                TableDescriptorFlags::VALID | TableDescriptorFlags::TABLE
+            );
+        }
+        temporary_page.unmap(active_table);
+
+        InactivePageTable { p4_frame: frame }
+    }
+
+    pub unsafe fn from_address(address: usize) -> InactivePageTable {
+        InactivePageTable {
+            p4_frame: Frame::containing_address(PhysicalAddress::new(address)),
+        }
+    }
+
+    pub unsafe fn address(&self) -> usize {
+        self.p4_frame.start_address().data()
+    }
+}
+
+/// A virtual address.
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub struct VirtualAddress(usize);
+
+#[derive(Debug, PartialEq)]
+pub enum VirtualAddressType {
+    User,
+    Kernel
+}
+
+impl VirtualAddress {
+    pub fn new(address: usize) -> Self {
+        VirtualAddress(address)
+    }
+
+    pub fn data(&self) -> usize {
+        self.0
+    }
+
+    pub fn get_type(&self) -> VirtualAddressType {
+        if ((self.0 >> 48) & 0xffff) == 0xffff {
+            VirtualAddressType::Kernel
+        } else {
+            VirtualAddressType::User
+        }
+    }
+}
+
+/// Page
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Page {
+    number: usize,
+}
+
+impl Page {
+    pub fn start_address(self) -> VirtualAddress {
+        VirtualAddress::new(self.number * PAGE_SIZE)
+    }
+
+    pub fn p4_index(self) -> usize {
+        (self.number >> 27) & 0o777
+    }
+
+    pub fn p3_index(self) -> usize {
+        (self.number >> 18) & 0o777
+    }
+
+    pub fn p2_index(self) -> usize {
+        (self.number >> 9) & 0o777
+    }
+
+    pub fn p1_index(self) -> usize {
+        self.number & 0o777
+    }
+
+    pub fn containing_address(address: VirtualAddress) -> Page {
+        //TODO assert!(address.data() < 0x0000_8000_0000_0000 || address.data() >= 0xffff_8000_0000_0000,
+        //    "invalid address: 0x{:x}", address.data());
+        Page {
+            number: address.data() / PAGE_SIZE,
+        }
+    }
+
+    pub fn range_inclusive(start: Page, end: Page) -> PageIter {
+        PageIter { start, end }
+    }
+
+    pub fn next(self) -> Page {
+        Self {
+            number: self.number + 1,
+        }
+    }
+}
+
+pub struct PageIter {
+    start: Page,
+    end: Page,
+}
+
+impl Iterator for PageIter {
+    type Item = Page;
+
+    fn next(&mut self) -> Option<Page> {
+        if self.start <= self.end {
+            let page = self.start;
+            self.start = self.start.next();
+            Some(page)
+        } else {
+            None
+        }
+    }
+}
diff --git a/src/arch/aarch64/paging/table.rs b/src/arch/aarch64/paging/table.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ccedf7e4ad1d6d3c3a82baafd330b1007005e9c6
--- /dev/null
+++ b/src/arch/aarch64/paging/table.rs
@@ -0,0 +1,160 @@
+//! # Page table
+//! Code borrowed from [Phil Opp's Blog](http://os.phil-opp.com/modifying-page-tables.html)
+
+use core::marker::PhantomData;
+use core::ops::{Index, IndexMut};
+
+use crate::memory::allocate_frames;
+
+use super::entry::{TableDescriptorFlags, Entry};
+use super::ENTRY_COUNT;
+
+pub const P4: *mut Table<Level4> = 0xffff_ffff_ffff_f000 as *mut _;
+pub const U4: *mut Table<Level4> = 0x0000_ffff_ffff_f000 as *mut _;
+
+const KSPACE_ADDR_MASK: usize = 0xffff_0000_0000_0000;
+const USPACE_ADDR_MASK: usize = 0x0000_ffff_ffff_ffff;
+
+pub trait TableLevel {}
+
+pub enum Level4 {}
+pub enum Level3 {}
+pub enum Level2 {}
+pub enum Level1 {}
+
+impl TableLevel for Level4 {}
+impl TableLevel for Level3 {}
+impl TableLevel for Level2 {}
+impl TableLevel for Level1 {}
+
+pub trait HierarchicalLevel: TableLevel {
+    type NextLevel: TableLevel;
+}
+
+impl HierarchicalLevel for Level4 {
+    type NextLevel = Level3;
+}
+
+impl HierarchicalLevel for Level3 {
+    type NextLevel = Level2;
+}
+
+impl HierarchicalLevel for Level2 {
+    type NextLevel = Level1;
+}
+
+pub struct Table<L: TableLevel> {
+    entries: [Entry; ENTRY_COUNT],
+    level: PhantomData<L>,
+}
+
+impl<L> Table<L> where L: TableLevel {
+    pub fn is_unused(&self) -> bool {
+        if self.entry_count() > 0 {
+            return false;
+        }
+
+        true
+    }
+
+    pub fn zero(&mut self) {
+        for entry in self.entries.iter_mut() {
+            entry.set_zero();
+        }
+    }
+
+    /// Set number of entries in first table entry
+    /// FIXMES:
+    /// Only 1 bit per table entry seems to work. So we need 9 entries (!).
+    /// This is one reason why we need to have a non-recursive paging scheme.
+    /// These updates require memory barriers and TLB invalidations.
+    fn set_entry_count(&mut self, count: u64) {
+        debug_assert!(count <= ENTRY_COUNT as u64, "count can't be greater than ENTRY_COUNT");
+        self.entries[0].set_counter_bits((count >> 0) & 0x1);
+        self.entries[1].set_counter_bits((count >> 1) & 0x1);
+        self.entries[2].set_counter_bits((count >> 2) & 0x1);
+        self.entries[3].set_counter_bits((count >> 3) & 0x1);
+        self.entries[4].set_counter_bits((count >> 4) & 0x1);
+        self.entries[5].set_counter_bits((count >> 5) & 0x1);
+        self.entries[6].set_counter_bits((count >> 6) & 0x1);
+        self.entries[7].set_counter_bits((count >> 7) & 0x1);
+        self.entries[8].set_counter_bits((count >> 8) & 0x1);
+    }
+
+    /// Get number of entries from first table entry
+    fn entry_count(&self) -> u64 {
+        let mut count: u64 = (self.entries[0].counter_bits() & 0x1) << 0;
+        count |= (self.entries[1].counter_bits() & 0x1) << 1;
+        count |= (self.entries[2].counter_bits() & 0x1) << 2;
+        count |= (self.entries[3].counter_bits() & 0x1) << 3;
+        count |= (self.entries[4].counter_bits() & 0x1) << 4;
+        count |= (self.entries[5].counter_bits() & 0x1) << 5;
+        count |= (self.entries[6].counter_bits() & 0x1) << 6;
+        count |= (self.entries[7].counter_bits() & 0x1) << 7;
+        count |= (self.entries[8].counter_bits() & 0x1) << 8;
+        count
+    }
+
+    pub fn increment_entry_count(&mut self) {
+        let current_count = self.entry_count();
+        self.set_entry_count(current_count + 1);
+    }
+
+    pub fn decrement_entry_count(&mut self) {
+        let current_count = self.entry_count();
+        self.set_entry_count(current_count - 1);
+    }
+}
+
+impl<L> Table<L> where L: HierarchicalLevel {
+    pub fn next_table(&self, index: usize) -> Option<&Table<L::NextLevel>> {
+        self.next_table_address(index).map(|address| unsafe { &*(address as *const _) })
+    }
+
+    pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<L::NextLevel>> {
+        self.next_table_address(index).map(|address| unsafe { &mut *(address as *mut _) })
+    }
+
+    pub fn next_table_create(&mut self, index: usize) -> &mut Table<L::NextLevel> {
+        if self.next_table(index).is_none() {
+            let frame = allocate_frames(1).expect("no frames available");
+            self.increment_entry_count();
+
+            /* Allow users to go down the page table, implement permissions at the page level */
+            let mut perms = TableDescriptorFlags::VALID;
+            perms |= TableDescriptorFlags::TABLE;
+
+            self[index].page_table_entry_set(frame, perms);
+            self.next_table_mut(index).unwrap().zero();
+        }
+        self.next_table_mut(index).unwrap()
+    }
+
+    fn next_table_address(&self, index: usize) -> Option<usize> {
+        let entry_flags = self[index].page_table_entry_flags();
+        if entry_flags.contains(TableDescriptorFlags::VALID) {
+            let table_address = self as *const _ as usize;
+            if (table_address & KSPACE_ADDR_MASK) != 0 {
+                Some((table_address << 9) | (index << 12))
+            } else {
+                Some(((table_address << 9) | (index << 12)) & USPACE_ADDR_MASK)
+            }
+        } else {
+            None
+        }
+    }
+}
+
+impl<L> Index<usize> for Table<L> where L: TableLevel {
+    type Output = Entry;
+
+    fn index(&self, index: usize) -> &Entry {
+        &self.entries[index]
+    }
+}
+
+impl<L> IndexMut<usize> for Table<L> where L: TableLevel {
+    fn index_mut(&mut self, index: usize) -> &mut Entry {
+        &mut self.entries[index]
+    }
+}
diff --git a/src/arch/aarch64/paging/temporary_page.rs b/src/arch/aarch64/paging/temporary_page.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8ccf4414d9e60955b7e77d7816434ed6b2493b3a
--- /dev/null
+++ b/src/arch/aarch64/paging/temporary_page.rs
@@ -0,0 +1,45 @@
+//! Temporarily map a page
+//! From [Phil Opp's Blog](http://os.phil-opp.com/remap-the-kernel.html)
+
+use crate::memory::Frame;
+
+use super::{ActivePageTable, Page, VirtualAddress};
+use super::entry::EntryFlags;
+use super::table::{Table, Level1};
+
+pub struct TemporaryPage {
+    page: Page,
+}
+
+impl TemporaryPage {
+    pub fn new(page: Page) -> TemporaryPage {
+        TemporaryPage {
+            page: page,
+        }
+    }
+
+    pub fn start_address (&self) -> VirtualAddress {
+        self.page.start_address()
+    }
+
+    /// Maps the temporary page to the given frame in the active table.
+    /// Returns the start address of the temporary page.
+    pub fn map(&mut self, frame: Frame, flags: EntryFlags, active_table: &mut ActivePageTable) -> VirtualAddress {
+        assert!(active_table.translate_page(self.page).is_none(), "temporary page is already mapped");
+        let result = active_table.map_to(self.page, frame, flags);
+        result.flush(active_table);
+        self.page.start_address()
+    }
+
+    /// Maps the temporary page to the given page table frame in the active
+    /// table. Returns a reference to the now mapped table.
+    pub fn map_table_frame(&mut self, frame: Frame, flags: EntryFlags, active_table: &mut ActivePageTable) -> &mut Table<Level1> {
+        unsafe { &mut *(self.map(frame, flags, active_table).data() as *mut Table<Level1>) }
+    }
+
+    /// Unmaps the temporary page in the active table.
+    pub fn unmap(&mut self, active_table: &mut ActivePageTable) {
+        let (result, _frame) = active_table.unmap_return(self.page, true);
+        result.flush(active_table);
+    }
+}
diff --git a/src/arch/aarch64/rmm.rs b/src/arch/aarch64/rmm.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3f7c179c437ddb6e3cffedff958b0de02fbf61f1
--- /dev/null
+++ b/src/arch/aarch64/rmm.rs
@@ -0,0 +1,292 @@
+use rmm::{
+    KILOBYTE,
+    MEGABYTE,
+    AArch64Arch,
+    Arch,
+    BuddyAllocator,
+    BumpAllocator,
+    FrameAllocator,
+    FrameCount,
+    FrameUsage,
+    MemoryArea,
+    PageFlags,
+    PageMapper,
+    PageTable,
+    PhysicalAddress,
+    VirtualAddress,
+};
+
+use spin::Mutex;
+
+extern "C" {
+    /// The starting byte of the text (code) data segment.
+    static mut __text_start: u8;
+    /// The ending byte of the text (code) data segment.
+    static mut __text_end: u8;
+    /// The starting byte of the _.rodata_ (read-only data) segment.
+    static mut __rodata_start: u8;
+    /// The ending byte of the _.rodata_ (read-only data) segment.
+    static mut __rodata_end: u8;
+}
+
+unsafe fn page_flags<A: Arch>(virt: VirtualAddress) -> PageFlags<A> {
+    let virt_addr = virt.data();
+
+    // Test for being inside a region
+    macro_rules! in_section {
+        ($n: ident) => {
+            virt_addr >= &concat_idents!(__, $n, _start) as *const u8 as usize
+                && virt_addr < &concat_idents!(__, $n, _end) as *const u8 as usize
+        };
+    }
+
+    if in_section!(text) {
+        // Remap text read-only, execute
+        PageFlags::new().write(false).execute(true)
+    } else if in_section!(rodata) {
+        // Remap rodata read-only, no execute
+        PageFlags::new().write(false).execute(false)
+    } else {
+        // Remap everything else read-write, no execute
+        PageFlags::new().write(true).execute(false)
+    }
+}
+
+unsafe fn dump_tables<A: Arch>(table: PageTable<A>) {
+    let level = table.level();
+    for i in 0..A::PAGE_ENTRIES {
+        if let Some(entry) = table.entry(i) {
+            if entry.present() {
+                let base = table.entry_base(i).unwrap();
+                let address = entry.address();
+                let flags = entry.flags();
+                for level in level..A::PAGE_LEVELS {
+                    print!(" ");
+                }
+                println!(
+                    "{}: map 0x{:X} to 0x{:X} flags 0x{:X}",
+                    i,
+                    base.data(),
+                    address.data(),
+                    flags
+                );
+                // This somewhat handles block entries
+                if flags & (1 << 1) != 0 {
+                    if let Some(next) = table.next(i) {
+                        for level in level..A::PAGE_LEVELS {
+                            print!(" ");
+                        }
+                        println!("{{");
+
+                        dump_tables(next);
+
+                        for level in level..A::PAGE_LEVELS {
+                            print!(" ");
+                        }
+                        println!("}}");
+                    }
+                }
+            }
+        }
+    }
+}
+
+unsafe fn inner<A: Arch>(areas: &'static [MemoryArea], kernel_base: usize, kernel_size_aligned: usize, bump_offset: usize) -> BuddyAllocator<A> {
+    // First, calculate how much memory we have
+    let mut size = 0;
+    for area in areas.iter() {
+        if area.size > 0 {
+            println!("{:X?}", area);
+            size += area.size;
+        }
+    }
+
+    println!("Memory: {} MB", (size + (MEGABYTE - 1)) / MEGABYTE);
+
+    // Create a basic allocator for the first pages
+    let mut bump_allocator = BumpAllocator::<A>::new(areas, bump_offset);
+
+    {
+        let mut mapper = PageMapper::<A, _>::current(
+            &mut bump_allocator
+        );
+
+        println!("Old Table: {:X}", mapper.table().phys().data());
+        //dump_tables(mapper.table());
+    }
+
+    {
+        let mut mapper = PageMapper::<A, _>::create(
+            &mut bump_allocator
+        ).expect("failed to create Mapper");
+
+        // Map all physical areas at PHYS_OFFSET
+        for area in areas.iter() {
+            for i in 0..area.size / A::PAGE_SIZE {
+                let phys = area.base.add(i * A::PAGE_SIZE);
+                let virt = A::phys_to_virt(phys);
+                let flags = page_flags::<A>(virt);
+                let flush = mapper.map_phys(
+                    virt,
+                    phys,
+                    flags
+                ).expect("failed to map frame");
+                flush.ignore(); // Not the active table
+            }
+        }
+
+        //TODO: this is a hack to add the aarch64 kernel mapping
+        for i in 0..kernel_size_aligned / A::PAGE_SIZE {
+            let phys = PhysicalAddress::new(kernel_base + i * A::PAGE_SIZE);
+            let virt = VirtualAddress::new(crate::KERNEL_OFFSET + i * A::PAGE_SIZE);
+            let flags = page_flags::<A>(virt);
+            let flush = mapper.map_phys(
+                virt,
+                phys,
+                flags
+            ).expect("failed to map frame");
+            flush.ignore(); // Not the active table
+        }
+
+        //TODO: this is another hack to map our UART
+        {
+            let phys = PhysicalAddress::new(0x9000000);
+            let virt = A::phys_to_virt(phys);
+            let flags = page_flags::<A>(virt);
+            let flush = mapper.map_phys(
+                virt,
+                phys,
+                flags
+            ).expect("failed to map frame");
+            flush.ignore(); // Not the active table
+        }
+
+        //TODO: remove backwards compatible recursive mapping
+        mapper.table().set_entry(511, rmm::PageEntry::new(
+            mapper.table().phys().data() | A::ENTRY_FLAG_READWRITE | A::ENTRY_FLAG_DEFAULT_TABLE
+        ));
+
+        println!("New Table: {:X}", mapper.table().phys().data());
+        //dump_tables(mapper.table());
+
+        // Use the new table
+        mapper.make_current();
+    }
+
+    // Create the physical memory map
+    let offset = bump_allocator.offset();
+    println!("Permanently used: {} KB", (offset + (KILOBYTE - 1)) / KILOBYTE);
+
+    BuddyAllocator::<A>::new(bump_allocator).expect("failed to create BuddyAllocator")
+}
+
+pub struct LockedAllocator {
+    inner: Mutex<Option<BuddyAllocator<AArch64Arch>>>,
+}
+
+impl LockedAllocator {
+    const fn new() -> Self {
+        Self {
+            inner: Mutex::new(None)
+        }
+    }
+}
+
+impl FrameAllocator for LockedAllocator {
+    unsafe fn allocate(&mut self, count: FrameCount) -> Option<PhysicalAddress> {
+        if let Some(ref mut allocator) = *self.inner.lock() {
+            allocator.allocate(count)
+        } else {
+            None
+        }
+    }
+
+    unsafe fn free(&mut self, address: PhysicalAddress, count: FrameCount) {
+        if let Some(ref mut allocator) = *self.inner.lock() {
+            allocator.free(address, count)
+        }
+    }
+
+    unsafe fn usage(&self) -> FrameUsage {
+        if let Some(ref allocator) = *self.inner.lock() {
+            allocator.usage()
+        } else {
+            FrameUsage::new(FrameCount::new(0), FrameCount::new(0))
+        }
+    }
+}
+
+static mut AREAS: [MemoryArea; 512] = [MemoryArea {
+    base: PhysicalAddress::new(0),
+    size: 0,
+}; 512];
+
+pub static mut FRAME_ALLOCATOR: LockedAllocator = LockedAllocator::new();
+
+pub unsafe fn mapper_new(table_addr: PhysicalAddress) -> PageMapper<'static, AArch64Arch, LockedAllocator> {
+    PageMapper::new(table_addr, &mut FRAME_ALLOCATOR)
+}
+
+//TODO: global paging lock?
+pub unsafe fn mapper_create() -> Option<PageMapper<'static, AArch64Arch, LockedAllocator>> {
+    PageMapper::create(&mut FRAME_ALLOCATOR)
+}
+
+pub unsafe fn mapper_current() -> PageMapper<'static, AArch64Arch, LockedAllocator> {
+    PageMapper::current(&mut FRAME_ALLOCATOR)
+}
+
+pub unsafe fn init(kernel_base: usize, kernel_size: usize) {
+    type A = AArch64Arch;
+
+    let kernel_size_aligned = ((kernel_size + (A::PAGE_SIZE - 1))/A::PAGE_SIZE) * A::PAGE_SIZE;
+    let kernel_end = kernel_base + kernel_size_aligned;
+    println!("kernel_end: {:X}", kernel_end);
+
+    // Copy memory map from bootloader location, and page align it
+    let mut area_i = 0;
+    let mut bump_offset = 0;
+    for i in 0..512 {
+        let old = &crate::init::device_tree::MEMORY_MAP[i];
+        if old._type != 1 {
+            // Not a free area
+            continue;
+        }
+
+        let mut base = old.base_addr as usize;
+        let mut size = old.length as usize;
+
+        // Page align base
+        let base_offset = (A::PAGE_SIZE - (base & A::PAGE_OFFSET_MASK)) & A::PAGE_OFFSET_MASK;
+        if base_offset > size {
+            // Area is too small to page align base
+            continue;
+        }
+        base += base_offset;
+        size -= base_offset;
+
+        // Page align size
+        size &= !A::PAGE_OFFSET_MASK;
+        if size == 0 {
+            // Area is zero sized
+            continue;
+        }
+
+        if base + size < kernel_end {
+            // Area is below static kernel data
+            bump_offset += size;
+        } else if base < kernel_end {
+            // Area contains static kernel data
+            bump_offset += kernel_end - base;
+        }
+
+        AREAS[area_i].base = PhysicalAddress::new(base);
+        AREAS[area_i].size = size;
+        area_i += 1;
+    }
+
+    println!("bump_offset: {:X}", bump_offset);
+
+    let allocator = inner::<A>(&AREAS, kernel_base, kernel_size_aligned, bump_offset);
+    *FRAME_ALLOCATOR.inner.lock() = Some(allocator);
+}
diff --git a/src/arch/aarch64/start.rs b/src/arch/aarch64/start.rs
new file mode 100644
index 0000000000000000000000000000000000000000..13d1b2da7693464d02d76d7f33963e9b158fb60d
--- /dev/null
+++ b/src/arch/aarch64/start.rs
@@ -0,0 +1,189 @@
+/// This function is where the kernel sets up IRQ handlers
+/// It is increcibly unsafe, and should be minimal in nature
+/// It must create the IDT with the correct entries, those entries are
+/// defined in other files inside of the `arch` module
+
+use core::slice;
+use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+
+use crate::memory::{Frame};
+use crate::paging::{ActivePageTable, PageTableType, Page, PAGE_SIZE, PhysicalAddress, VirtualAddress};
+use crate::paging::entry::{EntryFlags};
+
+use crate::allocator;
+use crate::device;
+use crate::init::device_tree;
+use crate::interrupt;
+use crate::log::{self, info};
+use crate::paging;
+
+/// Test of zero values in BSS.
+static BSS_TEST_ZERO: usize = 0;
+/// Test of non-zero values in data.
+static DATA_TEST_NONZERO: usize = 0xFFFF_FFFF_FFFF_FFFF;
+/// Test of zero values in thread BSS
+#[thread_local]
+static mut TBSS_TEST_ZERO: usize = 0;
+/// Test of non-zero values in thread data.
+#[thread_local]
+static mut TDATA_TEST_NONZERO: usize = 0xFFFF_FFFF_FFFF_FFFF;
+
+pub static KERNEL_BASE: AtomicUsize = AtomicUsize::new(0);
+pub static KERNEL_SIZE: AtomicUsize = AtomicUsize::new(0);
+pub static CPU_COUNT: AtomicUsize = AtomicUsize::new(0);
+pub static AP_READY: AtomicBool = AtomicBool::new(false);
+static BSP_READY: AtomicBool = AtomicBool::new(false);
+
+#[repr(packed)]
+pub struct KernelArgs {
+    kernel_base: u64,
+    kernel_size: u64,
+    stack_base: u64,
+    stack_size: u64,
+    env_base: u64,
+    env_size: u64,
+    dtb_base: u64,
+    dtb_size: u64,
+}
+
+/// The entry to Rust, all things must be initialized
+#[no_mangle]
+pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! {
+    let env = {
+        let args = &*args_ptr;
+
+        let kernel_base = args.kernel_base as usize;
+        let kernel_size = args.kernel_size as usize;
+        let stack_base = args.stack_base as usize;
+        let stack_size = args.stack_size as usize;
+        let env_base = args.env_base as usize;
+        let env_size = args.env_size as usize;
+        let dtb_base = args.dtb_base as usize;
+        let dtb_size = args.dtb_size as usize;
+
+        //TODO: remove this hack for early console, use device tree
+        {
+            let mut serial = device::uart_pl011::SerialPort::new(crate::KERNEL_DEVMAP_OFFSET + 0x9000000);
+            serial.init(false);
+            serial.send(b'T');
+            serial.send(b'E');
+            serial.send(b'S');
+            serial.send(b'T');
+            serial.send(b'\r');
+            serial.send(b'\n');
+            *device::serial::COM1.lock() = Some(serial);
+        }
+
+        // BSS should already be zero
+        {
+            assert_eq!(BSS_TEST_ZERO, 0);
+            assert_eq!(DATA_TEST_NONZERO, 0xFFFF_FFFF_FFFF_FFFF);
+        }
+
+        KERNEL_BASE.store(kernel_base, Ordering::SeqCst);
+        KERNEL_SIZE.store(kernel_size, Ordering::SeqCst);
+
+        // Initialize logger
+        log::init_logger(|r| {
+            use core::fmt::Write;
+            let _ = write!(
+                crate::debug::Writer::new(),
+                "{}:{} -- {}\n",
+                r.target(),
+                r.level(),
+                r.args()
+            );
+        });
+
+        info!("Redox OS starting...");
+        info!("Kernel: {:X}:{:X}", kernel_base, kernel_base + kernel_size);
+        info!("Stack: {:X}:{:X}", stack_base, stack_base + stack_size);
+        info!("Env: {:X}:{:X}", env_base, env_base + env_size);
+        info!("DTB: {:X}:{:X}", dtb_base, dtb_base + dtb_size);
+
+        //TODO: Until fixed, the DTB is at DEVMAP_OFFSET + dtb_base
+        // This is not required after paging is enabled because paging fixes this
+        device_tree::fill_memory_map(crate::KERNEL_DEVMAP_OFFSET + dtb_base, dtb_size);
+        let env_size = device_tree::fill_env_data(crate::KERNEL_DEVMAP_OFFSET + dtb_base, dtb_size, env_base);
+
+        // Initialize RMM
+        println!("RMM INIT START");
+        crate::arch::rmm::init(kernel_base, kernel_size + stack_size);
+        println!("RMM INIT COMPLETE");
+
+        // Initialize paging
+        println!("PAGING INIT START");
+        let (mut active_table, _tcb_offset) = paging::init(0);
+        println!("PAGING INIT COMPLETE");
+
+        // Test tdata and tbss
+        {
+            assert_eq!(TBSS_TEST_ZERO, 0);
+            TBSS_TEST_ZERO += 1;
+            assert_eq!(TBSS_TEST_ZERO, 1);
+            assert_eq!(TDATA_TEST_NONZERO, 0xFFFF_FFFF_FFFF_FFFF);
+            TDATA_TEST_NONZERO -= 1;
+            assert_eq!(TDATA_TEST_NONZERO, 0xFFFF_FFFF_FFFF_FFFE);
+        }
+
+        // Reset AP variables
+        CPU_COUNT.store(1, Ordering::SeqCst);
+        AP_READY.store(false, Ordering::SeqCst);
+        BSP_READY.store(false, Ordering::SeqCst);
+
+        // Setup kernel heap
+        println!("ALLOCATOR INIT START");
+        allocator::init(&mut active_table);
+        println!("ALLOCATOR INIT COMPLETE");
+
+        // Activate memory logging
+        println!("LOG INIT START");
+        log::init();
+        println!("LOG INIT COMPLETE");
+
+        // Initialize devices
+        println!("DEVICE INIT START");
+        device::init(&mut active_table);
+        println!("DEVICE INIT COMPLETE");
+
+        // Initialize all of the non-core devices not otherwise needed to complete initialization
+        println!("DEVICE INIT NONCORE START");
+        device::init_noncore();
+        println!("DEVICE INIT NONCORE COMPLETE");
+
+        BSP_READY.store(true, Ordering::SeqCst);
+
+        slice::from_raw_parts(env_base as *const u8, env_size)
+    };
+
+    println!("KMAIN");
+    crate::kmain(CPU_COUNT.load(Ordering::SeqCst), env);
+}
+
+#[repr(packed)]
+pub struct KernelArgsAp {
+    cpu_id: u64,
+    page_table: u64,
+    stack_start: u64,
+    stack_end: u64,
+}
+
+/// Entry to rust for an AP
+pub unsafe extern fn kstart_ap(args_ptr: *const KernelArgsAp) -> ! {
+    loop{}
+}
+
+#[naked]
+pub unsafe fn usermode(ip: usize, sp: usize, arg: usize, singlestep: bool) -> ! {
+    let cpu_id: usize = 0;
+    let spsr: u32 = 0;
+
+    llvm_asm!("msr   spsr_el1, $0" : : "r"(spsr) : : "volatile");
+    llvm_asm!("msr   elr_el1, $0" : : "r"(ip) : : "volatile");
+    llvm_asm!("msr   sp_el0, $0" : : "r"(sp) : : "volatile");
+
+    llvm_asm!("mov   x0, $0" : : "r"(arg) : : "volatile");
+    llvm_asm!("eret" : : : : "volatile");
+
+    unreachable!();
+}
diff --git a/src/arch/aarch64/stop.rs b/src/arch/aarch64/stop.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b44c7599861c9083a9c477c3b531268083883f11
--- /dev/null
+++ b/src/arch/aarch64/stop.rs
@@ -0,0 +1,21 @@
+#[no_mangle]
+pub unsafe extern fn kreset() -> ! {
+    println!("kreset");
+
+    let val: u32 = 0x8400_0009;
+    llvm_asm!("mov   x0, $0" : : "r"(val) : : "volatile");
+    llvm_asm!("hvc   #0" : : : : "volatile");
+
+    unreachable!();
+}
+
+#[no_mangle]
+pub unsafe extern fn kstop() -> ! {
+    println!("kstop");
+
+    let val: u32 = 0x8400_0008;
+    llvm_asm!("mov   x0, $0" : : "r"(val) : : "volatile");
+    llvm_asm!("hvc   #0" : : : : "volatile");
+
+    unreachable!();
+}
diff --git a/src/arch/mod.rs b/src/arch/mod.rs
index 1abbd033e03168b56fbc63af10a73425d66b84a7..813ee20da2e46c4b88e0ff0e19c6ef65773dcf5d 100644
--- a/src/arch/mod.rs
+++ b/src/arch/mod.rs
@@ -1,5 +1,11 @@
+#[cfg(target_arch = "aarch64")]
+#[macro_use]
+pub mod aarch64;
+#[cfg(target_arch = "aarch64")]
+pub use self::aarch64::*;
+
 #[cfg(target_arch = "x86_64")]
 #[macro_use]
 pub mod x86_64;
 #[cfg(target_arch = "x86_64")]
-pub use self::x86_64::*;
\ No newline at end of file
+pub use self::x86_64::*;
diff --git a/src/arch/x86_64/consts.rs b/src/arch/x86_64/consts.rs
index 952485296225d0e5405e0a145e098ba0da081cd9..c6170aa3dfe4e2089ae209c32effb6cb48dcb181 100644
--- a/src/arch/x86_64/consts.rs
+++ b/src/arch/x86_64/consts.rs
@@ -21,6 +21,9 @@
     /// Size of kernel heap
     pub const KERNEL_HEAP_SIZE: usize = 1 * 1024 * 1024; // 1 MB
 
+    /// Offset of temporary mapping for misc kernel bring-up actions
+    pub const KERNEL_TMP_MISC_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE;
+
     /// Offset to kernel percpu variables
     //TODO: Use 64-bit fs offset to enable this pub const KERNEL_PERCPU_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE;
     pub const KERNEL_PERCPU_OFFSET: usize = 0xC000_0000;
diff --git a/src/arch/x86_64/interrupt/trace.rs b/src/arch/x86_64/interrupt/trace.rs
index f14d19826ef99f22f6738e288a71e870223ea113..4b224d70bb9e1bec5362ba2df1ddf2acb5567c63 100644
--- a/src/arch/x86_64/interrupt/trace.rs
+++ b/src/arch/x86_64/interrupt/trace.rs
@@ -2,7 +2,7 @@ use core::{mem, str};
 use goblin::elf::sym;
 use rustc_demangle::demangle;
 
-use crate::paging::{ActivePageTable, VirtualAddress};
+use crate::paging::{ActivePageTable, PageTableType, VirtualAddress};
 
 /// Get a stack trace
 //TODO: Check for stack being mapped before dereferencing
@@ -13,7 +13,7 @@ pub unsafe fn stack_trace() {
 
     println!("TRACE: {:>016X}", rbp);
     //Maximum 64 frames
-    let active_table = ActivePageTable::new();
+    let active_table = ActivePageTable::new(PageTableType::User);
     for _frame in 0..64 {
         if let Some(rip_rbp) = rbp.checked_add(mem::size_of::<usize>()) {
             if active_table.translate(VirtualAddress::new(rbp)).is_some() && active_table.translate(VirtualAddress::new(rip_rbp)).is_some() {
diff --git a/src/arch/x86_64/paging/mod.rs b/src/arch/x86_64/paging/mod.rs
index d3f77ff7edb9f37d18123ea36420725ba5b82050..ac4d8a4c35d098ffd4eba7fd73a59e3ebeb4a0b3 100644
--- a/src/arch/x86_64/paging/mod.rs
+++ b/src/arch/x86_64/paging/mod.rs
@@ -15,7 +15,6 @@ pub use rmm::{
     Arch as RmmArch,
     PageFlags,
     PhysicalAddress,
-    VirtualAddress,
     X8664Arch as RmmA,
 };
 
@@ -185,7 +184,7 @@ pub unsafe fn init(
 
     init_pat();
 
-    let mut active_table = ActivePageTable::new_unlocked();
+    let mut active_table = ActivePageTable::new_unlocked(PageTableType::User);
 
     let flush_all = map_tss(cpu_id, &mut active_table);
     flush_all.flush();
@@ -199,7 +198,7 @@ pub unsafe fn init_ap(
 ) -> usize {
     init_pat();
 
-    let mut active_table = ActivePageTable::new_unlocked();
+    let mut active_table = ActivePageTable::new_unlocked(PageTableType::User);
 
     let mut new_table = InactivePageTable::from_address(bsp_table);
 
@@ -226,6 +225,11 @@ pub struct ActivePageTable {
     locked: bool,
 }
 
+pub enum PageTableType {
+    User,
+    Kernel
+}
+
 impl Deref for ActivePageTable {
     type Target = Mapper;
 
@@ -241,7 +245,7 @@ impl DerefMut for ActivePageTable {
 }
 
 impl ActivePageTable {
-    pub unsafe fn new() -> ActivePageTable {
+    pub unsafe fn new(_table_type: PageTableType) -> ActivePageTable {
         page_table_lock();
         ActivePageTable {
             mapper: Mapper::new(),
@@ -249,7 +253,7 @@ impl ActivePageTable {
         }
     }
 
-    pub unsafe fn new_unlocked() -> ActivePageTable {
+    pub unsafe fn new_unlocked(_table_type: PageTableType) -> ActivePageTable {
         ActivePageTable {
             mapper: Mapper::new(),
             locked: false,
@@ -364,9 +368,9 @@ impl InactivePageTable {
         InactivePageTable { frame: frame }
     }
 
-    pub unsafe fn from_address(cr3: usize) -> InactivePageTable {
+    pub unsafe fn from_address(address: usize) -> InactivePageTable {
         InactivePageTable {
-            frame: Frame::containing_address(PhysicalAddress::new(cr3)),
+            frame: Frame::containing_address(PhysicalAddress::new(address)),
         }
     }
 
@@ -375,6 +379,34 @@ impl InactivePageTable {
     }
 }
 
+/// A virtual address.
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub struct VirtualAddress(usize);
+
+#[derive(Debug, PartialEq)]
+pub enum VirtualAddressType {
+    User,
+    Kernel
+}
+
+impl VirtualAddress {
+    pub fn new(address: usize) -> Self {
+        VirtualAddress(address)
+    }
+
+    pub fn data(&self) -> usize {
+        self.0
+    }
+
+    pub fn get_type(&self) -> VirtualAddressType {
+        if ((self.0 >> 48) & 0xffff) == 0xffff {
+            VirtualAddressType::Kernel
+        } else {
+            VirtualAddressType::User
+        }
+    }
+}
+
 /// Page
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Page {
diff --git a/src/arch/x86_64/rmm.rs b/src/arch/x86_64/rmm.rs
index 640f60cf797c39cee474dc8fe784e39ce070e08a..970dbd00d3f4c1350a7f8e17a59d87fb3d1fa5f0 100644
--- a/src/arch/x86_64/rmm.rs
+++ b/src/arch/x86_64/rmm.rs
@@ -46,7 +46,7 @@ unsafe fn page_flags<A: Arch>(virt: VirtualAddress) -> PageFlags<A> {
         // Remap rodata read-only, no execute
         PageFlags::new()
     } else {
-        // Remap everything else writable, no execute
+        // Remap everything else read-write, no execute
         PageFlags::new().write(true)
     }
 }
diff --git a/src/context/arch/aarch64.rs b/src/context/arch/aarch64.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2b850a7c9785a87f51f7dcdc08e851e991b20c81
--- /dev/null
+++ b/src/context/arch/aarch64.rs
@@ -0,0 +1,500 @@
+use core::mem;
+use core::sync::atomic::{AtomicBool, Ordering};
+
+use crate::device::cpu::registers::{control_regs, tlb};
+use crate::syscall::FloatRegisters;
+
+/// This must be used by the kernel to ensure that context switches are done atomically
+/// Compare and exchange this to true when beginning a context switch on any CPU
+/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
+/// This must be done, as no locks can be held on the stack during switch
+pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
+
+#[derive(Clone, Debug)]
+pub struct Context {
+    elr_el1: usize,
+    sp_el0: usize,
+    ttbr0_el1: usize,   /* Pointer to U4 translation table for this Context     */
+    ttbr1_el1: usize,   /* Pointer to P4 translation table for this Context     */
+    tpidr_el0: usize,   /* Pointer to TLS region for this Context               */
+    tpidrro_el0: usize, /* Pointer to TLS (read-only) region for this Context   */
+    spsr_el1: usize,
+    esr_el1: usize,
+    fx_loadable: bool,
+    fx_address: usize,
+    sp: usize,          /* Stack Pointer (x31)                                  */
+    lr: usize,          /* Link Register (x30)                                  */
+    fp: usize,          /* Frame pointer Register (x29)                         */
+    x28: usize,         /* Callee saved Register                                */
+    x27: usize,         /* Callee saved Register                                */
+    x26: usize,         /* Callee saved Register                                */
+    x25: usize,         /* Callee saved Register                                */
+    x24: usize,         /* Callee saved Register                                */
+    x23: usize,         /* Callee saved Register                                */
+    x22: usize,         /* Callee saved Register                                */
+    x21: usize,         /* Callee saved Register                                */
+    x20: usize,         /* Callee saved Register                                */
+    x19: usize,         /* Callee saved Register                                */
+    x18: usize,
+    x17: usize,
+    x16: usize,
+    x15: usize,         /* Temporary Register                                   */
+    x14: usize,         /* Temporary Register                                   */
+    x13: usize,         /* Temporary Register                                   */
+    x12: usize,         /* Temporary Register                                   */
+    x11: usize,         /* Temporary Register                                   */
+    x10: usize,         /* Temporary Register                                   */
+    x9: usize,          /* Temporary Register                                   */
+    x8: usize,          /* Indirect location Register                           */
+}
+
+impl Context {
+    pub fn new() -> Context {
+        Context {
+            elr_el1: 0,
+            sp_el0: 0,
+            ttbr0_el1: 0,
+            ttbr1_el1: 0,
+            tpidr_el0: 0,
+            tpidrro_el0: 0,
+            spsr_el1: 0,
+            esr_el1: 0,
+            fx_loadable: false,
+            fx_address: 0,
+            sp: 0,
+            lr: 0,
+            fp: 0,
+            x28: 0,
+            x27: 0,
+            x26: 0,
+            x25: 0,
+            x24: 0,
+            x23: 0,
+            x22: 0,
+            x21: 0,
+            x20: 0,
+            x19: 0,
+            x18: 0,
+            x17: 0,
+            x16: 0,
+            x15: 0,
+            x14: 0,
+            x13: 0,
+            x12: 0,
+            x11: 0,
+            x10: 0,
+            x9: 0,
+            x8: 0,
+        }
+    }
+
+    pub fn get_page_utable(&self) -> usize {
+        self.ttbr0_el1
+    }
+
+    pub fn get_page_ktable(&self) -> usize {
+        self.ttbr1_el1
+    }
+
+    pub fn set_page_utable(&mut self, address: usize) {
+        self.ttbr0_el1 = address;
+    }
+
+    pub fn set_page_ktable(&mut self, address: usize) {
+        self.ttbr1_el1 = address;
+    }
+
+    pub fn set_stack(&mut self, address: usize) {
+        self.sp = address;
+    }
+
+    pub fn set_lr(&mut self, address: usize) {
+        self.lr = address;
+    }
+
+    pub fn set_tcb(&mut self, pid: usize) {
+        self.tpidr_el0 = (crate::USER_TCB_OFFSET + pid * crate::PAGE_SIZE);
+    }
+
+    pub fn set_fp(&mut self, address: usize) {
+        self.fp = address;
+    }
+
+    pub fn set_context_handle(&mut self) {
+        let address = self as *const _ as usize;
+        self.tpidrro_el0 = address;
+    }
+
+    pub fn get_context_handle(&mut self) -> usize {
+        self.tpidrro_el0
+    }
+
+    pub unsafe fn signal_stack(&mut self, handler: extern fn(usize), sig: u8) {
+        self.push_stack(sig as usize);
+        self.push_stack(handler as usize);
+        let lr = self.lr.clone();
+        self.push_stack(lr);
+        self.set_lr(signal_handler_wrapper as usize);
+    }
+
+    pub unsafe fn push_stack(&mut self, value: usize) {
+        self.sp -= 1 * mem::size_of::<usize>();
+        *(self.sp as *mut usize) = value;
+    }
+
+    pub unsafe fn pop_stack(&mut self) -> usize {
+        let value = *(self.sp as *const usize);
+        self.sp += 1 * mem::size_of::<usize>();
+        value
+    }
+
+    pub fn get_fx_regs(&self) -> Option<FloatRegisters> {
+        if !self.fx_loadable {
+            return None;
+        }
+        let mut regs = unsafe { *(self.fx_address as *const FloatRegisters) };
+        let mut new_st = regs.fp_simd_regs;
+        regs.fp_simd_regs = new_st;
+        Some(regs)
+    }
+
+    pub fn set_fx_regs(&mut self, mut new: FloatRegisters) -> bool {
+        if !self.fx_loadable {
+            return false;
+        }
+
+        {
+            let old = unsafe { &*(self.fx_address as *const FloatRegisters) };
+            let old_st = new.fp_simd_regs;
+            let mut new_st = new.fp_simd_regs;
+            for (new_st, old_st) in new_st.iter_mut().zip(&old_st) {
+                *new_st = *old_st;
+            }
+            new.fp_simd_regs = new_st;
+
+            // Make sure we don't use `old` from now on
+        }
+
+        unsafe {
+            *(self.fx_address as *mut FloatRegisters) = new;
+        }
+        true
+    }
+
+    pub fn set_fx(&mut self, address: usize) {
+        self.fx_address = address;
+    }
+
+
+    pub fn dump(&self) {
+        println!("elr_el1: 0x{:016x}", self.elr_el1);
+        println!("sp_el0: 0x{:016x}", self.sp_el0);
+        println!("ttbr0_el1: 0x{:016x}", self.ttbr0_el1);
+        println!("ttbr1_el1: 0x{:016x}", self.ttbr1_el1);
+        println!("tpidr_el0: 0x{:016x}", self.tpidr_el0);
+        println!("tpidrro_el0: 0x{:016x}", self.tpidrro_el0);
+        println!("spsr_el1: 0x{:016x}", self.spsr_el1);
+        println!("esr_el1: 0x{:016x}", self.esr_el1);
+        println!("sp: 0x{:016x}", self.sp);
+        println!("lr: 0x{:016x}", self.lr);
+        println!("fp: 0x{:016x}", self.fp);
+        println!("x28: 0x{:016x}", self.x28);
+        println!("x27: 0x{:016x}", self.x27);
+        println!("x26: 0x{:016x}", self.x26);
+        println!("x25: 0x{:016x}", self.x25);
+        println!("x24: 0x{:016x}", self.x24);
+        println!("x23: 0x{:016x}", self.x23);
+        println!("x22: 0x{:016x}", self.x22);
+        println!("x21: 0x{:016x}", self.x21);
+        println!("x20: 0x{:016x}", self.x20);
+        println!("x19: 0x{:016x}", self.x19);
+        println!("x18: 0x{:016x}", self.x18);
+        println!("x17: 0x{:016x}", self.x17);
+        println!("x16: 0x{:016x}", self.x16);
+        println!("x15: 0x{:016x}", self.x15);
+        println!("x14: 0x{:016x}", self.x14);
+        println!("x13: 0x{:016x}", self.x13);
+        println!("x12: 0x{:016x}", self.x12);
+        println!("x11: 0x{:016x}", self.x11);
+        println!("x10: 0x{:016x}", self.x10);
+        println!("x9: 0x{:016x}", self.x9);
+        println!("x8: 0x{:016x}", self.x8);
+    }
+
+    #[cold]
+    #[inline(never)]
+    #[naked]
+    pub unsafe fn switch_to(&mut self, next: &mut Context) {
+        let mut float_regs = &mut *(self.fx_address as *mut FloatRegisters);
+        asm!(
+            "stp q0, q1, [{0}, #16 * 0]",
+            "stp q2, q3, [{0}, #16 * 2]",
+            "stp q4, q5, [{0}, #16 * 4]",
+            "stp q6, q7, [{0}, #16 * 6]",
+            "stp q8, q9, [{0}, #16 * 8]",
+            "stp q10, q11, [{0}, #16 * 10]",
+            "stp q12, q13, [{0}, #16 * 12]",
+            "stp q14, q15, [{0}, #16 * 14]",
+            "stp q16, q17, [{0}, #16 * 16]",
+            "stp q18, q19, [{0}, #16 * 18]",
+            "stp q20, q21, [{0}, #16 * 20]",
+            "stp q22, q23, [{0}, #16 * 22]",
+            "stp q24, q25, [{0}, #16 * 24]",
+            "stp q26, q27, [{0}, #16 * 26]",
+            "stp q28, q29, [{0}, #16 * 28]",
+            "stp q30, q31, [{0}, #16 * 30]",
+            "mrs {1}, fpcr",
+            "mrs {2}, fpsr",
+            in(reg) &mut float_regs.fp_simd_regs,
+            out(reg) float_regs.fpcr,
+            out(reg) float_regs.fpsr
+        );
+
+        self.fx_loadable = true;
+
+        if next.fx_loadable {
+            let mut float_regs = &mut *(next.fx_address as *mut FloatRegisters);
+            asm!(
+                "ldp q0, q1, [{0}, #16 * 0]",
+                "ldp q2, q3, [{0}, #16 * 2]",
+                "ldp q4, q5, [{0}, #16 * 4]",
+                "ldp q6, q7, [{0}, #16 * 6]",
+                "ldp q8, q9, [{0}, #16 * 8]",
+                "ldp q10, q11, [{0}, #16 * 10]",
+                "ldp q12, q13, [{0}, #16 * 12]",
+                "ldp q14, q15, [{0}, #16 * 14]",
+                "ldp q16, q17, [{0}, #16 * 16]",
+                "ldp q18, q19, [{0}, #16 * 18]",
+                "ldp q20, q21, [{0}, #16 * 20]",
+                "ldp q22, q23, [{0}, #16 * 22]",
+                "ldp q24, q25, [{0}, #16 * 24]",
+                "ldp q26, q27, [{0}, #16 * 26]",
+                "ldp q28, q29, [{0}, #16 * 28]",
+                "ldp q30, q31, [{0}, #16 * 30]",
+                "msr fpcr, {1}",
+                "msr fpsr, {2}",
+                in(reg) &mut float_regs.fp_simd_regs,
+                in(reg) float_regs.fpcr,
+                in(reg) float_regs.fpsr
+            );
+        }
+
+        self.ttbr0_el1 = control_regs::ttbr0_el1() as usize;
+        if next.ttbr0_el1 != self.ttbr0_el1 {
+            control_regs::ttbr0_el1_write(next.ttbr0_el1 as u64);
+            tlb::flush_all();
+        }
+
+        llvm_asm!("mov   $0, x8" : "=r"(self.x8) : : "memory" : "volatile");
+        llvm_asm!("mov   x8, $0" : :  "r"(next.x8) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x9" : "=r"(self.x9) : : "memory" : "volatile");
+        llvm_asm!("mov   x9, $0" : :  "r"(next.x9) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x10" : "=r"(self.x10) : : "memory" : "volatile");
+        llvm_asm!("mov   x10, $0" : :  "r"(next.x10) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x11" : "=r"(self.x11) : : "memory" : "volatile");
+        llvm_asm!("mov   x11, $0" : :  "r"(next.x11) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x12" : "=r"(self.x12) : : "memory" : "volatile");
+        llvm_asm!("mov   x12, $0" : :  "r"(next.x12) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x13" : "=r"(self.x13) : : "memory" : "volatile");
+        llvm_asm!("mov   x13, $0" : :  "r"(next.x13) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x14" : "=r"(self.x14) : : "memory" : "volatile");
+        llvm_asm!("mov   x14, $0" : :  "r"(next.x14) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x15" : "=r"(self.x15) : : "memory" : "volatile");
+        llvm_asm!("mov   x15, $0" : :  "r"(next.x15) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x16" : "=r"(self.x16) : : "memory" : "volatile");
+        llvm_asm!("mov   x16, $0" : :  "r"(next.x16) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x17" : "=r"(self.x17) : : "memory" : "volatile");
+        llvm_asm!("mov   x17, $0" : :  "r"(next.x17) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x18" : "=r"(self.x18) : : "memory" : "volatile");
+        llvm_asm!("mov   x18, $0" : :  "r"(next.x18) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x19" : "=r"(self.x19) : : "memory" : "volatile");
+        llvm_asm!("mov   x19, $0" : :  "r"(next.x19) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x20" : "=r"(self.x20) : : "memory" : "volatile");
+        llvm_asm!("mov   x20, $0" : :  "r"(next.x20) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x21" : "=r"(self.x21) : : "memory" : "volatile");
+        llvm_asm!("mov   x21, $0" : :  "r"(next.x21) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x22" : "=r"(self.x22) : : "memory" : "volatile");
+        llvm_asm!("mov   x22, $0" : :  "r"(next.x22) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x23" : "=r"(self.x23) : : "memory" : "volatile");
+        llvm_asm!("mov   x23, $0" : :  "r"(next.x23) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x24" : "=r"(self.x24) : : "memory" : "volatile");
+        llvm_asm!("mov   x24, $0" : :  "r"(next.x24) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x25" : "=r"(self.x25) : : "memory" : "volatile");
+        llvm_asm!("mov   x25, $0" : :  "r"(next.x25) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x26" : "=r"(self.x26) : : "memory" : "volatile");
+        llvm_asm!("mov   x26, $0" : :  "r"(next.x26) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x27" : "=r"(self.x27) : : "memory" : "volatile");
+        llvm_asm!("mov   x27, $0" : :  "r"(next.x27) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x28" : "=r"(self.x28) : : "memory" : "volatile");
+        llvm_asm!("mov   x28, $0" : :  "r"(next.x28) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x29" : "=r"(self.fp) : : "memory" : "volatile");
+        llvm_asm!("mov   x29, $0" : :  "r"(next.fp) :"memory" : "volatile");
+
+        llvm_asm!("mov   $0, x30" : "=r"(self.lr) : : "memory" : "volatile");
+        llvm_asm!("mov   x30, $0" : :  "r"(next.lr) :"memory" : "volatile");
+
+        llvm_asm!("mrs   $0, elr_el1" : "=r"(self.elr_el1) : : "memory" : "volatile");
+        llvm_asm!("msr   elr_el1, $0" : : "r"(next.elr_el1) : "memory" : "volatile");
+
+        llvm_asm!("mrs   $0, sp_el0" : "=r"(self.sp_el0) : : "memory" : "volatile");
+        llvm_asm!("msr   sp_el0, $0" : : "r"(next.sp_el0) : "memory" : "volatile");
+
+        llvm_asm!("mrs   $0, tpidr_el0" : "=r"(self.tpidr_el0) : : "memory" : "volatile");
+        llvm_asm!("msr   tpidr_el0, $0" : : "r"(next.tpidr_el0) : "memory" : "volatile");
+
+        llvm_asm!("mrs   $0, tpidrro_el0" : "=r"(self.tpidrro_el0) : : "memory" : "volatile");
+        llvm_asm!("msr   tpidrro_el0, $0" : : "r"(next.tpidrro_el0) : "memory" : "volatile");
+
+        llvm_asm!("mrs   $0, spsr_el1" : "=r"(self.spsr_el1) : : "memory" : "volatile");
+        llvm_asm!("msr   spsr_el1, $0" : : "r"(next.spsr_el1) : "memory" : "volatile");
+
+        llvm_asm!("mrs   $0, esr_el1" : "=r"(self.esr_el1) : : "memory" : "volatile");
+        llvm_asm!("msr   esr_el1, $0" : : "r"(next.esr_el1) : "memory" : "volatile");
+
+        llvm_asm!("mov   $0, sp" : "=r"(self.sp) : : "memory" : "volatile");
+        llvm_asm!("mov   sp, $0" : : "r"(next.sp) : "memory" : "volatile");
+
+        CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
+    }
+}
+
+#[allow(dead_code)]
+#[repr(packed)]
+pub struct SignalHandlerStack {
+    x28: usize,         /* Callee saved Register                                */
+    x27: usize,         /* Callee saved Register                                */
+    x26: usize,         /* Callee saved Register                                */
+    x25: usize,         /* Callee saved Register                                */
+    x24: usize,         /* Callee saved Register                                */
+    x23: usize,         /* Callee saved Register                                */
+    x22: usize,         /* Callee saved Register                                */
+    x21: usize,         /* Callee saved Register                                */
+    x20: usize,         /* Callee saved Register                                */
+    x19: usize,         /* Callee saved Register                                */
+    x18: usize,
+    x17: usize,
+    x16: usize,
+    x15: usize,         /* Temporary Register                                   */
+    x14: usize,         /* Temporary Register                                   */
+    x13: usize,         /* Temporary Register                                   */
+    x12: usize,         /* Temporary Register                                   */
+    x11: usize,         /* Temporary Register                                   */
+    x10: usize,         /* Temporary Register                                   */
+    x9: usize,          /* Temporary Register                                   */
+    x8: usize,          /* Indirect location Register                           */
+    x7: usize,
+    x6: usize,
+    x5: usize,
+    x4: usize,
+    x3: usize,
+    x2: usize,
+    x1: usize,
+    x0: usize,
+    lr: usize,
+    handler: extern fn(usize),
+    sig: usize,
+}
+
+#[naked]
+unsafe extern fn signal_handler_wrapper() {
+    #[inline(never)]
+    unsafe fn inner(stack: &SignalHandlerStack) {
+        (stack.handler)(stack.sig);
+    }
+
+    // Push scratch registers
+    llvm_asm!("str	    x0, [sp, #-8]!
+          str	    x1, [sp, #-8]!
+          str	    x2, [sp, #-8]!
+          str	    x3, [sp, #-8]!
+          str	    x4, [sp, #-8]!
+          str	    x5, [sp, #-8]!
+          str	    x6, [sp, #-8]!
+          str	    x7, [sp, #-8]!
+          str	    x8, [sp, #-8]!
+          str	    x9, [sp, #-8]!
+          str	    x10, [sp, #-8]!
+          str	    x11, [sp, #-8]!
+          str	    x12, [sp, #-8]!
+          str	    x13, [sp, #-8]!
+          str	    x14, [sp, #-8]!
+          str	    x15, [sp, #-8]!
+          str	    x16, [sp, #-8]!
+          str	    x17, [sp, #-8]!
+          str	    x18, [sp, #-8]!
+          str	    x19, [sp, #-8]!
+          str	    x20, [sp, #-8]!
+          str	    x21, [sp, #-8]!
+          str	    x22, [sp, #-8]!
+          str	    x23, [sp, #-8]!
+          str	    x24, [sp, #-8]!
+          str	    x25, [sp, #-8]!
+          str	    x26, [sp, #-8]!
+          str	    x27, [sp, #-8]!
+          str	    x28, [sp, #-8]!"
+    : : : : "volatile");
+
+    // Get reference to stack variables
+    let sp: usize;
+    llvm_asm!("" : "={sp}"(sp) : : : "volatile");
+
+    let ptr = sp as *const SignalHandlerStack;
+    let final_lr = (*ptr).lr;
+
+    // Call inner rust function
+    inner(&*(sp as *const SignalHandlerStack));
+
+    // Pop scratch registers, error code, and return
+    llvm_asm!("ldr	    x28, [sp], #8
+          ldr	    x27, [sp], #8
+          ldr	    x26, [sp], #8
+          ldr	    x25, [sp], #8
+          ldr	    x24, [sp], #8
+          ldr	    x23, [sp], #8
+          ldr	    x22, [sp], #8
+          ldr	    x21, [sp], #8
+          ldr	    x20, [sp], #8
+          ldr	    x19, [sp], #8
+          ldr	    x18, [sp], #8
+          ldr	    x17, [sp], #8
+          ldr	    x16, [sp], #8
+          ldr	    x15, [sp], #8
+          ldr	    x14, [sp], #8
+          ldr	    x13, [sp], #8
+          ldr	    x12, [sp], #8
+          ldr	    x11, [sp], #8
+          ldr	    x10, [sp], #8
+          ldr	    x9, [sp], #8
+          ldr	    x8, [sp], #8
+          ldr	    x7, [sp], #8
+          ldr	    x6, [sp], #8
+          ldr	    x5, [sp], #8
+          ldr	    x4, [sp], #8
+          ldr	    x3, [sp], #8
+          ldr	    x2, [sp], #8
+          ldr	    x1, [sp], #8"
+    : : : : "volatile");
+
+    llvm_asm!("mov       x30, $0" : : "r"(final_lr) : "memory" : "volatile");
+}
diff --git a/src/context/arch/x86_64.rs b/src/context/arch/x86_64.rs
index ee7ce5b56f16ab9716f3d200489ee8e879702c72..a333a697680c0fa491394179a65a1eaeba5723e0 100644
--- a/src/context/arch/x86_64.rs
+++ b/src/context/arch/x86_64.rs
@@ -62,7 +62,7 @@ impl Context {
         }
     }
 
-    pub fn get_page_table(&mut self) -> usize {
+    pub fn get_page_utable(&mut self) -> usize {
         self.cr3
     }
 
@@ -110,7 +110,7 @@ impl Context {
         self.fx = address;
     }
 
-    pub fn set_page_table(&mut self, address: usize) {
+    pub fn set_page_utable(&mut self, address: usize) {
         self.cr3 = address;
     }
 
diff --git a/src/context/list.rs b/src/context/list.rs
index 0095e23e32dd861c61e8213521d4378c4e1c389d..32587248d7a289b0c6c8bc6eda56e3a0d5cbb2ae 100644
--- a/src/context/list.rs
+++ b/src/context/list.rs
@@ -4,7 +4,7 @@ use alloc::collections::BTreeMap;
 use core::alloc::{GlobalAlloc, Layout};
 use core::{iter, mem};
 use core::sync::atomic::Ordering;
-use crate::paging;
+use crate::paging::{ActivePageTable, PageTableType};
 use spin::RwLock;
 
 use crate::syscall::error::{Result, Error, EAGAIN};
@@ -79,18 +79,30 @@ impl ContextList {
         let context_lock = self.new_context()?;
         {
             let mut context = context_lock.write();
-            let mut fx = unsafe { Box::from_raw(crate::ALLOCATOR.alloc(Layout::from_size_align_unchecked(512, 16)) as *mut [u8; 512]) };
+            let mut fx = unsafe { Box::from_raw(crate::ALLOCATOR.alloc(Layout::from_size_align_unchecked(1024, 16)) as *mut [u8; 1024]) };
             for b in fx.iter_mut() {
                 *b = 0;
             }
             let mut stack = vec![0; 65_536].into_boxed_slice();
             let offset = stack.len() - mem::size_of::<usize>();
+
+            #[cfg(target_arch = "x86_64")]
             unsafe {
                 let offset = stack.len() - mem::size_of::<usize>();
                 let func_ptr = stack.as_mut_ptr().add(offset);
                 *(func_ptr as *mut usize) = func as usize;
             }
-            context.arch.set_page_table(unsafe { paging::ActivePageTable::new().address() });
+
+            #[cfg(target_arch = "aarch64")]
+            {
+                let context_id = context.id.into();
+                context.arch.set_lr(func as usize);
+                context.arch.set_context_handle();
+            }
+
+            context.arch.set_page_utable(unsafe { ActivePageTable::new(PageTableType::User).address() });
+            #[cfg(target_arch = "aarch64")]
+            context.arch.set_page_ktable(unsafe { ActivePageTable::new(PageTableType::Kernel).address() });
             context.arch.set_fx(fx.as_ptr() as usize);
             context.arch.set_stack(stack.as_ptr() as usize + offset);
             context.kfx = Some(fx);
diff --git a/src/context/memory.rs b/src/context/memory.rs
index 1b8b6dd366c87f06e4414b2c723e2444f32fdbfc..87f2e4190a6fc3479d97b422bfd72a18c5b76e56 100644
--- a/src/context/memory.rs
+++ b/src/context/memory.rs
@@ -15,7 +15,7 @@ use crate::arch::paging::PAGE_SIZE;
 use crate::context::file::FileDescriptor;
 use crate::ipi::{ipi, IpiKind, IpiTarget};
 use crate::memory::Frame;
-use crate::paging::{ActivePageTable, InactivePageTable, Page, PageFlags, PageIter, PhysicalAddress, RmmA, VirtualAddress};
+use crate::paging::{ActivePageTable, InactivePageTable, Page, PageFlags, PageTableType, PageIter, PhysicalAddress, RmmA, VirtualAddress};
 use crate::paging::mapper::PageFlushAll;
 use crate::paging::temporary_page::TemporaryPage;
 
@@ -303,7 +303,10 @@ impl Grant {
     }
 
     pub fn physmap(from: PhysicalAddress, to: VirtualAddress, size: usize, flags: PageFlags<RmmA>) -> Grant {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match to.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -330,7 +333,10 @@ impl Grant {
     }
 
     pub fn map(to: VirtualAddress, size: usize, flags: PageFlags<RmmA>) -> Grant {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match to.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -356,7 +362,10 @@ impl Grant {
     }
 
     pub fn map_inactive(from: VirtualAddress, to: VirtualAddress, size: usize, flags: PageFlags<RmmA>, desc_opt: Option<FileDescriptor>, new_table: &mut InactivePageTable, temporary_page: &mut TemporaryPage) -> Grant {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match from.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         //TODO: Do not allocate
         let mut frames = VecDeque::with_capacity(size/PAGE_SIZE);
@@ -368,6 +377,11 @@ impl Grant {
             frames.push_back(frame);
         }
 
+        let mut active_table = match to.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
+
         active_table.with(new_table, temporary_page, |mapper| {
             let start_page = Page::containing_address(to);
             let end_page = Page::containing_address(VirtualAddress::new(to.data() + size - 1));
@@ -397,7 +411,10 @@ impl Grant {
     pub fn secret_clone(&self, new_start: VirtualAddress) -> Grant {
         assert!(self.mapped);
 
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match new_start.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -454,7 +471,10 @@ impl Grant {
     pub fn move_to(&mut self, new_start: VirtualAddress, new_table: &mut InactivePageTable, temporary_page: &mut TemporaryPage) {
         assert!(self.mapped);
 
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match new_start.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -490,7 +510,11 @@ impl Grant {
     pub fn unmap(mut self) {
         assert!(self.mapped);
 
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match self.start_address().get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
+
 
         let flush_all = PageFlushAll::new();
 
@@ -519,7 +543,10 @@ impl Grant {
     pub fn unmap_inactive(mut self, new_table: &mut InactivePageTable, temporary_page: &mut TemporaryPage) {
         assert!(self.mapped);
 
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match self.start_address().get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         active_table.with(new_table, temporary_page, |mapper| {
             let start_page = Page::containing_address(self.start_address());
@@ -694,7 +721,10 @@ impl Memory {
     }
 
     fn map(&mut self, clear: bool) {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match self.start.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -714,7 +744,10 @@ impl Memory {
     }
 
     fn unmap(&mut self) {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match self.start.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -729,7 +762,10 @@ impl Memory {
     /// A complicated operation to move a piece of memory to a new page table
     /// It also allows for changing the address at the same time
     pub fn move_to(&mut self, new_start: VirtualAddress, new_table: &mut InactivePageTable, temporary_page: &mut TemporaryPage) {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match new_start.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -751,7 +787,10 @@ impl Memory {
     }
 
     pub fn remap(&mut self, new_flags: PageFlags<RmmA>) {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match self.start.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         let flush_all = PageFlushAll::new();
 
@@ -766,7 +805,10 @@ impl Memory {
     }
 
     pub fn resize(&mut self, new_size: usize, clear: bool) {
-        let mut active_table = unsafe { ActivePageTable::new() };
+        let mut active_table = match self.start.get_type() {
+            VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+            VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+        };
 
         //TODO: Calculate page changes to minimize operations
         if new_size > self.size {
diff --git a/src/context/mod.rs b/src/context/mod.rs
index efc6bcf815d4a8feedf3f6d4d466f03e98bd4fca..6fe66b6870e64a7bf4b658533e59e63896b0f7c8 100644
--- a/src/context/mod.rs
+++ b/src/context/mod.rs
@@ -10,6 +10,11 @@ pub use self::context::{Context, ContextId, ContextSnapshot, Status, WaitpidKey}
 pub use self::list::ContextList;
 pub use self::switch::switch;
 
+#[cfg(target_arch = "aarch64")]
+#[path = "arch/aarch64.rs"]
+mod arch;
+
+#[cfg(target_arch = "x86_64")]
 #[path = "arch/x86_64.rs"]
 mod arch;
 
@@ -52,7 +57,7 @@ pub fn init() {
     let mut contexts = contexts_mut();
     let context_lock = contexts.new_context().expect("could not initialize first context");
     let mut context = context_lock.write();
-    let mut fx = unsafe { Box::from_raw(crate::ALLOCATOR.alloc(Layout::from_size_align_unchecked(512, 16)) as *mut [u8; 512]) };
+    let mut fx = unsafe { Box::from_raw(crate::ALLOCATOR.alloc(Layout::from_size_align_unchecked(1024, 16)) as *mut [u8; 1024]) };
     for b in fx.iter_mut() {
         *b = 0;
     }
diff --git a/src/context/switch.rs b/src/context/switch.rs
index 484e7a0c2a26efdc20568cf37069f4729210effe..2c374309b14643091cc23e7548a550c147557420 100644
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -8,6 +8,7 @@ use spin::RwLock;
 
 use crate::context::signal::signal_handler;
 use crate::context::{arch, contexts, Context, Status, CONTEXT_ID};
+#[cfg(target_arch = "x86_64")]
 use crate::gdt;
 use crate::interrupt::irq::PIT_TICKS;
 use crate::interrupt;
@@ -165,10 +166,18 @@ pub unsafe fn switch() -> bool {
 
         from_context_guard.running = false;
         to_context.running = true;
-        if let Some(ref stack) = to_context.kstack {
-            gdt::set_tss_stack(stack.as_ptr() as usize + stack.len());
+        #[cfg(target_arch = "x86_64")]
+        {
+            if let Some(ref stack) = to_context.kstack {
+                gdt::set_tss_stack(stack.as_ptr() as usize + stack.len());
+            }
+            gdt::set_tcb(to_context.id.into());
+        }
+        #[cfg(target_arch = "aarch64")]
+        {
+            let pid = to_context.id.into();
+            to_context.arch.set_tcb(pid);
         }
-        gdt::set_tcb(to_context.id.into());
         CONTEXT_ID.store(to_context.id, Ordering::SeqCst);
 
         if let Some(sig) = to_sig {
diff --git a/src/devices/mod.rs b/src/devices/mod.rs
index 9dc8d5bd2a1d9432f4f2a07be227815fbc4ee68a..0fb7194a73ecf1842fdcc28f89171d85f86ef6aa 100644
--- a/src/devices/mod.rs
+++ b/src/devices/mod.rs
@@ -1 +1 @@
-pub mod uart_16550;
\ No newline at end of file
+pub mod uart_16550;
diff --git a/src/elf.rs b/src/elf.rs
index 183ed84aefa4b648616c4b4ba6903e1ad6548468..d74fe3f5a07ae37bc0c6331bfed3ef7e65cfa0b1 100644
--- a/src/elf.rs
+++ b/src/elf.rs
@@ -7,7 +7,10 @@ use goblin::elf::section_header::SHT_SYMTAB;
 #[cfg(target_arch = "x86")]
 pub use goblin::elf32::{header, program_header, section_header, sym};
 
-#[cfg(target_arch = "x86_64")]
+#[cfg(any(
+    target_arch = "aarch64",
+    target_arch = "x86_64"
+))]
 pub use goblin::elf64::{header, program_header, section_header, sym};
 
 /// An ELF executable
diff --git a/src/lib.rs b/src/lib.rs
index d56cac06ea9d58740911766894e4e4f40f6b39ba..0966e628758850d06b181e18ea1dafa8d1e3ab5d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -44,6 +44,7 @@
 
 #![feature(allocator_api)]
 #![feature(asm)] // TODO: Relax requirements of most asm invocations
+#![cfg_attr(target_arch = "aarch64", feature(llvm_asm))] // TODO: Rewrite using asm!
 #![feature(concat_idents)]
 #![feature(const_fn)]
 #![feature(core_intrinsics)]
@@ -63,6 +64,8 @@ extern crate alloc;
 
 #[macro_use]
 extern crate bitflags;
+#[macro_use]
+extern crate bitfield;
 extern crate goblin;
 extern crate linked_list_allocator;
 extern crate rustc_demangle;
diff --git a/src/ptrace.rs b/src/ptrace.rs
index 2528cd84a6737f3e642e86b6374152f5aaa14337..9d7480ea0395a43f5c3ef63edc55315c5781c176 100644
--- a/src/ptrace.rs
+++ b/src/ptrace.rs
@@ -8,7 +8,7 @@ use crate::{
         paging::{
             mapper::PageFlushAll,
             temporary_page::TemporaryPage,
-            ActivePageTable, InactivePageTable, Page, PAGE_SIZE, VirtualAddress
+            ActivePageTable, InactivePageTable, PageTableType, Page, PAGE_SIZE, VirtualAddress
         }
     },
     common::unique::Unique,
@@ -457,9 +457,9 @@ where F: FnOnce(*mut u8) -> Result<()>
     // in `proc:<pid>/mem`, or return a partial read/write.
     let start = Page::containing_address(VirtualAddress::new(crate::USER_TMP_MISC_OFFSET));
 
-    let mut active_page_table = unsafe { ActivePageTable::new() };
+    let mut active_page_table = unsafe { ActivePageTable::new(PageTableType::User) };
     let mut target_page_table = unsafe {
-        InactivePageTable::from_address(context.arch.get_page_table())
+        InactivePageTable::from_address(context.arch.get_page_utable())
     };
 
     // Find the physical frames for all pages
diff --git a/src/scheme/memory.rs b/src/scheme/memory.rs
index 5a812e60107e608f74718e7d2713ae7135a8c08b..125c57392845142a6943a1467bb8dc4cc48c4fb2 100644
--- a/src/scheme/memory.rs
+++ b/src/scheme/memory.rs
@@ -1,7 +1,7 @@
 use crate::context;
 use crate::context::memory::{page_flags, Grant};
 use crate::memory::{free_frames, used_frames, PAGE_SIZE};
-use crate::paging::{ActivePageTable, VirtualAddress};
+use crate::paging::{ActivePageTable, PageTableType, VirtualAddress, VirtualAddressType};
 use crate::syscall::data::{Map, OldMap, StatVfs};
 use crate::syscall::error::*;
 use crate::syscall::flag::MapFlags;
@@ -48,7 +48,10 @@ impl Scheme for MemoryScheme {
                 // Make sure it's *absolutely* not mapped already
                 // TODO: Keep track of all allocated memory so this isn't necessary
 
-                let active_table = unsafe { ActivePageTable::new() };
+                let active_table = match VirtualAddress::new(map.address).get_type() {
+                    VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+                    VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+                };
 
                 for page in region.pages() {
                     if active_table.translate_page(page).is_some() {
diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs
index ceb628dd97a9b4f6f4fa1100f505977b9ba025df..c07771b91df36304f86b8ac24018b248e27dacf4 100644
--- a/src/scheme/proc.rs
+++ b/src/scheme/proc.rs
@@ -335,6 +335,13 @@ impl Scheme for ProcScheme {
         Ok(value)
     }
 
+    #[cfg(target_arch = "aarch64")]
+    fn read(&self, id: usize, buf: &mut [u8]) -> Result<usize> {
+        //TODO
+        Err(Error::new(EINVAL))
+    }
+
+    #[cfg(target_arch = "x86_64")]
     fn read(&self, id: usize, buf: &mut [u8]) -> Result<usize> {
         // Don't hold a global lock during the context switch later on
         let info = {
@@ -455,6 +462,13 @@ impl Scheme for ProcScheme {
         }
     }
 
+    #[cfg(target_arch = "aarch64")]
+    fn write(&self, id: usize, buf: &[u8]) -> Result<usize> {
+        //TODO
+        Err(Error::new(EINVAL))
+    }
+
+    #[cfg(target_arch = "x86_64")]
     fn write(&self, id: usize, buf: &[u8]) -> Result<usize> {
         // Don't hold a global lock during the context switch later on
         let info = {
diff --git a/src/scheme/sys/mod.rs b/src/scheme/sys/mod.rs
index 4d6a08c54c5cd506b3f854c85b5ba8f3aa24757b..da6576cf917b912dbdd0a6bc1e9c71a2997a1ea8 100644
--- a/src/scheme/sys/mod.rs
+++ b/src/scheme/sys/mod.rs
@@ -52,6 +52,7 @@ impl SysScheme {
         files.insert("scheme_num", Box::new(scheme_num::resource));
         files.insert("syscall", Box::new(syscall::resource));
         files.insert("uname", Box::new(uname::resource));
+        #[cfg(target_arch = "x86_64")]
         files.insert("spurious_irq", Box::new(irq::spurious_irq_resource));
 
         SysScheme {
diff --git a/src/scheme/user.rs b/src/scheme/user.rs
index 7959413ffc12b16d85ad3202b1352700debdc726..2dd6713aedc26f9fdeeeed2ce45890212aca7a54 100644
--- a/src/scheme/user.rs
+++ b/src/scheme/user.rs
@@ -148,7 +148,7 @@ impl UserInner {
         let context_lock = context_weak.upgrade().ok_or(Error::new(ESRCH))?;
         let mut context = context_lock.write();
 
-        let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_table()) };
+        let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_utable()) };
         let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(crate::USER_TMP_GRANT_OFFSET)));
 
         let mut grants = context.grants.write();
@@ -179,7 +179,7 @@ impl UserInner {
             let context_lock = self.context.upgrade().ok_or(Error::new(ESRCH))?;
             let mut context = context_lock.write();
 
-            let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_table()) };
+            let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_utable()) };
             let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(crate::USER_TMP_GRANT_OFFSET)));
 
             let mut grants = context.grants.write();
diff --git a/src/syscall/driver.rs b/src/syscall/driver.rs
index 9d73f1bd5796b0b74c096988e5d94c5d5684b491..f943f639aed278bc14abb3e6d94a9370e9e26df0 100644
--- a/src/syscall/driver.rs
+++ b/src/syscall/driver.rs
@@ -1,6 +1,6 @@
 use crate::interrupt::InterruptStack;
 use crate::memory::{allocate_frames_complex, deallocate_frames, Frame};
-use crate::paging::{ActivePageTable, PageFlags, PhysicalAddress, VirtualAddress};
+use crate::paging::{ActivePageTable, PageFlags, PageTableType, PhysicalAddress, VirtualAddress};
 use crate::paging::entry::EntryFlags;
 use crate::context;
 use crate::context::memory::{Grant, Region};
@@ -18,6 +18,12 @@ fn enforce_root() -> Result<()> {
     }
 }
 
+#[cfg(target_arch = "aarch64")]
+pub fn iopl(level: usize, stack: &mut InterruptStack) -> Result<usize> {
+    Err(Error::new(syscall::error::ENOSYS))
+}
+
+#[cfg(target_arch = "x86_64")]
 pub fn iopl(level: usize, stack: &mut InterruptStack) -> Result<usize> {
     enforce_root()?;
 
@@ -88,6 +94,7 @@ pub fn inner_physmap(physical_address: usize, size: usize, flags: PhysmapFlags)
         if flags.contains(PHYSMAP_WRITE_COMBINE) {
             page_flags = page_flags.custom_flag(EntryFlags::HUGE_PAGE.bits(), true);
         }
+        #[cfg(target_arch = "x86_64")] // TODO: AARCH64
         if flags.contains(PHYSMAP_NO_CACHE) {
             page_flags = page_flags.custom_flag(EntryFlags::NO_CACHE.bits(), true);
         }
@@ -146,7 +153,11 @@ pub fn physunmap(virtual_address: usize) -> Result<usize> {
 pub fn virttophys(virtual_address: usize) -> Result<usize> {
     enforce_root()?;
 
-    let active_table = unsafe { ActivePageTable::new() };
+    let active_table = match VirtualAddress::new(virtual_address).get_type() {
+        VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+        VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+    };
+
     match active_table.translate(VirtualAddress::new(virtual_address)) {
         Some(physical_address) => Ok(physical_address.data()),
         None => Err(Error::new(EFAULT))
diff --git a/src/syscall/mod.rs b/src/syscall/mod.rs
index 9fd569954e30d1ce63abf6d6191f85026bbe339d..2287e252a579a36e20294284b80615f5e30e663f 100644
--- a/src/syscall/mod.rs
+++ b/src/syscall/mod.rs
@@ -123,13 +123,24 @@ pub fn syscall(a: usize, b: usize, c: usize, d: usize, e: usize, f: usize, bp: u
                 SYS_GETPPID => getppid().map(ContextId::into),
                 SYS_CLONE => {
                     let b = CloneFlags::from_bits_truncate(b);
-                    let old_rsp = stack.iret.rsp;
-                    if b.contains(flag::CLONE_STACK) {
-                        stack.iret.rsp = c;
+
+                    #[cfg(target_arch = "aarch64")]
+                    {
+                        //TODO: CLONE_STACK
+                        let ret = clone(b, bp).map(ContextId::into);
+                        ret
+                    }
+
+                    #[cfg(target_arch = "x86_64")]
+                    {
+                        let old_rsp = stack.iret.rsp;
+                        if b.contains(flag::CLONE_STACK) {
+                            stack.iret.rsp = c;
+                        }
+                        let ret = clone(b, bp).map(ContextId::into);
+                        stack.iret.rsp = old_rsp;
+                        ret
                     }
-                    let ret = clone(b, bp).map(ContextId::into);
-                    stack.iret.rsp = old_rsp;
-                    ret
                 },
                 SYS_EXIT => exit((b & 0xFF) << 8),
                 SYS_KILL => kill(ContextId::from(b), c),
diff --git a/src/syscall/process.rs b/src/syscall/process.rs
index 588b4ca039c2ab0c55ec528036f318a0b4a0d58a..daefc0996c45507a9bfac42a8274641e2f2d4361 100644
--- a/src/syscall/process.rs
+++ b/src/syscall/process.rs
@@ -21,7 +21,7 @@ use crate::ipi::{ipi, IpiKind, IpiTarget};
 use crate::memory::allocate_frames;
 use crate::paging::mapper::PageFlushAll;
 use crate::paging::temporary_page::TemporaryPage;
-use crate::paging::{ActivePageTable, InactivePageTable, Page, PageFlags, VirtualAddress, PAGE_SIZE};
+use crate::paging::{ActivePageTable, InactivePageTable, Page, PageFlags, PageTableType, VirtualAddress, PAGE_SIZE};
 use crate::{ptrace, syscall};
 use crate::scheme::FileHandle;
 use crate::start::usermode;
@@ -89,36 +89,49 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
             arch = context.arch.clone();
 
             if let Some(ref fx) = context.kfx {
-                let mut new_fx = unsafe { Box::from_raw(crate::ALLOCATOR.alloc(Layout::from_size_align_unchecked(512, 16)) as *mut [u8; 512]) };
+                let mut new_fx = unsafe { Box::from_raw(crate::ALLOCATOR.alloc(Layout::from_size_align_unchecked(1024, 16)) as *mut [u8; 1024]) };
                 for (new_b, b) in new_fx.iter_mut().zip(fx.iter()) {
                     *new_b = *b;
                 }
                 kfx_opt = Some(new_fx);
             }
 
-            if let Some(ref stack) = context.kstack {
-                // Get the relative offset to the return address of the function
-                // obtaining `stack_base`.
-                //
-                // (base pointer - start of stack) - one
-                offset = stack_base - stack.as_ptr() as usize - mem::size_of::<usize>(); // Add clone ret
-                let mut new_stack = stack.clone();
+            #[cfg(target_arch = "x86_64")]
+            {
+                if let Some(ref stack) = context.kstack {
+                    // Get the relative offset to the return address of the function
+                    // obtaining `stack_base`.
+                    //
+                    // (base pointer - start of stack) - one
+                    offset = stack_base - stack.as_ptr() as usize - mem::size_of::<usize>(); // Add clone ret
+                    let mut new_stack = stack.clone();
 
-                unsafe {
-                    // Set clone's return value to zero. This is done because
-                    // the clone won't return like normal, which means the value
-                    // would otherwise never get set.
-                    if let Some(regs) = ptrace::rebase_regs_ptr_mut(context.regs, Some(&mut new_stack)) {
-                        (*regs).scratch.rax = 0;
+                    unsafe {
+                        // Set clone's return value to zero. This is done because
+                        // the clone won't return like normal, which means the value
+                        // would otherwise never get set.
+                        if let Some(regs) = ptrace::rebase_regs_ptr_mut(context.regs, Some(&mut new_stack)) {
+                            (*regs).scratch.rax = 0;
+                        }
+
+                        // Change the return address of the child (previously
+                        // syscall) to the arch-specific clone_ret callback
+                        let func_ptr = new_stack.as_mut_ptr().add(offset);
+                        *(func_ptr as *mut usize) = interrupt::syscall::clone_ret as usize;
                     }
 
-                    // Change the return address of the child (previously
-                    // syscall) to the arch-specific clone_ret callback
-                    let func_ptr = new_stack.as_mut_ptr().add(offset);
-                    *(func_ptr as *mut usize) = interrupt::syscall::clone_ret as usize;
+                    kstack_opt = Some(new_stack);
                 }
+            }
 
-                kstack_opt = Some(new_stack);
+            #[cfg(target_arch = "aarch64")]
+            {
+                if let Some(ref stack) = context.kstack {
+                    offset = stack_base - stack.as_ptr() as usize;
+                    let mut new_stack = stack.clone();
+
+                    kstack_opt = Some(new_stack);
+                }
             }
 
             if flags.contains(CLONE_VM) {
@@ -339,31 +352,47 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
 
             context.arch = arch;
 
-            let mut active_table = unsafe { ActivePageTable::new() };
+            let mut active_utable = unsafe { ActivePageTable::new(PageTableType::User) };
+            let mut active_ktable = unsafe { ActivePageTable::new(PageTableType::Kernel) };
 
-            let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(crate::USER_TMP_MISC_OFFSET)));
+            let mut temporary_upage = TemporaryPage::new(Page::containing_address(VirtualAddress::new(crate::USER_TMP_MISC_OFFSET)));
+            let mut temporary_kpage = TemporaryPage::new(Page::containing_address(VirtualAddress::new(crate::KERNEL_TMP_MISC_OFFSET)));
 
-            let mut new_table = {
+            let mut new_utable = {
                 let frame = allocate_frames(1).expect("no more frames in syscall::clone new_table");
-                InactivePageTable::new(frame, &mut active_table, &mut temporary_page)
+                InactivePageTable::new(frame, &mut active_utable, &mut temporary_upage)
+            };
+            context.arch.set_page_utable(unsafe { new_utable.address() });
+
+            #[cfg(target_arch = "aarch64")]
+            let mut new_ktable = {
+                let mut new_ktable = {
+                    let frame = allocate_frames(1).expect("no more frames in syscall::clone new_table");
+                    InactivePageTable::new(frame, &mut active_ktable, &mut temporary_kpage)
+                };
+                context.arch.set_page_ktable(unsafe { new_ktable.address() });
+                new_ktable
             };
 
-            context.arch.set_page_table(unsafe { new_table.address() });
+            #[cfg(target_arch = "x86_64")]
+            let mut new_ktable = unsafe {
+                InactivePageTable::from_address(new_utable.address())
+            };
 
             // Copy kernel image mapping
             {
-                let frame = active_table.p4()[crate::KERNEL_PML4].pointed_frame().expect("kernel image not mapped");
-                let flags = active_table.p4()[crate::KERNEL_PML4].flags();
-                active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+                let frame = active_ktable.p4()[crate::KERNEL_PML4].pointed_frame().expect("kernel image not mapped");
+                let flags = active_ktable.p4()[crate::KERNEL_PML4].flags();
+                active_ktable.with(&mut new_ktable, &mut temporary_kpage, |mapper| {
                     mapper.p4_mut()[crate::KERNEL_PML4].set(frame, flags);
                 });
             }
 
             // Copy kernel heap mapping
             {
-                let frame = active_table.p4()[crate::KERNEL_HEAP_PML4].pointed_frame().expect("kernel heap not mapped");
-                let flags = active_table.p4()[crate::KERNEL_HEAP_PML4].flags();
-                active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+                let frame = active_ktable.p4()[crate::KERNEL_HEAP_PML4].pointed_frame().expect("kernel heap not mapped");
+                let flags = active_ktable.p4()[crate::KERNEL_HEAP_PML4].flags();
+                active_ktable.with(&mut new_ktable, &mut temporary_kpage, |mapper| {
                     mapper.p4_mut()[crate::KERNEL_HEAP_PML4].set(frame, flags);
                 });
             }
@@ -386,6 +415,10 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
             if let Some(stack) = kstack_opt.take() {
                 context.arch.set_stack(stack.as_ptr() as usize + offset);
                 context.kstack = Some(stack);
+                #[cfg(target_arch = "aarch64")]
+                {
+                    context.arch.set_lr(interrupt::syscall::clone_ret as usize);
+                }
             }
 
             // TODO: Clone ksig?
@@ -394,9 +427,9 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
             if flags.contains(CLONE_VM) {
                 // Copy user image mapping, if found
                 if ! image.is_empty() {
-                    let frame = active_table.p4()[crate::USER_PML4].pointed_frame().expect("user image not mapped");
-                    let flags = active_table.p4()[crate::USER_PML4].flags();
-                    active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+                    let frame = active_utable.p4()[crate::USER_PML4].pointed_frame().expect("user image not mapped");
+                    let flags = active_utable.p4()[crate::USER_PML4].flags();
+                    active_utable.with(&mut new_utable, &mut temporary_upage, |mapper| {
                         mapper.p4_mut()[crate::USER_PML4].set(frame, flags);
                     });
                 }
@@ -404,9 +437,9 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
 
                 // Copy grant mapping
                 if ! grants.read().is_empty() {
-                    let frame = active_table.p4()[crate::USER_GRANT_PML4].pointed_frame().expect("user grants not mapped");
-                    let flags = active_table.p4()[crate::USER_GRANT_PML4].flags();
-                    active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+                    let frame = active_utable.p4()[crate::USER_GRANT_PML4].pointed_frame().expect("user grants not mapped");
+                    let flags = active_utable.p4()[crate::USER_GRANT_PML4].flags();
+                    active_utable.with(&mut new_utable, &mut temporary_upage, |mapper| {
                         mapper.p4_mut()[crate::USER_GRANT_PML4].set(frame, flags);
                     });
                 }
@@ -429,8 +462,8 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
                     let start_page = Page::containing_address(VirtualAddress::new(start));
                     let end_page = Page::containing_address(VirtualAddress::new(end - 1));
                     for page in Page::range_inclusive(start_page, end_page) {
-                        let frame = active_table.translate_page(page).expect("kernel percpu not mapped");
-                        active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+                        let frame = active_ktable.translate_page(page).expect("kernel percpu not mapped");
+                        active_ktable.with(&mut new_ktable, &mut temporary_kpage, |mapper| {
                             let result = mapper.map_to(page, frame, PageFlags::new().write(true));
                             // Ignore result due to operating on inactive table
                             unsafe { result.ignore(); }
@@ -442,7 +475,7 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
                 for memory_shared in image.iter_mut() {
                     memory_shared.with(|memory| {
                         let start = VirtualAddress::new(memory.start_address().data() - crate::USER_TMP_OFFSET + crate::USER_OFFSET);
-                        memory.move_to(start, &mut new_table, &mut temporary_page);
+                        memory.move_to(start, &mut new_utable, &mut temporary_upage);
                     });
                 }
                 context.image = image;
@@ -454,7 +487,7 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
 
                     for mut grant in old_grants.inner.into_iter() {
                         let start = VirtualAddress::new(grant.start_address().data() + crate::USER_GRANT_OFFSET - crate::USER_TMP_GRANT_OFFSET);
-                        grant.move_to(start, &mut new_table, &mut temporary_page);
+                        grant.move_to(start, &mut new_utable, &mut temporary_upage);
                         grants.insert(grant);
                     }
                 }
@@ -464,14 +497,14 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
             // Setup user stack
             if let Some(stack_shared) = stack_opt {
                 if flags.contains(CLONE_STACK) {
-                    let frame = active_table.p4()[crate::USER_STACK_PML4].pointed_frame().expect("user stack not mapped");
-                    let flags = active_table.p4()[crate::USER_STACK_PML4].flags();
-                    active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+                    let frame = active_utable.p4()[crate::USER_STACK_PML4].pointed_frame().expect("user stack not mapped");
+                    let flags = active_utable.p4()[crate::USER_STACK_PML4].flags();
+                    active_utable.with(&mut new_utable, &mut temporary_upage, |mapper| {
                         mapper.p4_mut()[crate::USER_STACK_PML4].set(frame, flags);
                     });
                 } else {
                     stack_shared.with(|stack| {
-                        stack.move_to(VirtualAddress::new(crate::USER_STACK_OFFSET), &mut new_table, &mut temporary_page);
+                        stack.move_to(VirtualAddress::new(crate::USER_STACK_OFFSET), &mut new_utable, &mut temporary_upage);
                     });
                 }
                 context.stack = Some(stack_shared);
@@ -479,7 +512,7 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
 
             // Setup user sigstack
             if let Some(mut sigstack) = sigstack_opt {
-                sigstack.move_to(VirtualAddress::new(crate::USER_SIGSTACK_OFFSET), &mut new_table, &mut temporary_page);
+                sigstack.move_to(VirtualAddress::new(crate::USER_SIGSTACK_OFFSET), &mut new_utable, &mut temporary_upage);
                 context.sigstack = Some(sigstack);
             }
 
@@ -492,13 +525,36 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
                 true
             );
 
+            #[cfg(target_arch = "aarch64")]
+            {
+                if let Some(stack) = &mut context.kstack {
+                    unsafe {
+                        // stack_base contains a pointer to InterruptStack. Get its offset from
+                        // stack_base itself
+                        let istack_offset = *(stack_base as *const u64) - stack_base as u64;
+
+                        // Get the top of the new process' stack
+                        let new_sp = stack.as_mut_ptr().add(offset);
+
+                        // Update the pointer to the InterruptStack to reflect the new process'
+                        // stack. (Without this the pointer would be InterruptStack on the parent
+                        // process' stack).
+                        *(new_sp as *mut u64) = new_sp as u64 + istack_offset;
+
+                        // Update tpidr_el0 in the new process' InterruptStack
+                        let mut interrupt_stack = &mut *(stack.as_mut_ptr().add(offset + istack_offset as usize) as *mut crate::arch::interrupt::InterruptStack);
+                        interrupt_stack.iret.tpidr_el0 = tcb_addr;
+                    }
+                }
+            }
+
             // Setup user TLS
             if let Some(mut tls) = tls_opt {
                 // Copy TLS mapping
                 {
-                    let frame = active_table.p4()[crate::USER_TLS_PML4].pointed_frame().expect("user tls not mapped");
-                    let flags = active_table.p4()[crate::USER_TLS_PML4].flags();
-                    active_table.with(&mut new_table, &mut temporary_page, |mapper| {
+                    let frame = active_utable.p4()[crate::USER_TLS_PML4].pointed_frame().expect("user tls not mapped");
+                    let flags = active_utable.p4()[crate::USER_TLS_PML4].flags();
+                    active_utable.with(&mut new_utable, &mut temporary_upage, |mapper| {
                         mapper.p4_mut()[crate::USER_TLS_PML4].set(frame, flags);
                     });
                 }
@@ -506,7 +562,7 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
                 // TODO: Make sure size is not greater than USER_TLS_SIZE
                 let tls_addr = crate::USER_TLS_OFFSET + context.id.into() * crate::USER_TLS_SIZE;
                 //println!("{}: Copy TLS: address 0x{:x}, size 0x{:x}", context.id.into(), tls_addr, tls.mem.size());
-                tls.mem.move_to(VirtualAddress::new(tls_addr), &mut new_table, &mut temporary_page);
+                tls.mem.move_to(VirtualAddress::new(tls_addr), &mut new_utable, &mut temporary_upage);
                 unsafe {
                     *(tcb_addr as *mut usize) = tls.mem.start_address().data() + tls.mem.size();
                 }
@@ -521,7 +577,7 @@ pub fn clone(flags: CloneFlags, stack_base: usize) -> Result<ContextId> {
                 }
             }
 
-            tcb.move_to(VirtualAddress::new(tcb_addr), &mut new_table, &mut temporary_page);
+            tcb.move_to(VirtualAddress::new(tcb_addr), &mut new_utable, &mut temporary_upage);
             context.image.push(tcb.to_shared());
 
             context.name = name;
@@ -590,7 +646,7 @@ fn empty(context: &mut context::Context, reaping: bool) {
             if reaping {
                 log::error!("{}: {}: Grant should not exist: {:?}", context.id.into(), *context.name.read(), grant);
 
-                let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_table()) };
+                let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_utable()) };
                 let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(crate::USER_TMP_GRANT_OFFSET)));
 
                 grant.unmap_inactive(&mut new_table, &mut temporary_page);
@@ -1309,7 +1365,7 @@ pub fn mprotect(address: usize, size: usize, flags: MapFlags) -> Result<usize> {
     let end_offset = size.checked_sub(1).ok_or(Error::new(EFAULT))?;
     let end_address = address.checked_add(end_offset).ok_or(Error::new(EFAULT))?;
 
-    let mut active_table = unsafe { ActivePageTable::new() };
+    let mut active_table = unsafe { ActivePageTable::new(PageTableType::User) };
 
     let flush_all = PageFlushAll::new();
 
diff --git a/src/syscall/validate.rs b/src/syscall/validate.rs
index 6ae35024290caeccb5c1e0ce2a15a8ec547d4d59..33f0b86f423806a1ec6c2485a4e56330f60e9d79 100644
--- a/src/syscall/validate.rs
+++ b/src/syscall/validate.rs
@@ -1,13 +1,16 @@
 use core::{mem, slice, str};
 
-use crate::paging::{ActivePageTable, Page, VirtualAddress};
+use crate::paging::{ActivePageTable, Page, PageTableType, VirtualAddress};
 use crate::syscall::error::*;
 
 fn validate(address: usize, size: usize, writable: bool) -> Result<()> {
     let end_offset = size.checked_sub(1).ok_or(Error::new(EFAULT))?;
     let end_address = address.checked_add(end_offset).ok_or(Error::new(EFAULT))?;
 
-    let active_table = unsafe { ActivePageTable::new() };
+    let active_table = match VirtualAddress::new(address).get_type() {
+        VirtualAddressType::User => unsafe { ActivePageTable::new(PageTableType::User) },
+        VirtualAddressType::Kernel => unsafe { ActivePageTable::new(PageTableType::Kernel) }
+    };
 
     let start_page = Page::containing_address(VirtualAddress::new(address));
     let end_page = Page::containing_address(VirtualAddress::new(end_address));
diff --git a/targets/aarch64-unknown-none.json b/targets/aarch64-unknown-none.json
index d36ded358ed176d8bf5a0346952f4e995525ec88..f4abc23f01da388671e8741e4f26f0de8fe73639 100644
--- a/targets/aarch64-unknown-none.json
+++ b/targets/aarch64-unknown-none.json
@@ -13,11 +13,10 @@
     "pre-link-args": {
         "gcc": ["-m64", "-nostdlib", "-static"]
     },
-    "features": "+a53,+strict-align,-fp-armv8",
+    "features": "+strict-align,-neon,-fp-armv8,+tpidr-el1",
     "dynamic-linking": false,
     "executables": false,
     "relocation-model": "pic",
-    "code-model": "large",
     "disable-redzone": true,
     "eliminate-frame-pointer": false,
     "exe-suffix": "",