Bläddra i källkod

Merge pull request #6 from greatbridf/remove-cpp

Refactor and optimize trap handling and PCIe driver
greatbridf 8 månader sedan
förälder
incheckning
2b8042afc2
100 ändrade filer med 3901 tillägg och 1995 borttagningar
  1. 0 29
      .clang-format
  2. 0 2
      .gitignore
  3. 2 2
      .vscode/launch.json
  4. 0 1
      .vscode/settings.json
  5. 2 2
      .vscode/tasks.json
  6. 0 95
      CMakeLists.txt
  7. 123 227
      Cargo.lock
  8. 7 6
      Cargo.toml
  9. 66 40
      Makefile.src
  10. 19 14
      README.md
  11. 1 1
      arch/Cargo.toml
  12. 9 0
      arch/arch_macros/Cargo.toml
  13. 1 0
      arch/arch_macros/src/lib.rs
  14. 1 0
      arch/arch_macros/src/x86_64/mod.rs
  15. 32 0
      arch/arch_macros/src/x86_64/percpu.rs
  16. 0 47
      arch/percpu-macros/Cargo.lock
  17. 0 24
      arch/percpu-macros/src/arch.rs
  18. 0 1
      arch/src/lib.rs
  19. 32 0
      arch/src/x86_64/fpu.rs
  20. 0 555
      arch/src/x86_64/interrupt.rs
  21. 0 17
      arch/src/x86_64/io.rs
  22. 0 202
      arch/src/x86_64/mm.rs
  23. 2 23
      arch/src/x86_64/mod.rs
  24. 0 86
      arch/src/x86_64/percpu.rs
  25. 0 54
      arch/src/x86_64/user.rs
  26. 8 26
      build.rs
  27. 39 90
      configure
  28. 25 2
      crates/buddy_allocator/src/zone.rs
  29. 18 0
      crates/eonix_hal/Cargo.toml
  30. 49 0
      crates/eonix_hal/build.rs
  31. 12 0
      crates/eonix_hal/eonix_hal_macros/Cargo.toml
  32. 165 0
      crates/eonix_hal/eonix_hal_macros/src/lib.rs
  33. 8 0
      crates/eonix_hal/eonix_hal_traits/Cargo.toml
  34. 41 0
      crates/eonix_hal/eonix_hal_traits/src/context.rs
  35. 20 0
      crates/eonix_hal/eonix_hal_traits/src/fault.rs
  36. 6 0
      crates/eonix_hal/eonix_hal_traits/src/fpu.rs
  37. 9 0
      crates/eonix_hal/eonix_hal_traits/src/lib.rs
  38. 6 0
      crates/eonix_hal/eonix_hal_traits/src/mm.rs
  39. 7 0
      crates/eonix_hal/eonix_hal_traits/src/processor.rs
  40. 95 0
      crates/eonix_hal/eonix_hal_traits/src/trap.rs
  41. 14 0
      crates/eonix_hal/src/arch/mod.rs
  42. 516 0
      crates/eonix_hal/src/arch/x86_64/bootstrap.rs
  43. 374 0
      crates/eonix_hal/src/arch/x86_64/bootstrap/init.rs
  44. 46 77
      crates/eonix_hal/src/arch/x86_64/context.rs
  45. 132 60
      crates/eonix_hal/src/arch/x86_64/cpu.rs
  46. 28 23
      crates/eonix_hal/src/arch/x86_64/gdt.rs
  47. 196 0
      crates/eonix_hal/src/arch/x86_64/interrupt.rs
  48. 98 0
      crates/eonix_hal/src/arch/x86_64/link.x
  49. 11 0
      crates/eonix_hal/src/arch/x86_64/memory.x
  50. 375 0
      crates/eonix_hal/src/arch/x86_64/mm.rs
  51. 7 0
      crates/eonix_hal/src/arch/x86_64/mod.rs
  52. 390 0
      crates/eonix_hal/src/arch/x86_64/trap.rs
  53. 130 0
      crates/eonix_hal/src/arch/x86_64/trap/trap_context.rs
  54. 22 0
      crates/eonix_hal/src/bootstrap.rs
  55. 1 0
      crates/eonix_hal/src/context.rs
  56. 14 0
      crates/eonix_hal/src/lib.rs
  57. 114 0
      crates/eonix_hal/src/link.x.in
  58. 198 0
      crates/eonix_hal/src/mm.rs
  59. 1 0
      crates/eonix_hal/src/processor.rs
  60. 5 0
      crates/eonix_hal/src/trap.rs
  61. 1 1
      crates/eonix_mm/src/address.rs
  62. 2 0
      crates/eonix_mm/src/address/addr.rs
  63. 24 0
      crates/eonix_mm/src/address/paddr.rs
  64. 1 0
      crates/eonix_mm/src/lib.rs
  65. 54 22
      crates/eonix_mm/src/page_table/page_table.rs
  66. 1 5
      crates/eonix_mm/src/page_table/paging_mode.rs
  67. 4 13
      crates/eonix_mm/src/page_table/pte.rs
  68. 1 1
      crates/eonix_mm/src/page_table/pte_iterator.rs
  69. 2 2
      crates/eonix_mm/src/paging.rs
  70. 17 3
      crates/eonix_mm/src/paging/page.rs
  71. 28 1
      crates/eonix_mm/src/paging/page_alloc.rs
  72. 41 0
      crates/eonix_mm/src/paging/raw_page.rs
  73. 1 6
      crates/eonix_percpu/Cargo.toml
  74. 14 0
      crates/eonix_percpu/eonix_percpu_macros/Cargo.toml
  75. 42 13
      crates/eonix_percpu/eonix_percpu_macros/src/lib.rs
  76. 0 24
      crates/eonix_percpu/src/arch.rs
  77. 58 160
      crates/eonix_percpu/src/lib.rs
  78. 1 1
      crates/eonix_preempt/Cargo.toml
  79. 42 11
      crates/eonix_preempt/src/lib.rs
  80. 2 0
      crates/eonix_runtime/Cargo.toml
  81. 10 10
      crates/eonix_runtime/src/context.rs
  82. 1 1
      crates/eonix_runtime/src/ready_queue.rs
  83. 3 2
      crates/eonix_runtime/src/scheduler.rs
  84. 6 7
      crates/eonix_sync/Cargo.toml
  85. 8 0
      crates/eonix_sync/eonix_spin/Cargo.toml
  86. 1 1
      crates/eonix_sync/eonix_spin/src/guard.rs
  87. 11 4
      crates/eonix_sync/eonix_spin/src/lib.rs
  88. 10 0
      crates/eonix_sync/eonix_sync_base/Cargo.toml
  89. 0 0
      crates/eonix_sync/eonix_sync_base/src/guard.rs
  90. 0 0
      crates/eonix_sync/eonix_sync_base/src/lazy_lock.rs
  91. 13 0
      crates/eonix_sync/eonix_sync_base/src/lib.rs
  92. 0 0
      crates/eonix_sync/eonix_sync_base/src/locked.rs
  93. 0 0
      crates/eonix_sync/eonix_sync_base/src/locked/proof.rs
  94. 0 0
      crates/eonix_sync/eonix_sync_base/src/marker.rs
  95. 0 0
      crates/eonix_sync/eonix_sync_base/src/relax.rs
  96. 13 0
      crates/eonix_sync/eonix_sync_rt/Cargo.toml
  97. 11 0
      crates/eonix_sync/eonix_sync_rt/src/lib.rs
  98. 0 0
      crates/eonix_sync/eonix_sync_rt/src/mutex.rs
  99. 1 1
      crates/eonix_sync/eonix_sync_rt/src/mutex/guard.rs
  100. 0 0
      crates/eonix_sync/eonix_sync_rt/src/rwlock.rs

+ 0 - 29
.clang-format

@@ -1,29 +0,0 @@
----
-BasedOnStyle: Google
-AllowShortBlocksOnASingleLine: 'false'
-AllowShortCaseLabelsOnASingleLine: 'false'
-AllowShortFunctionsOnASingleLine: Inline
-AllowShortIfStatementsOnASingleLine: Never
-AllowShortLoopsOnASingleLine: 'false'
-BreakConstructorInitializers: BeforeComma
-ColumnLimit: '100'
-FixNamespaceComments: 'true'
-IncludeBlocks: Regroup
-IndentWidth: '4'
-NamespaceIndentation: Inner
-SpacesBeforeTrailingComments: '1'
-Language: Cpp
-Standard: Cpp11
-IncludeCategories:
-  - Regex: '^<types/'
-    Priority: '4'
-  - Regex: '^<(kernel|fs|net|driver)/'
-    Priority: '5'
-  - Regex: '^<.*\.h>'
-    Priority: '3'
-  - Regex: '^<.*>'
-    Priority: '2'
-  - Regex: '.*'
-    Priority: '1'
-
-...

+ 0 - 2
.gitignore

@@ -19,5 +19,3 @@ cross-compile.cmake
 .Trashes
 
 .gdbinit
-
-src/bindings.rs

+ 2 - 2
.vscode/launch.json

@@ -4,7 +4,7 @@
             "type": "cppdbg",
             "request": "launch",
             "name": "Launch Kernel",
-            "program": "${workspaceFolder}/build/kernel.out",
+            "program": "${workspaceFolder}/build/kernel.sym",
             "args": [],
             "stopAtEntry": false,
             "cwd": "${workspaceFolder}",
@@ -37,7 +37,7 @@
             "type": "cppdbg",
             "request": "launch",
             "name": "Attach Kernel",
-            "program": "${workspaceFolder}/build/kernel.out",
+            "program": "${workspaceFolder}/build/kernel.sym",
             "args": [],
             "stopAtEntry": false,
             "cwd": "${workspaceFolder}",

+ 0 - 1
.vscode/settings.json

@@ -1,4 +1,3 @@
 {
     "makefile.configureOnOpen": false,
-    "rust-analyzer.check.allTargets": false,
 }

+ 2 - 2
.vscode/tasks.json

@@ -10,7 +10,7 @@
             "isBackground": true,
             "problemMatcher": [
                 {
-                    "owner": "cpp",
+                    "owner": "rustc",
                     "fileLocation": [
                         "relative",
                         "${workspaceFolder}"
@@ -25,7 +25,7 @@
                     },
                     "background": {
                         "activeOnStart": true,
-                        "beginsPattern": "cmake --build",
+                        "beginsPattern": "cargo build",
                         "endsPattern": "qemu"
                     }
                 }

+ 0 - 95
CMakeLists.txt

@@ -1,95 +0,0 @@
-cmake_minimum_required(VERSION 3.15)
-project(kernel_main C CXX ASM)
-
-set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-
-set(CMAKE_CXX_LINK_EXECUTABLE
-    "<CMAKE_LINKER> <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
-
-set(C_CXX_FLAGS "-nostdinc -nostdlib -mno-sse -mno-mmx -W -Wall -Wextra -Wno-stringop-overflow -Wno-builtin-declaration-mismatch -Wno-format -fverbose-asm -fno-exceptions -ffreestanding -fno-pic -mno-red-zone -mstack-protector-guard=global -mcmodel=kernel")
-set(CMAKE_C_FLAGS "${C_CXX_FLAGS} -Werror=implicit-int -Werror=implicit-function-declaration -Werror=strict-aliasing")
-set(CMAKE_CXX_FLAGS "${C_CXX_FLAGS} -fno-use-cxa-atexit -fno-rtti")
-set(CMAKE_CXX_LINK_FLAGS "-z noexecstack --gc-sections")
-SET(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
-set(CMAKE_CXX_STANDARD 20)
-
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEBUG -O0 -g")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG -O0 -g")
-    set(CARGO_BUILD_TYPE "debug")
-    set(CARGO_BUILD_FLAGS "")
-elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -g -DNDEBUG")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -g -DNDEBUG")
-    set(CARGO_BUILD_TYPE "release")
-    set(CARGO_BUILD_FLAGS "--release")
-endif()
-
-if (NOT DEFINED FDISK_BIN)
-    set(FDISK_BIN fdisk)
-endif()
-
-add_subdirectory(gblibc)
-add_subdirectory(gblibstdc++)
-add_subdirectory(user-space-program)
-
-set(BOOTLOADER_SOURCES src/boot.s
-                       src/mbr.S
-                       )
-
-set(KERNEL_MAIN_SOURCES src/kinit.cpp
-                        src/kernel/async/lock.cc
-                        src/kernel/allocator.cc
-                        src/kernel/mem/slab.cc
-                        src/kernel/hw/acpi.cc
-                        src/kernel/hw/pci.cc
-                        src/types/libstdcpp.cpp
-                        include/defs.hpp
-                        include/kernel/async/lock.hpp
-                        include/kernel/mem/paging.hpp
-                        include/kernel/mem/slab.hpp
-                        include/kernel/mem/types.hpp
-                        include/kernel/utsname.hpp
-                        include/kernel/hw/acpi.hpp
-                        include/kernel/hw/pci.hpp
-                        include/kernel/hw/port.hpp
-                        include/types/list.hpp
-                        include/types/types.h
-                        include/types/allocator.hpp
-                        include/kernel/log.hpp
-                        )
-
-add_executable(kernel.out ${KERNEL_MAIN_SOURCES} ${BOOTLOADER_SOURCES})
-add_dependencies(kernel.out rustpart)
-target_link_libraries(kernel.out gblibc gblibstdc++ gbos_rust_part)
-target_include_directories(kernel.out PRIVATE ${PROJECT_SOURCE_DIR}/include)
-target_link_options(kernel.out PRIVATE
-    -T "${CMAKE_SOURCE_DIR}/src/kernel.ld"
-    -L "${CMAKE_BINARY_DIR}/x86_64-unknown-none/${CARGO_BUILD_TYPE}"
-    --no-check-sections
-    )
-set_target_properties(kernel.out PROPERTIES LINK_DEPENDS "${CMAKE_SOURCE_DIR}/src/kernel.ld")
-set_source_files_properties(src/mbr.S PROPERTIES OBJECT_DEPENDS
-    "${CMAKE_BINARY_DIR}/x86_64-unknown-none/${CARGO_BUILD_TYPE}/libgbos_rust_part.a"
-    )
-
-add_custom_target(rustpart
-    COMMAND cargo build ${CARGO_BUILD_FLAGS}
-)
-
-add_custom_command(OUTPUT mbr_hole.bin
-    DEPENDS kernel.out
-    COMMAND ${CMAKE_OBJCOPY} --strip-debug -O binary ${CMAKE_BINARY_DIR}/kernel.out mbr_hole.bin
-)
-
-add_custom_target(boot.img
-    DEPENDS mbr_hole.bin
-    DEPENDS user_space_programs
-    COMMAND make -C ${CMAKE_SOURCE_DIR} image
-)
-
-add_custom_command(OUTPUT run
-    POST_BUILD
-    DEPENDS boot.img
-    COMMAND bochs -f ${CMAKE_SOURCE_DIR}/bochs.conf
-)

+ 123 - 227
Cargo.lock

@@ -3,12 +3,14 @@
 version = 4
 
 [[package]]
-name = "aho-corasick"
-version = "1.1.3"
+name = "acpi"
+version = "5.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+checksum = "94476c7ef97af4c4d998b3f422c1b01d5211aad57c80ed200baf148d1f1efab6"
 dependencies = [
- "memchr",
+ "bit_field",
+ "bitflags",
+ "log",
 ]
 
 [[package]]
@@ -16,8 +18,17 @@ name = "arch"
 version = "0.1.0"
 dependencies = [
  "cfg-if",
+ "eonix_hal_traits",
  "eonix_mm",
- "percpu-macros",
+]
+
+[[package]]
+name = "arch_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -31,24 +42,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
-name = "bindgen"
-version = "0.70.1"
+name = "bit_field"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
-dependencies = [
- "bitflags",
- "cexpr",
- "clang-sys",
- "itertools",
- "log",
- "prettyplease",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash",
- "shlex",
- "syn",
-]
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
 
 [[package]]
 name = "bitflags"
@@ -64,15 +61,6 @@ dependencies = [
  "intrusive_list",
 ]
 
-[[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom",
-]
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -80,21 +68,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "clang-sys"
-version = "1.8.1"
+name = "either"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "eonix_hal"
+version = "0.1.0"
 dependencies = [
- "glob",
- "libc",
- "libloading",
+ "acpi",
+ "arch",
+ "cfg-if",
+ "eonix_hal_macros",
+ "eonix_hal_traits",
+ "eonix_mm",
+ "eonix_percpu",
+ "eonix_preempt",
+ "eonix_sync_base",
 ]
 
 [[package]]
-name = "either"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+name = "eonix_hal_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "eonix_hal_traits"
+version = "0.1.0"
+dependencies = [
+ "bitflags",
+ "eonix_mm",
+]
+
+[[package]]
+name = "eonix_kernel"
+version = "0.1.0"
+dependencies = [
+ "acpi",
+ "arch",
+ "atomic_unique_refcell",
+ "bitflags",
+ "buddy_allocator",
+ "eonix_hal",
+ "eonix_log",
+ "eonix_macros",
+ "eonix_mm",
+ "eonix_percpu",
+ "eonix_preempt",
+ "eonix_runtime",
+ "eonix_sync",
+ "intrusive-collections",
+ "intrusive_list",
+ "itertools",
+ "pointers",
+ "posix_types",
+ "slab_allocator",
+]
 
 [[package]]
 name = "eonix_log"
@@ -103,6 +137,15 @@ dependencies = [
  "eonix_sync",
 ]
 
+[[package]]
+name = "eonix_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "eonix_mm"
 version = "0.1.0"
@@ -110,11 +153,28 @@ dependencies = [
  "bitflags",
 ]
 
+[[package]]
+name = "eonix_percpu"
+version = "0.1.0"
+dependencies = [
+ "eonix_percpu_macros",
+]
+
+[[package]]
+name = "eonix_percpu_macros"
+version = "0.1.0"
+dependencies = [
+ "arch_macros",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "eonix_preempt"
 version = "0.1.0"
 dependencies = [
- "arch",
+ "eonix_percpu",
 ]
 
 [[package]]
@@ -123,7 +183,9 @@ version = "0.1.0"
 dependencies = [
  "arch",
  "atomic_unique_refcell",
+ "eonix_hal",
  "eonix_log",
+ "eonix_percpu",
  "eonix_preempt",
  "eonix_sync",
  "intrusive-collections",
@@ -131,42 +193,38 @@ dependencies = [
 ]
 
 [[package]]
-name = "eonix_sync"
+name = "eonix_spin"
 version = "0.1.0"
 dependencies = [
- "arch",
  "eonix_preempt",
- "intrusive-collections",
+ "eonix_sync_base",
+]
+
+[[package]]
+name = "eonix_sync"
+version = "0.1.0"
+dependencies = [
+ "eonix_spin",
+ "eonix_sync_base",
+ "eonix_sync_rt",
 ]
 
 [[package]]
-name = "gbos-rust-part"
+name = "eonix_sync_base"
+version = "0.1.0"
+
+[[package]]
+name = "eonix_sync_rt"
 version = "0.1.0"
 dependencies = [
  "arch",
- "atomic_unique_refcell",
- "bindgen",
- "bitflags",
- "buddy_allocator",
- "eonix_log",
- "eonix_mm",
+ "eonix_hal",
  "eonix_preempt",
- "eonix_runtime",
- "eonix_sync",
+ "eonix_spin",
+ "eonix_sync_base",
  "intrusive-collections",
- "intrusive_list",
- "itertools",
- "pointers",
- "posix_types",
- "slab_allocator",
 ]
 
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
-
 [[package]]
 name = "intrusive-collections"
 version = "0.9.7"
@@ -189,34 +247,12 @@ dependencies = [
  "either",
 ]
 
-[[package]]
-name = "libc"
-version = "0.2.164"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
-
-[[package]]
-name = "libloading"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
-dependencies = [
- "cfg-if",
- "windows-targets",
-]
-
 [[package]]
 name = "log"
 version = "0.4.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
 
-[[package]]
-name = "memchr"
-version = "2.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
-
 [[package]]
 name = "memoffset"
 version = "0.9.1"
@@ -226,31 +262,6 @@ dependencies = [
  "autocfg",
 ]
 
-[[package]]
-name = "minimal-lexical"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-
-[[package]]
-name = "nom"
-version = "7.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
-dependencies = [
- "memchr",
- "minimal-lexical",
-]
-
-[[package]]
-name = "percpu-macros"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "pointers"
 version = "0.1.0"
@@ -259,16 +270,6 @@ version = "0.1.0"
 name = "posix_types"
 version = "0.1.0"
 
-[[package]]
-name = "prettyplease"
-version = "0.2.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
-dependencies = [
- "proc-macro2",
- "syn",
-]
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.92"
@@ -287,47 +288,6 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "regex"
-version = "1.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-automata"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
-
-[[package]]
-name = "rustc-hash"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
-
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
 [[package]]
 name = "slab_allocator"
 version = "0.1.0"
@@ -353,67 +313,3 @@ name = "unicode-ident"
 version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
-
-[[package]]
-name = "windows-targets"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
-dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

+ 7 - 6
Cargo.toml

@@ -1,10 +1,10 @@
 [package]
-name = "gbos-rust-part"
+name = "eonix_kernel"
 version = "0.1.0"
 edition = "2021"
 
 [lib]
-crate-type = ["staticlib"]
+crate-type = ["bin"]
 
 [dependencies]
 arch = { path = "./arch" }
@@ -12,8 +12,10 @@ atomic_unique_refcell = { path = "./crates/atomic_unique_refcell", features = [
     "no_std",
 ] }
 buddy_allocator = { path = "./crates/buddy_allocator" }
-slab_allocator = { path = "./crates/slab_allocator" }
+eonix_hal = { path = "./crates/eonix_hal" }
+eonix_macros = { path = "./macros" }
 eonix_mm = { path = "./crates/eonix_mm" }
+eonix_percpu = { path = "./crates/eonix_percpu" }
 eonix_preempt = { path = "./crates/eonix_preempt" }
 eonix_runtime = { path = "./crates/eonix_runtime" }
 eonix_sync = { path = "./crates/eonix_sync" }
@@ -21,10 +23,12 @@ eonix_log = { path = "./crates/eonix_log" }
 intrusive_list = { path = "./crates/intrusive_list" }
 pointers = { path = "./crates/pointers" }
 posix_types = { path = "./crates/posix_types" }
+slab_allocator = { path = "./crates/slab_allocator" }
 
 bitflags = "2.6.0"
 intrusive-collections = "0.9.7"
 itertools = { version = "0.13.0", default-features = false }
+acpi = "5.2.0"
 
 [features]
 default = ["smp"]
@@ -34,9 +38,6 @@ log_trace = ["trace_syscall", "trace_scheduler"]
 log_debug = []
 smp = []
 
-[build-dependencies]
-bindgen = "0.70.1"
-
 [profile.dev]
 panic = "abort"
 

+ 66 - 40
Makefile.src

@@ -1,42 +1,51 @@
-# disable kvm to debug triple faults
-QEMU_BIN=##PLACEHOLDER_1##
-GDB_BIN=##PLACEHOLDER_2##
-QEMU_ACCELERATION_FLAG=##PLACEHOLDER_3##
-QEMU_DEBUG_FLAG=#-d cpu_reset,int -D build/qemu.log
-QEMU_ARGS=-machine q35 -drive id=disk,file=build/boot.img,format=raw,if=none \
+HOST ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
+ARCH ?= ##DEFAULT_ARCH##
+MODE ?= debug
+SMP ?= 4
+
+QEMU ?= ##QEMU##
+GDB ?= ##GDB##
+FDISK ?= ##FDISK##
+
+QEMU_ARGS ?= -machine q35 -drive id=disk,file=build/boot.img,format=raw,if=none \
 	-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -smp 4 \
-	-no-reboot -no-shutdown $(QEMU_ACCELERATION_FLAG) $(QEMU_DEBUG_FLAG)
+	-netdev user,id=mynet0,net=192.168.1.0/24,dhcpstart=192.168.1.16 -device e1000e,netdev=mynet0 \
+	-no-reboot -no-shutdown
+
+ifeq ($(HOST),darwin)
+QEMU_ACCEL ?= -accel tcg
+else ifeq ($(HOST),linux)
+QEMU_ACCEL ?= -accel kvm
+endif
+
+QEMU_ARGS += $(QEMU_ACCEL)
+
+ifneq ($(DEBUG_TRAPS),)
+QEMU_ARGS += -d cpu_reset,int,guest_errors -D build/qemu.log
+endif
 
-FDISK_BIN ?= ##PLACEHOLDER_5##
+ifneq ($(SMP),)
+QEMU_ARGS += -smp $(SMP)
+endif
+
+ifeq ($(MODE),debug)
+MODE := dev
+endif
 
-CROSS_COMPILE=##PLACEHOLDER_4##
 .PHONY: run
 run: build
-	$(QEMU_BIN) $(QEMU_ARGS) -display curses -S -s
+	$(QEMU) $(QEMU_ARGS) -display none -serial mon:stdio
+
 .PHONY: srun
 srun: build
-	$(QEMU_BIN) $(QEMU_ARGS) -display none -S -s -serial mon:stdio
-.PHONY: nativerun
-nativerun: build
-	$(QEMU_BIN) $(QEMU_ARGS) -display none -serial mon:stdio
-
-.PHONY: prepare
-prepare:
-	cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug $(CROSS_COMPILE)
-	cp build/compile_commands.json .
-
-.PHONY: reprepare
-reprepare: clean prepare
-	true
-
-.PHONY: build
-build:
-	cmake --build build -j 6 --target boot.img
+	$(QEMU) $(QEMU_ARGS) -display none -S -s -serial mon:stdio
 
 .PHONY: clean
 clean:
+	-mv build/fs.img .
 	-rm -rf build
-	-rm compile_commands.json
+	-mkdir build
+	-mv fs.img build
 
 .PHONY: clean-all
 clean-all: clean
@@ -44,14 +53,14 @@ clean-all: clean
 
 .PHONY: debug
 debug:
-	-$(GDB_BIN) --symbols=build/kernel.out \
+	-RUST_GDB=$(GDB) rust-gdb --symbols=build/kernel.sym \
 		-iex 'source pretty-print.py' \
 		-iex 'set pagination off' \
 		-iex 'set output-radix 16' \
 		-iex 'set print asm-demangle on' \
 		-iex 'set print pretty on' \
 		-iex 'target remote:1234'
-	-killall $(QEMU_BIN)
+	-killall $(QEMU)
 
 .PHONY: tmux-debug
 tmux-debug:
@@ -62,17 +71,34 @@ tmux-debug:
 	-tmux attach -t gbos-debug
 	tmux kill-session -t gbos-debug
 
+.PHONY: kernel
+kernel:
+	cargo build --profile $(MODE)
+
+build/kernel.sym: kernel
+	cargo objcopy --profile $(MODE) -- --only-keep-debug build/kernel.sym
+
+build/mbr.bin: kernel
+	cargo objcopy --profile $(MODE) -- -O binary -j .mbr build/mbr.bin
+
+build/stage1.bin: kernel
+	cargo objcopy --profile $(MODE) -- -O binary -j .stage1 build/stage1.bin
+
+build/kernel.bin: kernel
+	cargo objcopy --profile $(MODE) -- -O binary --strip-debug \
+		-R .mbr -R .stage1 build/kernel.bin
+
 build/fs.img: init_script.sh
 	sh script/build-img.sh
 
-build/boot.img: build/fs.img build/mbr_hole.bin
-	dd if=build/mbr_hole.bin of=build/boot.img
-	dd if=build/fs.img of=build/boot.img bs=$(shell expr 4 \* 1024 \* 1024) seek=1 conv=notrunc
-	sh -c 'echo n; echo; echo; echo 8192; echo; echo a; echo w' | $(FDISK_BIN) build/boot.img
+build/boot.img: build/mbr.bin build/stage1.bin build/kernel.bin build/fs.img
+	dd if=build/mbr.bin of=build/boot.img bs=512 count=1 conv=notrunc 2> /dev/null
+	dd if=build/stage1.bin of=build/boot.img bs=512 seek=1 conv=notrunc 2> /dev/null
+	dd if=build/kernel.bin of=build/boot.img bs=4096 seek=1 conv=notrunc 2> /dev/null
+	dd if=build/fs.img of=build/boot.img bs=$(shell expr 4 \* 1024 \* 1024) \
+		seek=1 conv=notrunc 2> /dev/null
+	sh -c 'echo n; echo; echo; echo 8192; echo; echo a; echo w' \
+		| $(FDISK) build/boot.img 2> /dev/null > /dev/null
 
-build/boot.vdi: build/boot.img
-	-rm build/boot.vdi
-	VBoxManage convertfromraw $< $@ --format VDI
-
-.PHONY: image
-image: build/boot.img
+.PHONY: build
+build: build/boot.img build/kernel.sym

+ 19 - 14
README.md

@@ -15,7 +15,7 @@
 - [x] 静态 ELF 加载器
 - [x] TTY 及任务控制接口(正在进一步实现)
 - [x] FAT32 文件系统的读取实现
-- [ ] 全部 Rust 化(只剩一点点)
+- [x] 全部 Rust 化(只剩一点点)
 - [ ] 网卡驱动(WIP)
 - [ ] POSIX 线程接口(WIP)
 - [ ] 动态加载器(WIP)
@@ -180,11 +180,11 @@
 
 ```bash
 # 配置构建环境
-./configure && make prepare && make build
+./configure && make build
 
 # 直接运行
 
-make nativerun
+make run
 
 # 如果需要调试
 
@@ -201,14 +201,19 @@ make tmux-debug
 
 可能需要在运行 `./configure` 时在环境变量中指定正确版本的构建工具。
 
-- `QEMU`: 用于调试运行的 QEMU。默认使用 `qemu-system-x86_64`。
-- `GDB`: 用于 `make debug` 的 GDB。我们将默认查找 `gdb` 或者是 `x86_64-elf-gdb` 并检查支持的架构。
-- `FDISK_BIN`: 用于创建磁盘镜像分区表的 fdisk 可执行文件。默认使用 `fdisk`。
-
-如果正在进行交叉编译,请在运行 `./configure` 时将 `CROSS_COMPILE` 设置为你的交叉编译器的相应目标三元组。
-
-## 运行自己编译的程序
-
-项目目录下的 `user` 目录主要是出于一些*历史*原因存在,几乎没有任何用处。所以不要尝试查看里面的内容。
-
-要将你的程序(可以使用任何编译器为i386架构编译,静态链接)复制到构建的磁盘镜像中,你可以编辑 `CMakeLists.txt` 并在 `boot.img` 部分添加一行。你也可以尝试编辑 `init_script.sh` 以自定义启动过程。
+- `DEFAULT_ARCH`: 在调用 Makefile 时如果不进行额外指定,默认使用的架构。默认为 `x86_64`。
+- `QEMU`: 用于调试运行的 QEMU。默认使用 `qemu-system-$(ARCH)`。
+- `GDB`: 用于 `make debug` 的 GDB。我们将默认查找 `$(ARCH)-elf-gdb` 并检查支持的架构。
+- `FDISK`: 用于创建磁盘镜像分区表的 fdisk 可执行文件,要求使用来自 util-linux 版本的 fdisk。默认使用 `fdisk`。
+
+在运行 make 时可以指定的额外选项:
+
+- `HOST`: 当前平台架构,用于决定 qemu 的默认加速模式,默认使用 `uname -s` 的输出。
+- `ARCH`: 编译运行的目标架构,默认使用 configure 时指定的值。
+- `MODE`: 编译运行的模式,可以使用 `debug` 或者是 `release`。
+- `SMP`: 是否运行多处理器处理,默认使用 4 CPU。
+- `QEMU`: 手动指定 qemu 路径。
+- `GDB`: 手动指定 gdb 路径。
+- `FDISK`: 手动指定 fdisk 路径。
+- `QEMU_ACCEL`: 手动指定要使用的 qemu 加速方法。
+- `DEBUG_TRAPS`: 是否要进行 trap 的调试,使 qemu 输出详细的 trap 日志。

+ 1 - 1
arch/Cargo.toml

@@ -4,6 +4,6 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
+eonix_hal_traits = { path = "../crates/eonix_hal/eonix_hal_traits" }
 eonix_mm = { path = "../crates/eonix_mm" }
-percpu-macros = { path = "./percpu-macros" }
 cfg-if = "1.0"

+ 9 - 0
arch/arch_macros/Cargo.toml

@@ -0,0 +1,9 @@
+[package]
+name = "arch_macros"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "2.0", features = ["full"] }

+ 1 - 0
arch/arch_macros/src/lib.rs

@@ -0,0 +1 @@
+pub mod x86_64;

+ 1 - 0
arch/arch_macros/src/x86_64/mod.rs

@@ -0,0 +1 @@
+pub mod percpu;

+ 32 - 0
arch/arch_macros/src/x86_64/percpu.rs

@@ -0,0 +1,32 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::{Ident, LitStr, Type};
+
+/// Generate the assembly code to load the address of a symbol and add it to a register.
+pub fn load_addr_of_to(symbol: &LitStr, target: &LitStr) -> TokenStream {
+    quote! {
+        concat!("mov %gs:0, ", #target),
+        concat!("add $", #symbol, ", ", #target)
+    }
+}
+
+/// Get the base address for percpu variables of the current thread.
+pub fn get_percpu_pointer(percpu: &Ident, ty: &Type) -> TokenStream {
+    let stmt = load_addr_of_to(
+        &LitStr::new("{ident}", Span::call_site()),
+        &LitStr::new("{address}", Span::call_site()),
+    );
+
+    quote! {
+        {
+            let base: *mut #ty;
+            ::core::arch::asm!(
+                #stmt,
+                ident = sym #percpu,
+                address = out(reg) base,
+                options(att_syntax, nostack, preserves_flags)
+            );
+            base
+        }
+    }
+}

+ 0 - 47
arch/percpu-macros/Cargo.lock

@@ -1,47 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "percpu-macros"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.89"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"

+ 0 - 24
arch/percpu-macros/src/arch.rs

@@ -1,24 +0,0 @@
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::{Ident, Type};
-
-/// Get the base address for percpu variables of the current thread.
-pub fn get_percpu_pointer(percpu: &Ident, ty: &Type) -> TokenStream {
-    quote! {
-        {
-            #[cfg(target_arch = "x86_64")]
-            {
-                let base: *mut #ty;
-                ::core::arch::asm!(
-                    "mov %gs:0, {address}",
-                    "add ${percpu_pointer}, {address}",
-                    percpu_pointer = sym #percpu,
-                    address = out(reg) base,
-                    options(att_syntax)
-                );
-                base
-            }
-        }
-    }
-    .into()
-}

+ 0 - 1
arch/src/lib.rs

@@ -1,5 +1,4 @@
 #![no_std]
-#![feature(naked_functions)]
 
 cfg_if::cfg_if! {
     if #[cfg(target_arch = "x86_64")] {

+ 32 - 0
arch/src/x86_64/fpu.rs

@@ -0,0 +1,32 @@
+use core::arch::asm;
+use eonix_hal_traits::fpu::RawFpuState;
+
+#[derive(Clone, Copy)]
+#[repr(align(16))]
+pub struct FpuState([u8; 512]);
+
+impl RawFpuState for FpuState {
+    fn new() -> Self {
+        Self([0; 512])
+    }
+
+    fn save(&mut self) {
+        unsafe {
+            asm!(
+                "fxsave ({0})",
+                in(reg) &mut self.0,
+                options(att_syntax, nostack, preserves_flags)
+            )
+        }
+    }
+
+    fn restore(&mut self) {
+        unsafe {
+            asm!(
+                "fxrstor ({0})",
+                in(reg) &mut self.0,
+                options(att_syntax, nostack, preserves_flags)
+            )
+        }
+    }
+}

+ 0 - 555
arch/src/x86_64/interrupt.rs

@@ -1,555 +0,0 @@
-use core::{
-    arch::{asm, global_asm},
-    pin::Pin,
-    ptr::NonNull,
-};
-
-use crate::rdmsr;
-
-use super::pause;
-
-global_asm!(
-    r"
-    .set RAX, 0x00
-    .set RBX, 0x08
-    .set RCX, 0x10
-    .set RDX, 0x18
-    .set RDI, 0x20
-    .set RSI, 0x28
-    .set R8, 0x30
-    .set R9, 0x38
-    .set R10, 0x40
-    .set R11, 0x48
-    .set R12, 0x50
-    .set R13, 0x58
-    .set R14, 0x60
-    .set R15, 0x68
-    .set RBP, 0x70
-    .set INT_NO, 0x78
-    .set ERRCODE, 0x80
-    .set RIP, 0x88
-    .set CS, 0x90
-    .set FLAGS, 0x98
-    .set RSP, 0xa0
-    .set SS, 0xa8
-
-    .macro movcfi reg, offset
-        mov \reg, \offset(%rsp)
-        .cfi_rel_offset \reg, \offset
-    .endm
-
-    .macro movrst reg, offset
-        mov \offset(%rsp), \reg
-        .cfi_restore \reg
-    .endm
-
-    .globl ISR_stub_restore
-    .type ISR_stub_restore @function
-
-    ISR_stub:
-        .cfi_startproc
-        .cfi_signal_frame
-        .cfi_def_cfa_offset 0x18
-        .cfi_offset %rsp, 0x10
-
-        cmpq $0x08, 24(%rsp)
-        je 1f
-        swapgs
-
-    1:
-        sub $0x78, %rsp
-        .cfi_def_cfa_offset 0x90
-
-        movcfi %rax, RAX
-        movcfi %rbx, RBX
-        movcfi %rcx, RCX
-        movcfi %rdx, RDX
-        movcfi %rdi, RDI
-        movcfi %rsi, RSI
-        movcfi %r8,  R8
-        movcfi %r9,  R9
-        movcfi %r10, R10
-        movcfi %r11, R11
-        movcfi %r12, R12
-        movcfi %r13, R13
-        movcfi %r14, R14
-        movcfi %r15, R15
-        movcfi %rbp, RBP
-
-        mov INT_NO(%rsp), %rax
-        sub $ISR0, %rax
-        shr $3, %rax
-        mov %rax, INT_NO(%rsp)
-
-        mov %rsp, %rbx
-        .cfi_def_cfa_register %rbx
-
-        and $~0xf, %rsp
-        sub $512, %rsp
-        fxsave (%rsp)
-
-        mov %rbx, %rdi
-        mov %rsp, %rsi
-        call interrupt_handler
-
-    ISR_stub_restore:
-        fxrstor (%rsp)
-        mov %rbx, %rsp
-        .cfi_def_cfa_register %rsp
-
-    .globl _arch_fork_return
-    _arch_fork_return:
-        movrst %rax, RAX
-        movrst %rbx, RBX
-        movrst %rcx, RCX
-        movrst %rdx, RDX
-        movrst %rdi, RDI
-        movrst %rsi, RSI
-        movrst %r8,  R8
-        movrst %r9,  R9
-        movrst %r10, R10
-        movrst %r11, R11
-        movrst %r12, R12
-        movrst %r13, R13
-        movrst %r14, R14
-        movrst %r15, R15
-        movrst %rbp, RBP
-
-        add $0x88, %rsp
-        .cfi_def_cfa_offset 0x08
-
-        cmpq $0x08, 8(%rsp)
-        je 1f
-        swapgs
-
-    1:
-        iretq
-        .cfi_endproc
-
-    .altmacro
-    .macro build_isr_no_err name
-        .align 8
-        .globl ISR\name
-        .type  ISR\name @function
-        ISR\name:
-            .cfi_startproc
-            .cfi_signal_frame
-            .cfi_def_cfa_offset 0x08
-            .cfi_offset %rsp, 0x10
-
-            .cfi_same_value %rax
-            .cfi_same_value %rbx
-            .cfi_same_value %rcx
-            .cfi_same_value %rdx
-            .cfi_same_value %rdi
-            .cfi_same_value %rsi
-            .cfi_same_value %r8
-            .cfi_same_value %r9
-            .cfi_same_value %r10
-            .cfi_same_value %r11
-            .cfi_same_value %r12
-            .cfi_same_value %r13
-            .cfi_same_value %r14
-            .cfi_same_value %r15
-            .cfi_same_value %rbp
-
-            push %rbp # push placeholder for error code
-            .cfi_def_cfa_offset 0x10
-
-            call ISR_stub
-            .cfi_endproc
-    .endm
-
-    .altmacro
-    .macro build_isr_err name
-        .align 8
-        .globl ISR\name
-        .type  ISR\name @function
-        ISR\name:
-            .cfi_startproc
-            .cfi_signal_frame
-            .cfi_def_cfa_offset 0x10
-            .cfi_offset %rsp, 0x10
-
-            .cfi_same_value %rax
-            .cfi_same_value %rbx
-            .cfi_same_value %rcx
-            .cfi_same_value %rdx
-            .cfi_same_value %rdi
-            .cfi_same_value %rsi
-            .cfi_same_value %r8
-            .cfi_same_value %r9
-            .cfi_same_value %r10
-            .cfi_same_value %r11
-            .cfi_same_value %r12
-            .cfi_same_value %r13
-            .cfi_same_value %r14
-            .cfi_same_value %r15
-            .cfi_same_value %rbp
-
-            call ISR_stub
-            .cfi_endproc
-    .endm
-
-    build_isr_no_err 0
-    build_isr_no_err 1
-    build_isr_no_err 2
-    build_isr_no_err 3
-    build_isr_no_err 4
-    build_isr_no_err 5
-    build_isr_no_err 6
-    build_isr_no_err 7
-    build_isr_err    8
-    build_isr_no_err 9
-    build_isr_err    10
-    build_isr_err    11
-    build_isr_err    12
-    build_isr_err    13
-    build_isr_err    14
-    build_isr_no_err 15
-    build_isr_no_err 16
-    build_isr_err    17
-    build_isr_no_err 18
-    build_isr_no_err 19
-    build_isr_no_err 20
-    build_isr_err    21
-    build_isr_no_err 22
-    build_isr_no_err 23
-    build_isr_no_err 24
-    build_isr_no_err 25
-    build_isr_no_err 26
-    build_isr_no_err 27
-    build_isr_no_err 28
-    build_isr_err    29
-    build_isr_err    30
-    build_isr_no_err 31
-
-    .set i, 32
-    .rept 0x80+1
-        build_isr_no_err %i
-        .set i, i+1
-    .endr
-
-    .section .rodata
-
-    .align 8
-    .globl ISR_START_ADDR
-    .type  ISR_START_ADDR @object
-    ISR_START_ADDR:
-        .quad ISR0
-    ",
-    options(att_syntax),
-);
-
-/// Saved registers when a trap (interrupt or exception) occurs.
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(Debug, Default, Clone, Copy)]
-pub struct InterruptContext {
-    pub rax: u64,
-    pub rbx: u64,
-    pub rcx: u64,
-    pub rdx: u64,
-    pub rdi: u64,
-    pub rsi: u64,
-    pub r8: u64,
-    pub r9: u64,
-    pub r10: u64,
-    pub r11: u64,
-    pub r12: u64,
-    pub r13: u64,
-    pub r14: u64,
-    pub r15: u64,
-    pub rbp: u64,
-
-    pub int_no: u64,
-    pub error_code: u64,
-
-    // Pushed by CPU
-    pub rip: u64,
-    pub cs: u64,
-    pub eflags: u64,
-    pub rsp: u64,
-    pub ss: u64,
-}
-
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(Clone, Copy)]
-pub struct ExtendedContext {
-    /// For FPU states
-    data: [u8; 512],
-}
-
-#[repr(C)]
-#[derive(Clone, Copy)]
-struct IDTEntry {
-    offset_low: u16,
-    selector: u16,
-
-    interrupt_stack: u8,
-    attributes: u8,
-
-    offset_mid: u16,
-    offset_high: u32,
-    reserved: u32,
-}
-
-pub struct APICReg(*mut u32);
-pub struct APICRegs {
-    base: NonNull<u32>,
-}
-
-/// Architecture-specific interrupt control block.
-pub struct InterruptControl {
-    idt: [IDTEntry; 256],
-    apic_base: APICRegs,
-}
-
-/// State of the interrupt flag.
-pub struct IrqState(u64);
-
-impl InterruptContext {
-    pub fn set_return_value(&mut self, value: u64) {
-        // The return value is stored in rax.
-        self.rax = value;
-    }
-
-    pub fn set_return_address(&mut self, addr: u64, user: bool) {
-        // The return address is stored in rip.
-        self.rip = addr;
-        if user {
-            self.cs = 0x2b; // User code segment
-        } else {
-            self.cs = 0x08; // Kernel code segment
-        }
-    }
-
-    pub fn set_stack_pointer(&mut self, sp: u64, user: bool) {
-        // The stack pointer is stored in rsp.
-        self.rsp = sp;
-        if user {
-            self.ss = 0x33; // User stack segment
-        } else {
-            self.ss = 0x10; // Kernel stack segment
-        }
-    }
-
-    pub fn set_interrupt_enabled(&mut self, enabled: bool) {
-        // The interrupt state is stored in eflags.
-        if enabled {
-            self.eflags |= 0x200; // Set the interrupt flag
-        } else {
-            self.eflags &= !0x200; // Clear the interrupt flag
-        }
-    }
-}
-
-impl IDTEntry {
-    const fn new(offset: usize, selector: u16, attributes: u8) -> Self {
-        Self {
-            offset_low: offset as u16,
-            selector,
-            interrupt_stack: 0,
-            attributes,
-            offset_mid: (offset >> 16) as u16,
-            offset_high: (offset >> 32) as u32,
-            reserved: 0,
-        }
-    }
-
-    const fn null() -> Self {
-        Self {
-            offset_low: 0,
-            selector: 0,
-            interrupt_stack: 0,
-            attributes: 0,
-            offset_mid: 0,
-            offset_high: 0,
-            reserved: 0,
-        }
-    }
-}
-
-impl APICReg {
-    fn new(pointer: *mut u32) -> Self {
-        Self(pointer)
-    }
-
-    pub fn read(&self) -> u32 {
-        unsafe { self.0.read_volatile() }
-    }
-
-    pub fn write(&self, value: u32) {
-        unsafe { self.0.write_volatile(value) }
-    }
-}
-
-impl APICRegs {
-    pub fn local_apic_id(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0x20).as_ptr()) }
-    }
-
-    pub fn task_priority(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0x80).as_ptr()) }
-    }
-
-    pub fn end_of_interrupt(&self) {
-        unsafe { APICReg::new(self.base.byte_offset(0xb0).as_ptr()).write(0) }
-    }
-
-    pub fn spurious(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0xf0).as_ptr()) }
-    }
-
-    pub fn interrupt_command(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0x300).as_ptr()) }
-    }
-
-    pub fn timer_register(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0x320).as_ptr()) }
-    }
-
-    pub fn timer_initial_count(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0x380).as_ptr()) }
-    }
-
-    pub fn timer_current_count(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0x390).as_ptr()) }
-    }
-
-    pub fn timer_divide(&self) -> APICReg {
-        unsafe { APICReg::new(self.base.byte_offset(0x3e0).as_ptr()) }
-    }
-}
-
-impl InterruptControl {
-    /// # Return
-    /// Returns a tuple of InterruptControl and the cpu id of the current cpu.
-    pub(crate) fn new() -> (Self, usize) {
-        extern "C" {
-            static ISR_START_ADDR: usize;
-        }
-
-        let idt = core::array::from_fn(|idx| match idx {
-            0..0x80 => IDTEntry::new(unsafe { ISR_START_ADDR } + 8 * idx, 0x08, 0x8e),
-            0x80 => IDTEntry::new(unsafe { ISR_START_ADDR } + 8 * idx, 0x08, 0xee),
-            _ => IDTEntry::null(),
-        });
-
-        let apic_base = {
-            let apic_base = rdmsr(0x1b);
-            assert_eq!(apic_base & 0x800, 0x800, "LAPIC not enabled");
-
-            let apic_base = ((apic_base & !0xfff) + 0xffffff00_00000000) as *mut u32;
-            APICRegs {
-                // TODO: A better way to convert to physical address
-                base: NonNull::new(apic_base).expect("Invalid APIC base"),
-            }
-        };
-
-        // Make sure APIC is enabled.
-        apic_base.spurious().write(0x1ff);
-
-        let cpuid = apic_base.local_apic_id().read() >> 24;
-
-        (Self { idt, apic_base }, cpuid as usize)
-    }
-
-    pub fn setup_timer(&self) {
-        self.apic_base.task_priority().write(0);
-        self.apic_base.timer_divide().write(0x3); // Divide by 16
-        self.apic_base.timer_register().write(0x20040);
-
-        // TODO: Get the bus frequency from...?
-        let freq = 200;
-        let count = freq * 1_000_000 / 16 / 100;
-        self.apic_base.timer_initial_count().write(count as u32);
-    }
-
-    pub fn setup_idt(self: Pin<&mut Self>) {
-        lidt(
-            self.idt.as_ptr() as usize,
-            (size_of::<IDTEntry>() * 256 - 1) as u16,
-        );
-    }
-
-    pub fn send_sipi(&self) {
-        let icr = self.apic_base.interrupt_command();
-
-        icr.write(0xc4500);
-        while icr.read() & 0x1000 != 0 {
-            pause();
-        }
-
-        icr.write(0xc4601);
-        while icr.read() & 0x1000 != 0 {
-            pause();
-        }
-    }
-
-    /// Send EOI to APIC so that it can send more interrupts.
-    pub fn end_of_interrupt(&self) {
-        self.apic_base.end_of_interrupt()
-    }
-}
-
-impl IrqState {
-    pub fn restore(self) {
-        let Self(state) = self;
-
-        unsafe {
-            asm!(
-                "push {state}",
-                "popf",
-                state = in(reg) state,
-                options(att_syntax, nomem)
-            );
-        }
-    }
-}
-
-pub fn enable_irqs() {
-    unsafe {
-        asm!("sti", options(att_syntax, nomem, nostack));
-    }
-}
-
-pub fn disable_irqs() {
-    unsafe {
-        asm!("cli", options(att_syntax, nomem, nostack));
-    }
-}
-
-pub fn disable_irqs_save() -> IrqState {
-    let state: u64;
-    unsafe {
-        asm!(
-            "pushf",
-            "pop {state}",
-            "cli",
-            state = out(reg) state,
-            options(att_syntax, nomem)
-        );
-    }
-
-    IrqState(state)
-}
-
-extern "C" {
-    pub fn _arch_fork_return();
-}
-
-fn lidt(base: usize, limit: u16) {
-    let mut idt_descriptor = [0u16; 5];
-
-    idt_descriptor[0] = limit;
-    idt_descriptor[1] = base as u16;
-    idt_descriptor[2] = (base >> 16) as u16;
-    idt_descriptor[3] = (base >> 32) as u16;
-    idt_descriptor[4] = (base >> 48) as u16;
-
-    unsafe {
-        asm!("lidt ({})", in(reg) &idt_descriptor, options(att_syntax));
-    }
-}

+ 0 - 17
arch/src/x86_64/io.rs

@@ -1,22 +1,5 @@
 use core::arch::asm;
 
-pub fn enable_sse() {
-    unsafe {
-        asm!(
-            "mov %cr0, %rax",
-            "and $(~0xc), %rax",
-            "or $0x22, %rax",
-            "mov %rax, %cr0",
-            "mov %cr4, %rax",
-            "or $0x600, %rax",
-            "mov %rax, %cr4",
-            "fninit",
-            out("rax") _,
-            options(att_syntax, nomem, nostack)
-        )
-    }
-}
-
 pub fn inb(no: u16) -> u8 {
     let data;
     unsafe {

+ 0 - 202
arch/src/x86_64/mm.rs

@@ -1,202 +0,0 @@
-use core::{marker::PhantomData, ptr::NonNull};
-use eonix_mm::{
-    address::{Addr as _, PAddr},
-    page_table::{
-        PageAttribute, PageTableLevel, PagingMode, RawAttribute, RawPageTable, TableAttribute, PTE,
-    },
-    paging::{PageBlock, PFN},
-};
-
-pub const PAGE_SIZE: usize = 0x1000;
-
-const KERNEL_PML4_PFN: PFN = PFN::from_val(0x2000 >> 12);
-
-const PA_P: u64 = 0x001;
-const PA_RW: u64 = 0x002;
-const PA_US: u64 = 0x004;
-#[allow(dead_code)]
-const PA_PWT: u64 = 0x008;
-#[allow(dead_code)]
-const PA_PCD: u64 = 0x010;
-const PA_A: u64 = 0x020;
-const PA_D: u64 = 0x040;
-const PA_PS: u64 = 0x080;
-const PA_G: u64 = 0x100;
-const PA_COW: u64 = 0x200;
-const PA_MMAP: u64 = 0x400;
-const PA_ANON: u64 = 0x800;
-const PA_NXE: u64 = 0x8000_0000_0000_0000;
-const PA_MASK: u64 = 0xfff0_0000_0000_0fff;
-
-#[repr(transparent)]
-pub struct PTE64(u64);
-
-#[derive(Clone, Copy)]
-pub struct PageAttribute64(u64);
-
-pub struct RawPageTable4Levels<'a>(NonNull<PTE64>, PhantomData<&'a ()>);
-
-pub struct PagingMode4Levels;
-
-impl PTE for PTE64 {
-    type Attr = PageAttribute64;
-
-    fn set(&mut self, pfn: PFN, attr: Self::Attr) {
-        let paddr = PAddr::from(pfn).addr();
-
-        self.0 = (paddr as u64 & !PA_MASK) | (attr.0 & PA_MASK);
-    }
-
-    fn get(&self) -> (PFN, Self::Attr) {
-        (
-            PFN::from(PAddr::from((self.0 & !PA_MASK) as usize)),
-            PageAttribute64(self.0 & PA_MASK),
-        )
-    }
-}
-
-impl PagingMode for PagingMode4Levels {
-    type Entry = PTE64;
-    type RawTable<'a> = RawPageTable4Levels<'a>;
-
-    const LEVELS: &'static [PageTableLevel] = &[
-        PageTableLevel::new(39, 9),
-        PageTableLevel::new(30, 9),
-        PageTableLevel::new(21, 9),
-        PageTableLevel::new(12, 9),
-    ];
-
-    const KERNEL_ROOT_TABLE_PFN: PFN = KERNEL_PML4_PFN;
-}
-
-impl<'a> RawPageTable<'a> for RawPageTable4Levels<'a> {
-    type Entry = PTE64;
-
-    fn index(&self, index: u16) -> &'a Self::Entry {
-        unsafe { &self.0.cast::<[PTE64; 512]>().as_ref()[index as usize] }
-    }
-
-    fn index_mut(&mut self, index: u16) -> &'a mut Self::Entry {
-        unsafe { &mut self.0.cast::<[PTE64; 512]>().as_mut()[index as usize] }
-    }
-
-    unsafe fn from_ptr(ptr: NonNull<PageBlock>) -> Self {
-        Self(ptr.cast(), PhantomData)
-    }
-}
-
-impl RawAttribute for PageAttribute64 {
-    fn null() -> Self {
-        Self(0)
-    }
-
-    fn as_table_attr(self) -> Option<TableAttribute> {
-        let mut table_attr = TableAttribute::empty();
-
-        if self.0 & PA_PS != 0 {
-            panic!("Encountered a huge page while parsing table attributes");
-        }
-
-        if self.0 & PA_P != 0 {
-            table_attr |= TableAttribute::PRESENT;
-        }
-        if self.0 & PA_G != 0 {
-            table_attr |= TableAttribute::GLOBAL;
-        }
-        if self.0 & PA_US != 0 {
-            table_attr |= TableAttribute::USER;
-        }
-        if self.0 & PA_A != 0 {
-            table_attr |= TableAttribute::ACCESSED;
-        }
-
-        Some(table_attr)
-    }
-
-    fn as_page_attr(self) -> Option<PageAttribute> {
-        let mut page_attr = PageAttribute::READ;
-
-        if self.0 & PA_P != 0 {
-            page_attr |= PageAttribute::PRESENT;
-        }
-
-        if self.0 & PA_RW != 0 {
-            page_attr |= PageAttribute::WRITE;
-        }
-
-        if self.0 & PA_NXE == 0 {
-            page_attr |= PageAttribute::EXECUTE;
-        }
-
-        if self.0 & PA_US != 0 {
-            page_attr |= PageAttribute::USER;
-        }
-
-        if self.0 & PA_A != 0 {
-            page_attr |= PageAttribute::ACCESSED;
-        }
-
-        if self.0 & PA_D != 0 {
-            page_attr |= PageAttribute::DIRTY;
-        }
-
-        if self.0 & PA_G != 0 {
-            page_attr |= PageAttribute::GLOBAL;
-        }
-
-        if self.0 & PA_COW != 0 {
-            page_attr |= PageAttribute::COPY_ON_WRITE;
-        }
-
-        if self.0 & PA_MMAP != 0 {
-            page_attr |= PageAttribute::MAPPED;
-        }
-
-        if self.0 & PA_ANON != 0 {
-            page_attr |= PageAttribute::ANONYMOUS;
-        }
-
-        Some(page_attr)
-    }
-
-    fn from_table_attr(table_attr: TableAttribute) -> Self {
-        let mut raw_attr = PA_RW;
-
-        for attr in table_attr.iter() {
-            match attr {
-                TableAttribute::PRESENT => raw_attr |= PA_P,
-                TableAttribute::GLOBAL => raw_attr |= PA_G,
-                TableAttribute::USER => raw_attr |= PA_US,
-                TableAttribute::ACCESSED => raw_attr |= PA_A,
-                _ => unreachable!("Invalid table attribute"),
-            }
-        }
-
-        Self(raw_attr)
-    }
-
-    fn from_page_attr(page_attr: PageAttribute) -> Self {
-        let mut raw_attr = PA_NXE;
-
-        for attr in page_attr.iter() {
-            match attr {
-                PageAttribute::PRESENT => raw_attr |= PA_P,
-                PageAttribute::READ => {}
-                PageAttribute::WRITE => raw_attr |= PA_RW,
-                PageAttribute::EXECUTE => raw_attr &= !PA_NXE,
-                PageAttribute::USER => raw_attr |= PA_US,
-                PageAttribute::ACCESSED => raw_attr |= PA_A,
-                PageAttribute::DIRTY => raw_attr |= PA_D,
-                PageAttribute::GLOBAL => raw_attr |= PA_G,
-                PageAttribute::COPY_ON_WRITE => raw_attr |= PA_COW,
-                PageAttribute::MAPPED => raw_attr |= PA_MMAP,
-                PageAttribute::ANONYMOUS => raw_attr |= PA_ANON,
-                _ => unreachable!("Invalid page attribute"),
-            }
-        }
-
-        Self(raw_attr)
-    }
-}
-
-pub type DefaultPagingMode = PagingMode4Levels;

+ 2 - 23
arch/src/x86_64/mod.rs

@@ -1,27 +1,14 @@
-mod context;
 mod fence;
-mod gdt;
-mod init;
-mod interrupt;
+mod fpu;
 mod io;
-mod mm;
-mod percpu;
-mod user;
 
 use core::arch::asm;
 use eonix_mm::address::{Addr as _, PAddr, VAddr};
 use eonix_mm::paging::PFN;
 
-pub use self::context::*;
-pub use self::gdt::*;
-pub use self::init::*;
-pub use self::interrupt::*;
 pub use self::io::*;
-pub use self::user::*;
 pub use fence::*;
-pub use mm::*;
-pub use percpu::*;
-pub use percpu_macros::{define_percpu, define_percpu_shared};
+pub use fpu::*;
 
 #[inline(always)]
 pub fn flush_tlb(vaddr: usize) {
@@ -97,14 +84,6 @@ pub fn pause() {
     }
 }
 
-#[inline(always)]
-pub fn freeze() -> ! {
-    loop {
-        interrupt::disable_irqs();
-        halt();
-    }
-}
-
 #[inline(always)]
 pub fn rdmsr(msr: u32) -> u64 {
     let edx: u32;

+ 0 - 86
arch/src/x86_64/percpu.rs

@@ -1,86 +0,0 @@
-use super::wrmsr;
-use crate::x86_64::mm::PAGE_SIZE;
-use core::{
-    alloc::Layout,
-    arch::asm,
-    cell::UnsafeCell,
-    ptr::{null_mut, NonNull},
-    sync::atomic::{AtomicPtr, Ordering},
-};
-
-pub const MAX_CPUS: usize = 256;
-
-#[repr(align(4096))]
-struct PercpuData(UnsafeCell<()>); // Not `Sync`.
-
-pub struct PercpuArea {
-    data: NonNull<PercpuData>,
-}
-
-static PERCPU_POINTERS: [AtomicPtr<PercpuData>; MAX_CPUS] =
-    [const { AtomicPtr::new(null_mut()) }; MAX_CPUS];
-
-impl PercpuArea {
-    fn page_count() -> usize {
-        extern "C" {
-            static PERCPU_PAGES: usize;
-        }
-        // SAFETY: `PERCPU_PAGES` is defined in linker script and never change.
-        let page_count = unsafe { PERCPU_PAGES };
-        assert_ne!(page_count, 0);
-        page_count
-    }
-
-    fn data_start() -> NonNull<u8> {
-        extern "C" {
-            fn _PERCPU_DATA_START();
-        }
-
-        NonNull::new(_PERCPU_DATA_START as usize as *mut _)
-            .expect("Percpu data should not be null.")
-    }
-
-    fn layout() -> Layout {
-        Layout::from_size_align(Self::page_count() * PAGE_SIZE, PAGE_SIZE).expect("Invalid layout.")
-    }
-
-    pub fn new<F>(allocate: F) -> Self
-    where
-        F: FnOnce(Layout) -> NonNull<u8>,
-    {
-        let data_pointer = allocate(Self::layout());
-
-        unsafe {
-            // SAFETY: The `data_pointer` is of valid length and properly aligned.
-            data_pointer
-                .copy_from_nonoverlapping(Self::data_start(), Self::page_count() * PAGE_SIZE);
-        }
-
-        Self {
-            data: data_pointer.cast(),
-        }
-    }
-
-    /// Set up the percpu area for the current CPU.
-    pub fn setup(&self) {
-        wrmsr(0xC0000101, self.data.as_ptr() as u64);
-
-        unsafe {
-            // SAFETY: %gs:0 points to the start of the percpu area.
-            asm!(
-                "movq {}, %gs:0",
-                in(reg) self.data.as_ptr(),
-                options(nostack, preserves_flags, att_syntax)
-            );
-        }
-    }
-
-    pub fn register(self: Self, cpuid: usize) {
-        PERCPU_POINTERS[cpuid].store(self.data.as_ptr(), Ordering::Release);
-    }
-
-    pub fn get_for(cpuid: usize) -> Option<NonNull<()>> {
-        let pointer = PERCPU_POINTERS[cpuid].load(Ordering::Acquire);
-        NonNull::new(pointer.cast())
-    }
-}

+ 0 - 54
arch/src/x86_64/user.rs

@@ -1,54 +0,0 @@
-use core::pin::Pin;
-
-use super::{CPU, GDTEntry};
-
-#[derive(Debug, Clone)]
-pub enum UserTLS {
-    /// TODO: This is not used yet.
-    #[allow(dead_code)]
-    TLS64(u64),
-    TLS32 {
-        base: u64,
-        desc: GDTEntry,
-    },
-}
-
-impl UserTLS {
-    /// # Return
-    /// Returns the TLS descriptor and the index of the TLS segment.
-    pub fn new32(base: u32, limit: u32, is_limit_in_pages: bool) -> (Self, u32) {
-        let flags = if is_limit_in_pages { 0xc } else { 0x4 };
-
-        (
-            Self::TLS32 {
-                base: base as u64,
-                desc: GDTEntry::new(base, limit, 0xf2, flags),
-            },
-            7,
-        )
-    }
-
-    pub fn load(&self, cpu_status: Pin<&mut CPU>) {
-        match self {
-            Self::TLS64(base) => {
-                const IA32_KERNEL_GS_BASE: u32 = 0xc0000102;
-                super::wrmsr(IA32_KERNEL_GS_BASE, *base);
-            }
-            Self::TLS32 { base, desc } => {
-                unsafe {
-                    // SAFETY: We don't move the CPUStatus object.
-                    let cpu_mut = cpu_status.get_unchecked_mut();
-                    cpu_mut.set_tls32(*desc);
-                }
-
-                const IA32_KERNEL_GS_BASE: u32 = 0xc0000102;
-                super::wrmsr(IA32_KERNEL_GS_BASE, *base);
-            }
-        }
-    }
-}
-
-pub unsafe fn load_interrupt_stack(cpu_status: Pin<&mut CPU>, stack: u64) {
-    // SAFETY: We don't move the CPUStatus object.
-    cpu_status.get_unchecked_mut().set_rsp0(stack);
-}

+ 8 - 26
build.rs

@@ -1,28 +1,10 @@
-fn main() {
-    println!("cargo:rustc-link-search=native=./build/gblibstdc++");
-    println!("cargo:rustc-link-lib=static=gblibstdc++");
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    println!("cargo:rustc-link-arg=-T{}", "link.x");
+    if let Ok(extra_link_args) = std::env::var("DEP_EONIX_HAL_EXTRA_LINK_ARGS") {
+        for arg in extra_link_args.split_whitespace() {
+            println!("cargo:rustc-link-arg={}", arg);
+        }
+    }
 
-    let headers = [
-        "rust-headers.hpp",
-        "include/kernel/hw/pci.hpp",
-    ];
-
-    let bindings = bindgen::Builder::default()
-        .use_core()
-        .ctypes_prefix("core::ffi")
-        .headers(headers)
-        .clang_arg("-I./gblibstdc++/include")
-        .clang_arg("-I./gblibc/include")
-        .clang_arg("-I./include")
-        .clang_arg("-std=c++20")
-        .opaque_type("std::.*")
-        .enable_cxx_namespaces()
-        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
-        .generate()
-        .expect("Unable to generate bindings");
-
-    let out_path = std::path::PathBuf::from(std::env::var("PWD").unwrap());
-    bindings
-        .write_to_file(out_path.join("src/bindings.rs"))
-        .expect("Couldn't write bindings!");
+    Ok(())
 }

+ 39 - 90
configure

@@ -1,41 +1,27 @@
 #!/bin/sh
-QEMU_EXECUTABLES="qemu-system-x86_64"
-GDB_EXECUTABLES="gdb x86_64-elf-gdb"
+DEFAULT_ARCH="x86_64"
 
 event() {
     printf "$1... "
 }
 
-# $1: OS NAME
-# $2: CROSS COMPILE FLAG
-# $3: FDISK_BIN
-generate_cross_compile_script() {
-cat > cross-compile.cmake <<EOF
-set(CMAKE_SYSTEM_NAME $1)
-
-set(TOOLCHAIN_PATH_AND_PREFIX "$2")
-
-set(CMAKE_C_COMPILER \${TOOLCHAIN_PATH_AND_PREFIX}gcc)
-set(CMAKE_CXX_COMPILER \${TOOLCHAIN_PATH_AND_PREFIX}c++)
-set(CMAKE_AR \${TOOLCHAIN_PATH_AND_PREFIX}ar)
-set(CMAKE_LINKER \${TOOLCHAIN_PATH_AND_PREFIX}ld)
-set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
-
-SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-# for libraries and headers in the target directories
-SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
-SET(FDISK_BIN $3)
-EOF
-}
-
-event "finding qemu"
-for item in $QEMU_EXECUTABLES; do
-    if $item --version > /dev/null 2>&1; then
-        QEMU="$item"
+if [ "$QEMU" = "" ]; then
+    event "checking default qemu"
+    QEMU="qemu-system-$DEFAULT_ARCH"
+    if $QEMU --version > /dev/null 2>&1; then
+        QEMU="qemu-system-\$(ARCH)"
         break
     fi
-done
+else
+    event "checking given qemu"
+    for item in $QEMU; do
+        if $item --version > /dev/null 2>&1; then
+            QEMU="$item"
+            break
+        fi
+    done
+    QEMU=""
+fi
 if [ "$QEMU" = "" ]; then
     echo "failed"
     exit 1
@@ -46,7 +32,7 @@ check_gdb_arch() {
     local item="$1"
     if $item --init-eval-command 'set arch' \
              --init-eval-command 'q' 2>&1 \
-             | grep 'x86-64' >/dev/null 2>&1; then
+             | grep "$DEFAULT_ARCH" >/dev/null 2>&1; then
         return 0
     else
         return 1
@@ -54,77 +40,41 @@ check_gdb_arch() {
 }
 
 if [ "$GDB" = "" ]; then
-    event "finding gdb"
-    for item in $GDB_EXECUTABLES; do
-        if check_gdb_arch "$item"; then
+    event "checking default gdb"
+    if check_gdb_arch "$DEFAULT_ARCH-elf-gdb"; then
+        GDB="\$(ARCH)-elf-gdb"
+        break
+    fi
+else
+    event 'checking given gdb'
+    for item in $GDB; do
+        if check_gdb_arch "$GDB"; then
             GDB="$item"
             break
         fi
     done
-    if [ "$GDB" = "" ]; then
-        echo "failed"
-        exit 1
-    fi
-else
-    event 'checking given gdb'
-    if ! check_gdb_arch "$GDB"; then
-        echo "failed"
-        exit 1
-    fi
+    GDB=""
+fi
+if [ "$GDB" = "" ]; then
+    echo "failed"
+    exit 1
 fi
 echo "$GDB"
 
-event "checking os type"
-OS=`uname`
-case "$OS" in
-    "Linux")
-        echo "Linux"
-        ;;
-    "Darwin")
-        echo "macOS"
-        ;;
-    *)
-        echo "unknown"
-        exit 1
-        ;;
-esac
-
-event "setting hardware acceleration type"
-case "$OS" in
-    "Linux")
-        echo "kvm"
-        # QEMU_ACCEL='-enable-kvm'
-        ;;
-    "Darwin")
-        echo "tcg"
-        QEMU_ACCEL='-accel tcg'
-        ;;
-esac
-
 event "checking util-linux fdisk"
-if [ "$FDISK_BIN" = "" -a "$CROSS_COMPILE" = "" ]; then
+if [ "$FDISK" = "" ]; then
     if ! which fdisk > /dev/null 2>&1; then
         echo "no"
         exit 1
     fi
-    FDISK_BIN=`which fdisk`
+    FDISK=`which fdisk`
 fi
 
-if ! $FDISK_BIN -v 2>&1 | grep util-linux > /dev/null 2>&1 ; then
+if ! $FDISK -v 2>&1 | grep util-linux > /dev/null 2>&1 ; then
     echo "no, fdisk is not from util-linux"
     exit 1
 else
-    echo "$FDISK_BIN"
-fi
-
-event "checking cross compiling"
-if [ "$CROSS_COMPILE" != "" ]; then
-    echo "yes"
-    CROSS_COMPILE_FLAG='--toolchain cross-compile.cmake'
-    generate_cross_compile_script "$OS" "$CROSS_COMPILE" "$FDISK_BIN"
-else
-    echo "no"
-    CROSS_COMPILE_FLAG=
+    echo "$FDISK"
 fi
 
 event "checking mkfs tool"
@@ -136,9 +86,8 @@ else
 fi
 
 cp Makefile.src Makefile
-sed -i '' -e "s|##PLACEHOLDER_1##|$QEMU|" Makefile > /dev/null 2>&1
-sed -i '' -e "s|##PLACEHOLDER_2##|$GDB|" Makefile > /dev/null 2>&1
-sed -i '' -e "s|##PLACEHOLDER_3##|$QEMU_ACCEL|" Makefile > /dev/null 2>&1
-sed -i '' -e "s|##PLACEHOLDER_4##|$CROSS_COMPILE_FLAG|" Makefile > /dev/null 2>&1
-sed -i '' -e "s|##PLACEHOLDER_5##|$FDISK_BIN|" Makefile > /dev/null 2>&1
+sed -i '' -e "s|##DEFAULT_ARCH##|$DEFAULT_ARCH|" Makefile > /dev/null 2>&1
+sed -i '' -e "s|##GDB##|$GDB|" Makefile > /dev/null 2>&1
+sed -i '' -e "s|##QEMU##|$QEMU|" Makefile > /dev/null 2>&1
+sed -i '' -e "s|##FDISK##|$FDISK|" Makefile > /dev/null 2>&1
 exit 0

+ 25 - 2
crates/buddy_allocator/src/zone.rs

@@ -30,7 +30,18 @@ where
             if current_order > order {
                 self.expand(pages_ptr, current_order, order);
             }
-            assert!(pages_ptr.is_free() && pages_ptr.is_present());
+
+            assert!(
+                pages_ptr.is_present(),
+                "Page {:?} is not present",
+                pages_ptr.into(),
+            );
+
+            assert!(
+                pages_ptr.is_free(),
+                "Page {:?} is not free",
+                pages_ptr.into(),
+            );
 
             return Some(pages_ptr);
         }
@@ -57,6 +68,18 @@ where
         let mut pfn = Into::<PFN>::into(pages_ptr);
         let mut current_order = pages_ptr.order();
 
+        assert!(
+            pages_ptr.is_present(),
+            "Freeing a page that is not present: {:?}",
+            pages_ptr.into(),
+        );
+
+        assert!(
+            !pages_ptr.is_free(),
+            "Freeing a page that is free: {:?}",
+            pages_ptr.into(),
+        );
+
         while current_order < (AREAS - 1) as u32 {
             let buddy_pfn = pfn.buddy_pfn(current_order);
             let buddy_pages_ptr = Raw::from(buddy_pfn);
@@ -111,7 +134,7 @@ where
                 .trailing_zeros()
                 .min((AREAS - 1) as u32);
 
-            while start_pfn + order as usize > end_pfn {
+            while start_pfn + (1 << order) as usize > end_pfn {
                 order -= 1;
             }
             let page_ptr = Raw::from(start_pfn);

+ 18 - 0
crates/eonix_hal/Cargo.toml

@@ -0,0 +1,18 @@
+[package]
+name = "eonix_hal"
+version = "0.1.0"
+edition = "2024"
+links = "eonix_hal"
+
+[dependencies]
+eonix_hal_traits = { path = "./eonix_hal_traits" }
+eonix_hal_macros = { path = "./eonix_hal_macros" }
+
+arch = { path = "../../arch" }
+eonix_mm = { path = "../eonix_mm" }
+eonix_sync_base = { path = "../eonix_sync/eonix_sync_base" }
+eonix_percpu = { path = "../eonix_percpu" }
+eonix_preempt = { path = "../eonix_preempt" }
+
+acpi = "5.2.0"
+cfg-if = "1.0"

+ 49 - 0
crates/eonix_hal/build.rs

@@ -0,0 +1,49 @@
+use std::path::PathBuf;
+use std::{env, fs};
+
+fn read_dependent_script(script: &str) -> Result<String, Box<dyn std::error::Error>> {
+    let content = fs::read_to_string(script)?;
+    println!("cargo:rerun-if-changed={}", script);
+    Ok(content)
+}
+
+fn process_ldscript_x86(script: &mut String) -> Result<(), Box<dyn std::error::Error>> {
+    // Otherwise `bootstrap.rs` might be ignored and not linked in.
+    println!("cargo:extra-link-args=--undefined=move_mbr --no-check-sections");
+
+    let memory = read_dependent_script("src/arch/x86_64/memory.x")?;
+    let link = read_dependent_script("src/arch/x86_64/link.x")?;
+
+    *script = memory + script;
+    script.push_str(&link);
+
+    Ok(())
+}
+
+fn process_ldscript_arch(
+    script: &mut String,
+    arch: &str,
+) -> Result<(), Box<dyn std::error::Error>> {
+    match arch {
+        "x86_64" => {
+            process_ldscript_x86(script)?;
+        }
+        _ => panic!("Unsupported architecture: {}", arch),
+    }
+
+    Ok(())
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let out_dir = PathBuf::from(env::var("OUT_DIR")?);
+    let out_script = out_dir.join("link.x");
+
+    let in_script = "src/link.x.in";
+    let mut script = read_dependent_script(in_script)?;
+
+    process_ldscript_arch(&mut script, &env::var("CARGO_CFG_TARGET_ARCH")?)?;
+
+    fs::write(out_script, script)?;
+    println!("cargo:rustc-link-search={}", out_dir.display());
+    Ok(())
+}

+ 12 - 0
crates/eonix_hal/eonix_hal_macros/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "eonix_hal_macros"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "2.0", features = ["full"] }

+ 165 - 0
crates/eonix_hal/eonix_hal_macros/src/lib.rs

@@ -0,0 +1,165 @@
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse, parse_macro_input, spanned::Spanned as _, FnArg, ItemFn};
+
+/// Define the default trap handler. The function should take exactly one argument
+/// of type `&mut TrapContext`.
+///
+/// # Usage
+/// ```no_run
+/// #[eonix_hal::default_trap_handler]
+/// fn interrupt_handler(ctx: &mut TrapContext) {
+///     println!("Trap {} received!", ctx.trap_no());
+///     // ...
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn default_trap_handler(attrs: TokenStream, item: TokenStream) -> TokenStream {
+    let item = parse_macro_input!(item as ItemFn);
+
+    if !attrs.is_empty() {
+        return parse::Error::new(
+            item.span(),
+            "`default_trap_handler` attribute does not take any arguments",
+        )
+        .into_compile_error()
+        .into();
+    }
+
+    if item.sig.inputs.len() > 1 {
+        return parse::Error::new(
+            item.span(),
+            "`default_trap_handler` only takes one argument",
+        )
+        .into_compile_error()
+        .into();
+    }
+
+    let attrs = &item.attrs;
+    let arg = item.sig.inputs.first().unwrap();
+    let block = &item.block;
+
+    quote! {
+        #(#attrs)*
+        #[no_mangle]
+        pub unsafe extern "C" fn _default_trap_handler(#arg) #block
+    }
+    .into()
+}
+
+/// Define the entry point. The function should have signature like
+///
+/// ```ignore
+/// [unsafe] fn ident(ident: eonix_hal::bootstrap::BootStrapData) -> !
+/// ```
+///
+/// # Usage
+/// ```no_run
+/// #[eonix_hal::main]
+/// fn kernel_main(data: eonix_hal::bootstrap::BootStrapData) -> ! {
+///     // ...
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn main(attrs: TokenStream, item: TokenStream) -> TokenStream {
+    let item = parse_macro_input!(item as ItemFn);
+
+    if !attrs.is_empty() {
+        return parse::Error::new(item.span(), "`main` attribute does not take any arguments")
+            .into_compile_error()
+            .into();
+    }
+
+    if item.sig.inputs.len() != 1 {
+        return parse::Error::new(item.span(), "`main` should have exactly one argument.")
+            .into_compile_error()
+            .into();
+    }
+
+    let arg_ident = match item.sig.inputs.first().unwrap() {
+        FnArg::Receiver(_) => {
+            return parse::Error::new(
+                item.span(),
+                "`main` function cannot take `self` as an argument",
+            )
+            .into_compile_error()
+            .into();
+        }
+        FnArg::Typed(ty) => &ty.pat,
+    };
+
+    let ident = &item.sig.ident;
+    let attrs = item.attrs;
+    let unsafety = item.sig.unsafety;
+    let block = &item.block;
+
+    quote! {
+        #(#attrs)*
+        #[export_name = "_eonix_hal_main"]
+        pub #unsafety fn #ident(
+            #arg_ident: eonix_hal::bootstrap::BootStrapData,
+        ) -> ! #block
+    }
+    .into()
+}
+
+/// Define the AP entry point. The function should have signature like
+///
+/// ```ignore
+/// [unsafe] fn ident(ident: eonix_mm::address::PRange) -> !
+/// ```
+///
+/// # Usage
+/// ```no_run
+/// #[eonix_hal::main]
+/// fn ap_main(stack_range: eonix_mm::address::PRange) -> ! {
+///     // ...
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn ap_main(attrs: TokenStream, item: TokenStream) -> TokenStream {
+    let item = parse_macro_input!(item as ItemFn);
+
+    if !attrs.is_empty() {
+        return parse::Error::new(
+            item.span(),
+            "`ap_main` attribute does not take any arguments",
+        )
+        .into_compile_error()
+        .into();
+    }
+
+    if item.sig.inputs.len() != 1 {
+        return parse::Error::new(item.span(), "`ap_main` should have exactly one argument.")
+            .into_compile_error()
+            .into();
+    }
+
+    let arg_ident = match item.sig.inputs.first().unwrap() {
+        FnArg::Receiver(_) => {
+            return parse::Error::new(
+                item.span(),
+                "`ap_main` function cannot take `self` as an argument",
+            )
+            .into_compile_error()
+            .into();
+        }
+        FnArg::Typed(ty) => &ty.pat,
+    };
+
+    let ident = &item.sig.ident;
+    let attrs = item.attrs;
+    let unsafety = item.sig.unsafety;
+    let block = &item.block;
+
+    quote! {
+        #(#attrs)*
+        #[export_name = "_eonix_hal_ap_main"]
+        pub #unsafety fn #ident(
+            #arg_ident: eonix_mm::address::PRange,
+        ) -> ! #block
+    }
+    .into()
+}

+ 8 - 0
crates/eonix_hal/eonix_hal_traits/Cargo.toml

@@ -0,0 +1,8 @@
+[package]
+name = "eonix_hal_traits"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+eonix_mm = { path = "../../eonix_mm" }
+bitflags = "2.6.0"

+ 41 - 0
crates/eonix_hal/eonix_hal_traits/src/context.rs

@@ -0,0 +1,41 @@
+#[doc(notable_trait)]
+pub trait RawTaskContext: Sized {
+    /// Creates a new instance of the task context with interrupt enabled and the program
+    /// counter and stack pointer set to zero (a.k.a. some invalid state).
+    ///
+    /// Using the created context without setting the program counter and stack pointer
+    /// will result in undefined behavior.
+    fn new() -> Self;
+
+    fn set_program_counter(&mut self, pc: usize);
+    fn set_stack_pointer(&mut self, sp: usize);
+
+    fn is_interrupt_enabled(&self) -> bool;
+    fn set_interrupt_enabled(&mut self, is_enabled: bool);
+
+    /// Sets the instruction pointer to the given function and prepares the context
+    /// to call it with the given argument.
+    fn call(&mut self, func: unsafe extern "C" fn(usize) -> !, arg: usize);
+
+    /// Switch the execution context from `from` to `to`.
+    ///
+    /// # Safety
+    /// This function is unsafe because it performs a context switch, which can lead to
+    /// undefined behavior if the contexts are not properly set up.
+    unsafe extern "C" fn switch(from: &mut Self, to: &mut Self);
+
+    /// Switches the execution context to `to` where we will not return.
+    ///
+    /// # Safety
+    /// This function is unsafe because it performs a context switch that does not return.
+    /// The caller must ensure that the `to` context is properly set up and that it will not
+    /// return to the caller.
+    unsafe extern "C" fn switch_to_noreturn(to: &mut Self) -> ! {
+        let mut from_ctx = Self::new();
+        unsafe {
+            Self::switch(&mut from_ctx, to);
+        }
+
+        unreachable!("We should never return from `switch_to_noreturn()`");
+    }
+}

+ 20 - 0
crates/eonix_hal/eonix_hal_traits/src/fault.rs

@@ -0,0 +1,20 @@
+use bitflags::bitflags;
+
+bitflags! {
+    #[derive(Debug)]
+    pub struct PageFaultErrorCode: u32 {
+        const NonPresent = 1;
+        const Read = 2;
+        const Write = 4;
+        const InstructionFetch = 8;
+        const UserAccess = 16;
+    }
+}
+
+#[derive(Debug)]
+pub enum Fault {
+    InvalidOp,
+    BadAccess,
+    PageFault(PageFaultErrorCode),
+    Unknown(usize),
+}

+ 6 - 0
crates/eonix_hal/eonix_hal_traits/src/fpu.rs

@@ -0,0 +1,6 @@
+#[doc(notable_trait)]
+pub trait RawFpuState: Copy {
+    fn new() -> Self;
+    fn save(&mut self);
+    fn restore(&mut self);
+}

+ 9 - 0
crates/eonix_hal/eonix_hal_traits/src/lib.rs

@@ -0,0 +1,9 @@
+#![no_std]
+#![feature(doc_notable_trait)]
+
+pub mod context;
+pub mod fault;
+pub mod fpu;
+pub mod mm;
+pub mod processor;
+pub mod trap;

+ 6 - 0
crates/eonix_hal/eonix_hal_traits/src/mm.rs

@@ -0,0 +1,6 @@
+use eonix_mm::address::PRange;
+
+pub trait Memory {
+    fn present_ram() -> impl Iterator<Item = PRange>;
+    fn free_ram() -> impl Iterator<Item = PRange>;
+}

+ 7 - 0
crates/eonix_hal/eonix_hal_traits/src/processor.rs

@@ -0,0 +1,7 @@
+use core::{ops::Deref, pin::Pin};
+
+pub trait Processor {
+    fn local() -> impl Deref<Target = Pin<&'static mut Self>>
+    where
+        Self: 'static;
+}

+ 95 - 0
crates/eonix_hal/eonix_hal_traits/src/trap.rs

@@ -0,0 +1,95 @@
+use crate::fault::Fault;
+use core::marker::PhantomData;
+
+/// A raw trap context.
+///
+/// This should be implemented by the architecture-specific trap context
+/// and will be used in the HAL crates.
+#[doc(notable_trait)]
+pub trait RawTrapContext: Copy {
+    fn new() -> Self;
+
+    fn trap_type(&self) -> TrapType;
+
+    fn get_program_counter(&self) -> usize;
+    fn get_stack_pointer(&self) -> usize;
+
+    fn set_program_counter(&mut self, pc: usize);
+    fn set_stack_pointer(&mut self, sp: usize);
+
+    fn is_interrupt_enabled(&self) -> bool;
+    fn set_interrupt_enabled(&mut self, enabled: bool);
+
+    fn is_user_mode(&self) -> bool;
+    fn set_user_mode(&mut self, user: bool);
+
+    fn set_user_return_value(&mut self, retval: usize);
+}
+
+#[doc(notable_trait)]
+pub trait TrapReturn {
+    /// Return to the context before the trap occurred.
+    ///
+    /// # Safety
+    /// This function is unsafe because the caller MUST ensure that the
+    /// context before the trap is valid, that is, that the stack pointer
+    /// points to a valid stack frame and the program counter points to some
+    /// valid instruction.
+    unsafe fn trap_return(&mut self);
+}
+
+pub trait IrqState {
+    /// Restore the IRQ state.
+    fn restore(self);
+}
+
+/// The reason that caused the trap.
+pub enum TrapType {
+    Syscall { no: usize, args: [usize; 6] },
+    Fault(Fault),
+    Irq(usize),
+    Timer,
+}
+
+/// A marker type that indicates that the type is a raw trap context.
+///
+/// # Usage
+///
+/// Check whether a type implements `RawTrapContext` using a `PhantomData` field.
+///
+/// The following code should fail to compile:
+///
+/// ```compile_fail
+/// # use eonix_hal_traits::trap::IsRawTrapContext;
+/// struct NonRawTrapContext; // Does not implement `RawTrapContext`!
+///
+/// // Compile-time error: `NonRawTrapContext` does not implement `RawTrapContext`.
+/// struct UserStruct(NonRawTrapContext, IsRawTrapContext<NonRawTrapContext>);
+/// ```
+///
+/// While the following code should compile:
+///
+/// ```no_run
+/// # use eonix_hal_traits::trap::IsRawTrapContext;
+/// struct RawTrapContextType;
+///
+/// impl RawTrapContext for RawTrapContextType {
+///     // ...
+/// #   fn new() -> Self { todo!() }
+/// #   fn trap_type() -> TrapType { todo!() }
+/// #   fn get_program_counter(&self) -> usize { todo!() }
+/// #   fn get_stack_pointer(&self) -> usize { todo!() }
+/// #   fn set_program_counter(&mut self, _: usize) { todo!() }
+/// #   fn set_stack_pointer(&mut self, _: usize) { todo!() }
+/// #   fn is_interrupt_enabled(&self) -> bool { todo!() }
+/// #   fn set_interrupt_enabled(&mut self, _: bool) { todo!() }
+/// #   fn is_user_mode(&self) -> bool { todo!() }
+/// #   fn set_user_mode(&mut self, _: bool) { todo!() }
+/// #   fn set_user_return_value(&mut self, _: usize) { todo!() }
+/// }
+///
+/// struct UserStruct(RawTrapContextType, IsRawTrapContext<RawTrapContextType>);
+/// ```
+pub struct IsRawTrapContext<T>(PhantomData<T>)
+where
+    T: RawTrapContext;

+ 14 - 0
crates/eonix_hal/src/arch/mod.rs

@@ -0,0 +1,14 @@
+cfg_if::cfg_if! {
+    if #[cfg(target_arch = "x86_64")] {
+        mod x86_64;
+        pub use x86_64::*;
+    } else if #[cfg(target_arch = "aarch64")] {
+        pub mod aarch64;
+        pub use aarch64::*;
+    } else if #[cfg(target_arch = "riscv64")] {
+        pub mod riscv64;
+        pub use riscv64::*;
+    } else {
+        compile_error!("Unsupported architecture");
+    }
+}

+ 516 - 0
crates/eonix_hal/src/arch/x86_64/bootstrap.rs

@@ -0,0 +1,516 @@
+pub(crate) mod init;
+
+use super::mm::{E820_MEM_MAP_DATA, PA_G, PA_NXE, PA_P, PA_PS, PA_RW};
+use core::arch::{global_asm, naked_asm};
+
+const KERNEL_IMAGE_PADDR: usize = 0x200000;
+const KERNEL_PML4: usize = 0x1000;
+const KERNEL_PDPT_PHYS_MAPPING: usize = 0x2000;
+const KERNEL_PDPT_KERNEL_SPACE: usize = 0x3000;
+const KERNEL_PD_KIMAGE: usize = 0x4000;
+const KERNEL_PT_KIMAGE: usize = 0x5000;
+
+#[unsafe(link_section = ".low")]
+static mut EARLY_GDT: [u64; 7] = [0; 7];
+
+#[unsafe(no_mangle)]
+#[unsafe(link_section = ".low")]
+static mut EARLY_GDT_DESCRIPTOR: (u16, u32) = (0, 0);
+
+#[unsafe(link_section = ".low")]
+static mut BIOS_IDT_DESCRIPTOR: (u16, u32) = (0, 0);
+
+unsafe extern "C" {
+    fn KIMAGE_32K_COUNT();
+    fn KIMAGE_PAGES();
+
+    fn STAGE1_MAGIC();
+    fn STAGE1_MAGIC_VALUE();
+
+    fn start_32bit() -> !;
+}
+
+global_asm!(
+    r#"
+    .pushsection .mbr, "ax", @progbits
+    .code16
+
+    .globl move_mbr
+    move_mbr:
+        xor %ax, %ax
+        mov %ax, %ds
+        mov %ax, %es
+        mov %ax, %ss
+
+        # move the MBR to 0xe00
+        mov $128, %cx # 512 bytes
+        mov $0x7c00, %si
+        mov $0x0e00, %di
+        rep movsl
+
+        ljmp $0x00, $2f
+
+    2:
+        # read the kernel stage1
+        mov $.Lread_data_packet, %si
+        mov $0x42, %ah
+        mov $0x80, %dl
+        int $0x13
+        jc .Lhalt16
+
+        # get memory size info and storage it
+        mov $0xe801, %ax
+        int $0x15
+        jc .Lhalt16
+
+        cmp $0x86, %ah # unsupported function
+        je .Lhalt16
+        cmp $0x80, %ah # invalid command
+        je .Lhalt16
+
+        jcxz 2f
+        mov %cx, %ax
+        mov %dx, %bx
+
+    2:
+        mov ${e820_data_addr}, %esp
+        movzw %ax, %eax
+        mov %eax, 8(%esp)  # 1k blocks
+        movzw %bx, %ebx
+        mov %ebx, 12(%esp) # 64k blocks
+
+        # save the destination address to es:di
+        mov %sp, %di
+        add $16, %di # buffer is 1024 - 16 bytes
+
+        # set default entry size
+        movl $20, 4(%esp)
+
+        # clear %ebx, len
+        xor %ebx, %ebx
+        mov %ebx, (%esp)
+
+    2:
+        # set the magic number to edx
+        mov $0x534D4150, %edx
+
+        # set function number to eax
+        mov $0xe820, %eax
+
+        # set default entry size
+        mov $24, %ecx
+
+        int $0x15
+
+        incl (%esp)
+        add $24, %edi
+
+        jc .Lsave_mem_fin
+        cmp $0, %ebx
+        jz .Lsave_mem_fin
+
+        cmp $24, %ecx
+        cmovnz 4(%esp), %ecx
+        mov %ecx, 4(%esp)
+
+        jmp 2b
+
+    .Lsave_mem_fin:
+        mov $0x3ff, %ax
+        mov ${bios_idt_descriptor}, %di
+        mov %ax, (%di)
+
+        xor %eax, %eax
+        mov %eax, 2(%di)
+
+        lgdt .Learly_gdt_descriptor
+
+        cli
+        # IDT descriptor is 6 0's. borrow the null gdt entry
+        lidt .Learly_gdt
+
+        # enable protection mode
+        mov %cr0, %eax
+        or $1, %eax
+        mov %eax, %cr0
+
+        ljmp $0x08, ${start_32bit}
+
+    .Lhalt16:
+        hlt
+        jmp .
+
+    .align 16
+    .Learly_gdt:
+        .8byte 0x0                # null selector
+        .8byte 0x00cf9a000000ffff # 32bit code selector
+        .8byte 0x00cf92000000ffff # 32bit data selector
+
+    .align 4
+    .Learly_gdt_descriptor:
+        .word 0x17 # size
+        .long .Learly_gdt  # address
+
+    .align 16
+    .Lread_data_packet:
+        .long  0x00070010 # .stage1 takes up 3.5K, or 7 sectors
+        .long  0x00006000 # read to 0000:6000
+        .8byte 1          # read from LBA 1
+    .popsection
+    "#,
+    start_32bit = sym start_32bit,
+    bios_idt_descriptor = sym BIOS_IDT_DESCRIPTOR,
+    e820_data_addr = sym E820_MEM_MAP_DATA,
+    options(att_syntax),
+);
+
+global_asm!(
+    r#"
+    .pushsection .stage1, "ax", @progbits
+    .code16
+    .Lhalt:
+        hlt
+        jmp .
+    
+    # scratch %eax
+    # return address should be of 2 bytes, and will be zero extended to 4 bytes
+    .Lgo_32bit:
+        cli
+        # borrow the null entry from the early gdt
+        lidt {EARLY_GDT}
+
+        # set PE bit
+        mov %cr0, %eax
+        or $1, %eax
+        mov %eax, %cr0
+
+        ljmp $0x18, $.Lgo_32bit0
+
+    .Lgo_16bit0:
+        mov $0x30, %ax
+        mov %ax, %ds
+        mov %ax, %es
+        mov %ax, %ss
+
+        lidt {BIOS_IDT_DESCRIPTOR}
+
+        mov %cr0, %eax
+        and $0xfffffffe, %eax
+        mov %eax, %cr0
+
+        ljmp $0x00, $2f
+
+    2:
+        xor %ax, %ax
+        mov %ax, %ds
+        mov %ax, %ss
+        mov %ax, %es
+
+        sti
+
+        pop %eax
+        push %ax
+        ret
+
+    .code32
+    # scratch %eax
+    # return address should be of 4 bytes, and extra 2 bytes will be popped from the stack
+    .Lgo_16bit:
+        cli
+        ljmp $0x28, $.Lgo_16bit0
+
+    .Lgo_32bit0:
+        mov $0x20, %ax
+        mov %ax, %ds
+        mov %ax, %es
+        mov %ax, %ss
+
+        pop %ax
+        movzw %ax, %eax
+        push %eax
+        ret
+
+    # build read disk packet on the stack and perform read operation
+    #
+    # read 16k to 0x8000 and then copy to destination
+    #
+    # %edi: lba start
+    # %esi: destination
+    .code32
+    read_disk:
+        push %ebp
+        mov %esp, %ebp
+
+        lea -24(%esp), %esp
+
+        mov $0x00200010, %eax # packet size 0, sector count 64
+        mov %eax, (%esp)
+
+        mov $0x08000000, %eax # destination address 0x0800:0x0000
+        mov %eax, 4(%esp)
+
+        mov %edi, 8(%esp)  # lba low 4bytes
+
+        xor %eax, %eax
+        mov %eax, 12(%esp) # lba high 2bytes
+
+        mov %esi, %edi
+        mov %esp, %esi # packet address
+
+        call .Lgo_16bit
+    .code16
+        mov $0x42, %ah
+        mov $0x80, %dl
+        int $0x13
+        jc .Lhalt
+
+        call .Lgo_32bit
+    .code32
+        # move data to destination
+        mov $0x8000, %esi
+        mov $4096, %ecx
+        rep movsl
+
+        mov %ebp, %esp
+        pop %ebp
+        ret
+
+    .align 8
+    .Lgdt_data:
+        .8byte 0x00209a0000000000 # 64bit code selector
+        .8byte 0x0000920000000000 # 64bit data selector
+        .8byte 0x00cf9a000000ffff # 32bit code selector
+        .8byte 0x00cf92000000ffff # 32bit data selector
+        .8byte 0x000f9a000000ffff # 16bit code selector
+        .8byte 0x000f92000000ffff # 16bit data selector
+
+    {start_32bit}:
+        mov $0x10, %ax
+        mov %ax, %ds
+        mov %ax, %es
+        mov %ax, %ss
+
+        mov ${STAGE1_MAGIC}, %edi
+        mov (%edi), %edi
+
+        cmp ${STAGE1_MAGIC_VALUE}, %edi
+        jne .Lhalt
+
+        mov ${EARLY_GDT_DESCRIPTOR}, %edi
+        mov $0x37, %ax
+        mov %ax, (%edi)
+
+        mov ${EARLY_GDT}, %eax
+        mov %eax, 2(%edi)
+
+        # fill in early kernel GDT
+        xchg %eax, %edi
+        xor %eax, %eax
+        mov $2, %ecx
+
+        # null segment
+        rep stosl
+
+        # other data
+        mov $.Lgdt_data, %esi
+        mov $12, %ecx
+
+        rep movsl
+
+        lgdt {EARLY_GDT_DESCRIPTOR}
+        ljmp $0x18, $2f
+
+    2:
+        mov $0x20, %ax
+        mov %ax, %ds
+        mov %ax, %es
+        mov %ax, %ss
+
+        # temporary kernel stack
+        mov $0x1000, %esp
+
+        # read kimage into memory
+        lea -16(%esp), %esp
+        mov ${KIMAGE_32K_COUNT}, %ecx
+        shl $1, %ecx
+        movl ${KERNEL_IMAGE_PADDR}, 4(%esp) # destination address
+        movl $8, (%esp) # LBA
+
+    2:
+        mov (%esp), %edi
+        mov 4(%esp), %esi
+
+        mov %ecx, %ebx
+        call read_disk
+        mov %ebx, %ecx
+
+        addl $0x4000, 4(%esp)
+        addl $32, (%esp)
+
+        loop 2b
+
+        lea 16(%esp), %esp
+
+        cld
+        xor %eax, %eax
+
+        # clear paging structures
+        mov $0x1000, %edi
+        mov $0x5000, %ecx
+        shr $2, %ecx # %ecx /= 4
+        rep stosl
+
+        # set P, RW, G
+        mov $({PA_P} | {PA_RW} | {PA_G}), %ebx
+        xor %edx, %edx
+        mov ${KERNEL_PDPT_PHYS_MAPPING}, %esi
+
+        # PML4E 0x000
+        # we need the first 1GB identically mapped
+        # so that we won't trigger a triple fault after
+        # enabling paging
+        mov ${KERNEL_PML4}, %edi
+        call fill_pxe
+
+        # PML4E 0xff0
+        mov $({PA_NXE} >> 32), %edx
+        lea 0xff0(%edi), %edi
+        call fill_pxe
+        xor %edx, %edx
+
+        # setup PDPT for physical memory mapping
+        mov ${KERNEL_PDPT_PHYS_MAPPING}, %edi
+
+        # set PS
+        or ${PA_PS}, %ebx
+        mov $512, %ecx
+        xor %esi, %esi
+    2:
+        call fill_pxe
+        lea 8(%edi), %edi
+        add $0x40000000, %esi # 1GB
+        adc $0, %edx
+        loop 2b
+
+        xor %edx, %edx
+
+        # PML4E 0xff8
+        mov ${KERNEL_PDPT_KERNEL_SPACE}, %esi
+        mov ${KERNEL_PML4}, %edi
+        lea 0xff8(%edi), %edi
+        # clear PS
+        and $(~{PA_PS}), %ebx
+        call fill_pxe
+
+        # PDPTE 0xff8
+        mov ${KERNEL_PDPT_KERNEL_SPACE}, %edi
+        lea 0xff8(%edi), %edi
+        mov ${KERNEL_PD_KIMAGE}, %esi
+        call fill_pxe
+
+        # PDE 0xff0
+        mov ${KERNEL_PD_KIMAGE}, %edi
+        lea 0xff0(%edi), %edi
+        mov ${KERNEL_PT_KIMAGE}, %esi # 0x104000
+        call fill_pxe
+
+        # fill PT (kernel image)
+        mov ${KERNEL_PT_KIMAGE}, %edi
+        mov ${KERNEL_IMAGE_PADDR}, %esi
+
+        mov ${KIMAGE_PAGES}, %ecx
+
+    2:
+        call fill_pxe
+        lea 8(%edi), %edi
+        lea 0x1000(%esi), %esi
+        loop 2b
+
+        # set msr
+        mov $0xc0000080, %ecx
+        rdmsr
+        or $0x901, %eax # set LME, NXE, SCE
+        wrmsr
+
+        # set cr4
+        mov %cr4, %eax
+        or $0xa0, %eax # set PAE, PGE
+        mov %eax, %cr4
+
+        # load new page table
+        mov ${KERNEL_PML4}, %eax
+        mov %eax, %cr3
+
+        mov %cr0, %eax
+        // SET PE, WP, PG
+        or $0x80010001, %eax
+        mov %eax, %cr0
+
+        ljmp $0x08, $2f
+
+    # %ebx: attribute low
+    # %edx: attribute high
+    # %esi: page physical address
+    # %edi: page x entry address
+    fill_pxe:
+        lea (%ebx, %esi, 1), %eax
+        mov %eax, (%edi)
+        mov %edx, 4(%edi)
+
+        ret
+
+    .code64
+    2:
+        jmp {start_64bit}
+    
+    .popsection
+    "#,
+    EARLY_GDT = sym EARLY_GDT,
+    EARLY_GDT_DESCRIPTOR = sym EARLY_GDT_DESCRIPTOR,
+    BIOS_IDT_DESCRIPTOR = sym BIOS_IDT_DESCRIPTOR,
+    KIMAGE_32K_COUNT = sym KIMAGE_32K_COUNT,
+    KIMAGE_PAGES = sym KIMAGE_PAGES,
+    STAGE1_MAGIC = sym STAGE1_MAGIC,
+    STAGE1_MAGIC_VALUE = sym STAGE1_MAGIC_VALUE,
+    KERNEL_IMAGE_PADDR = const KERNEL_IMAGE_PADDR,
+    KERNEL_PML4 = const KERNEL_PML4,
+    PA_P = const PA_P,
+    PA_RW = const PA_RW,
+    PA_G = const PA_G,
+    PA_PS = const PA_PS,
+    PA_NXE = const PA_NXE,
+    KERNEL_PDPT_PHYS_MAPPING = const KERNEL_PDPT_PHYS_MAPPING,
+    KERNEL_PDPT_KERNEL_SPACE = const KERNEL_PDPT_KERNEL_SPACE,
+    KERNEL_PD_KIMAGE = const KERNEL_PD_KIMAGE,
+    KERNEL_PT_KIMAGE = const KERNEL_PT_KIMAGE,
+    start_64bit = sym start_64bit,
+    start_32bit = sym start_32bit,
+    options(att_syntax),
+);
+
+#[unsafe(naked)]
+pub unsafe extern "C" fn start_64bit() {
+    naked_asm!(
+        "mov $0x10, %ax",
+        "mov %ax, %ds",
+        "mov %ax, %es",
+        "mov %ax, %ss",
+        "",
+        "mov ${kernel_identical_base}, %rax",
+        "mov ${stack_paddr}, %rsp",
+        "add %rax, %rsp",
+        "",
+        "xor %rbp, %rbp", // Clear previous stack frame
+        "push %rbp", // NULL return address
+        "",
+        "mov ${e820_data_addr}, %rdi",
+        "add %rax, %rdi",
+        "",
+        "jmp {kernel_init}",
+        kernel_identical_base = const 0xffffff0000000000u64,
+        stack_paddr = const 0x80000,
+        e820_data_addr = sym E820_MEM_MAP_DATA,
+        kernel_init = sym init::kernel_init,
+        options(att_syntax)
+    )
+}

+ 374 - 0
crates/eonix_hal/src/arch/x86_64/bootstrap/init.rs

@@ -0,0 +1,374 @@
+use crate::{
+    arch::{
+        bootstrap::{EARLY_GDT_DESCRIPTOR, KERNEL_PML4},
+        cpu::CPU,
+        mm::{ArchPhysAccess, GLOBAL_PAGE_TABLE, V_KERNEL_BSS_START},
+    },
+    bootstrap::BootStrapData,
+    mm::{ArchMemory, ArchPagingMode, BasicPageAlloc, BasicPageAllocRef, ScopedAllocator},
+};
+use acpi::{platform::ProcessorState, AcpiHandler, AcpiTables, PhysicalMapping, PlatformInfo};
+use arch::wrmsr;
+use core::{
+    alloc::Allocator,
+    arch::{asm, global_asm},
+    cell::RefCell,
+    hint::spin_loop,
+    sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering},
+};
+use eonix_hal_traits::mm::Memory;
+use eonix_mm::{
+    address::{Addr as _, PAddr, PRange, PhysAccess, VRange},
+    page_table::{PageAttribute, PagingMode, PTE as _},
+    paging::{Page, PageAccess, PageAlloc, PAGE_SIZE},
+};
+use eonix_percpu::PercpuArea;
+
+static BSP_PAGE_ALLOC: AtomicPtr<RefCell<BasicPageAlloc>> = AtomicPtr::new(core::ptr::null_mut());
+
+static AP_COUNT: AtomicUsize = AtomicUsize::new(0);
+static AP_STACK: AtomicUsize = AtomicUsize::new(0);
+static AP_SEM: AtomicBool = AtomicBool::new(false);
+
+global_asm!(
+    r#"
+    .pushsection .stage1.smp, "ax", @progbits
+    .code16
+    ljmp $0x0, $2f
+
+    2:
+    lgdt {early_gdt_descriptor}
+    mov $0xc0000080, %ecx
+    rdmsr
+    or $0x901, %eax # set LME, NXE, SCE
+    wrmsr
+
+    mov %cr4, %eax
+    or $0xa0, %eax # set PAE, PGE
+    mov %eax, %cr4
+
+    mov ${kernel_pml4}, %eax
+    mov %eax, %cr3
+
+    mov %cr0, %eax
+    or $0x80010001, %eax # set PE, WP, PG
+    mov %eax, %cr0
+
+    ljmp $0x08, $2f
+
+    .code64
+    2:
+    mov $0x10, %ax
+    mov %ax, %ds
+    mov %ax, %es
+    mov %ax, %ss
+
+    xor %rax, %rax
+    inc %rax
+    mov ${ap_semaphore}, %rcx
+
+    2:
+    xchg %rax, (%rcx) # AcqRel
+    cmp $0, %rax
+    je 2f
+    pause
+    jmp 2b
+
+    2:
+    mov ${ap_stack}, %rcx
+
+    2:
+    mov (%rcx), %rsp # Acquire
+    cmp $0, %rsp
+    jne 2f
+    pause
+    jmp 2b
+
+    2:
+    xor %rbp, %rbp
+    mov %rbp, (%rcx) # Relaxed
+
+    mov ${ap_semaphore}, %rcx
+    xchg %rax, (%rcx) # Release
+
+    mov %rsp, %rdi
+    push %rbp # NULL return address
+    mov ${ap_entry}, %rax
+    jmp *%rax
+
+    .popsection
+    "#,
+    early_gdt_descriptor = sym EARLY_GDT_DESCRIPTOR,
+    kernel_pml4 = const KERNEL_PML4,
+    ap_semaphore = sym AP_SEM,
+    ap_stack = sym AP_STACK,
+    ap_entry = sym ap_entry,
+    options(att_syntax),
+);
+
+fn enable_sse() {
+    unsafe {
+        asm!(
+            "mov %cr0, %rax",
+            "and $(~0xc), %rax",
+            "or $0x22, %rax",
+            "mov %rax, %cr0",
+            "mov %cr4, %rax",
+            "or $0x600, %rax",
+            "mov %rax, %cr4",
+            "fninit",
+            out("rax") _,
+            options(att_syntax, nomem, nostack)
+        )
+    }
+}
+
+fn setup_cpu(alloc: impl PageAlloc) {
+    let mut percpu_area = PercpuArea::new(|layout| {
+        // TODO: Use page size defined in `arch`.
+        let page_count = layout.size().div_ceil(PAGE_SIZE);
+        let page = Page::alloc_at_least_in(page_count, alloc);
+
+        let ptr = ArchPhysAccess::get_ptr_for_page(&page).cast();
+        page.into_raw();
+
+        ptr
+    });
+
+    percpu_area.setup(|pointer| {
+        wrmsr(0xC0000101, pointer.addr().get() as u64);
+
+        unsafe {
+            // SAFETY: %gs:0 points to the start of the percpu area.
+            asm!(
+                "movq {}, %gs:0",
+                in(reg) pointer.addr().get(),
+                options(nostack, preserves_flags, att_syntax)
+            );
+        }
+    });
+
+    let mut cpu = CPU::local();
+    unsafe {
+        // SAFETY: Preemption is disabled and interrupt MUST be disabled since
+        //         we are doing this in the kernel initialization phase.
+        cpu.as_mut().init();
+    }
+
+    percpu_area.register(cpu.cpuid());
+}
+
+fn setup_pic() {
+    // TODO: Remove this when we have completely switched to APIC.
+    pub struct Port8 {
+        no: u16,
+    }
+
+    impl Port8 {
+        pub const fn new(no: u16) -> Self {
+            Self { no }
+        }
+
+        pub fn write(&self, data: u8) {
+            arch::outb(self.no, data)
+        }
+    }
+
+    const PIC1_COMMAND: Port8 = Port8::new(0x20);
+    const PIC1_DATA: Port8 = Port8::new(0x21);
+    const PIC2_COMMAND: Port8 = Port8::new(0xA0);
+    const PIC2_DATA: Port8 = Port8::new(0xA1);
+
+    // Initialize PIC
+    PIC1_COMMAND.write(0x11); // edge trigger mode
+    PIC1_DATA.write(0x20); // IRQ 0-7 offset
+    PIC1_DATA.write(0x04); // cascade with slave PIC
+    PIC1_DATA.write(0x01); // no buffer mode
+
+    PIC2_COMMAND.write(0x11); // edge trigger mode
+    PIC2_DATA.write(0x28); // IRQ 8-15 offset
+    PIC2_DATA.write(0x02); // cascade with master PIC
+    PIC2_DATA.write(0x01); // no buffer mode
+
+    // Allow all IRQs
+    PIC1_DATA.write(0x0);
+    PIC2_DATA.write(0x0);
+}
+
+fn bootstrap_smp(alloc: impl Allocator, page_alloc: &RefCell<BasicPageAlloc>) {
+    #[derive(Clone)]
+    struct Handler;
+
+    impl AcpiHandler for Handler {
+        unsafe fn map_physical_region<T>(
+            &self,
+            physical_address: usize,
+            size: usize,
+        ) -> PhysicalMapping<Self, T> {
+            unsafe {
+                PhysicalMapping::new(
+                    physical_address,
+                    ArchPhysAccess::as_ptr(PAddr::from(physical_address)),
+                    size,
+                    size,
+                    self.clone(),
+                )
+            }
+        }
+
+        fn unmap_physical_region<T>(_: &PhysicalMapping<Self, T>) {}
+    }
+
+    let acpi_tables = unsafe {
+        // SAFETY: Probing for RSDP in BIOS memory should be fine.
+        AcpiTables::search_for_rsdp_bios(Handler).unwrap()
+    };
+
+    let platform_info = PlatformInfo::new_in(&acpi_tables, &alloc).unwrap();
+    let processor_info = platform_info.processor_info.unwrap();
+
+    let ap_count = processor_info
+        .application_processors
+        .iter()
+        .filter(|ap| !matches!(ap.state, ProcessorState::Disabled))
+        .count();
+
+    unsafe {
+        CPU::local().bootstrap_cpus();
+    }
+
+    for current_count in 0..ap_count {
+        let stack_range = {
+            let page_alloc = BasicPageAllocRef::new(&page_alloc);
+
+            let ap_stack = Page::alloc_order_in(3, page_alloc);
+            let stack_range = ap_stack.range();
+            ap_stack.into_raw();
+
+            stack_range
+        };
+
+        // SAFETY: All the APs can see the allocator work done before this point.
+        let old = BSP_PAGE_ALLOC.swap((&raw const *page_alloc) as *mut _, Ordering::Release);
+        assert!(
+            old.is_null(),
+            "BSP_PAGE_ALLOC should be null before we release it"
+        );
+
+        // SAFETY: The AP reading the stack will see the allocation work.
+        while let Err(_) = AP_STACK.compare_exchange_weak(
+            0,
+            stack_range.end().addr(),
+            Ordering::Release,
+            Ordering::Relaxed,
+        ) {
+            // Spin until we can set the stack pointer for the AP.
+            spin_loop();
+        }
+
+        spin_loop();
+
+        // SAFETY: Make sure if we read the AP count, the allocator MUST have been released.
+        while AP_COUNT.load(Ordering::Acquire) == current_count {
+            // Wait for the AP to finish its initialization.
+            spin_loop();
+        }
+
+        // SAFETY: We acquire the work done by the AP.
+        let old = BSP_PAGE_ALLOC.swap(core::ptr::null_mut(), Ordering::Acquire);
+        assert_eq!(
+            old as *const _, &raw const *page_alloc,
+            "We should read the previously saved allocator"
+        );
+    }
+}
+
+pub extern "C" fn kernel_init() -> ! {
+    let global_page_table = &GLOBAL_PAGE_TABLE;
+    let paging_levels = ArchPagingMode::LEVELS;
+
+    enable_sse();
+
+    let real_allocator = RefCell::new(BasicPageAlloc::new());
+    let alloc = BasicPageAllocRef::new(&real_allocator);
+
+    unsafe extern "C" {
+        fn BSS_LENGTH();
+    }
+
+    for range in ArchMemory::free_ram() {
+        real_allocator.borrow_mut().add_range(range);
+    }
+
+    // Map kernel BSS
+    for pte in global_page_table.iter_kernel_in(
+        VRange::from(V_KERNEL_BSS_START).grow(BSS_LENGTH as usize),
+        paging_levels,
+        &alloc,
+    ) {
+        let attr = PageAttribute::PRESENT
+            | PageAttribute::WRITE
+            | PageAttribute::READ
+            | PageAttribute::HUGE
+            | PageAttribute::GLOBAL;
+
+        let page = Page::alloc_in(&alloc);
+        pte.set(page.into_raw(), attr.into());
+    }
+
+    unsafe {
+        // SAFETY: We've just mapped the area with sufficient length.
+        core::ptr::write_bytes(V_KERNEL_BSS_START.addr() as *mut (), 0, BSS_LENGTH as usize);
+    }
+
+    setup_cpu(&alloc);
+    setup_pic();
+
+    ScopedAllocator::new(&mut [0; 1024])
+        .with_alloc(|mem_alloc| bootstrap_smp(mem_alloc, &real_allocator));
+
+    unsafe extern "Rust" {
+        fn _eonix_hal_main(_: BootStrapData) -> !;
+    }
+
+    let bootstrap_data = BootStrapData {
+        early_stack: PRange::new(PAddr::from(0x6000), PAddr::from(0x80000)),
+        allocator: Some(real_allocator),
+    };
+
+    unsafe {
+        _eonix_hal_main(bootstrap_data);
+    }
+}
+
+pub extern "C" fn ap_entry(stack_bottom: PAddr) -> ! {
+    let stack_range = PRange::new(stack_bottom - (1 << 3) * PAGE_SIZE, stack_bottom);
+
+    {
+        // SAFETY: Acquire all the work done by the BSP and other APs.
+        let alloc = loop {
+            let alloc = BSP_PAGE_ALLOC.swap(core::ptr::null_mut(), Ordering::AcqRel);
+
+            if !alloc.is_null() {
+                break alloc;
+            }
+        };
+
+        let ref_alloc = unsafe { &*alloc };
+        setup_cpu(BasicPageAllocRef::new(&ref_alloc));
+
+        // SAFETY: Release our allocation work.
+        BSP_PAGE_ALLOC.store(alloc, Ordering::Release);
+    }
+
+    // SAFETY: Make sure the allocator is set before we increment the AP count.
+    AP_COUNT.fetch_add(1, Ordering::Release);
+
+    unsafe extern "Rust" {
+        fn _eonix_hal_ap_main(stack_range: PRange) -> !;
+    }
+
+    unsafe {
+        _eonix_hal_ap_main(stack_range);
+    }
+}

+ 46 - 77
arch/src/x86_64/context.rs → crates/eonix_hal/src/arch/x86_64/context.rs

@@ -1,6 +1,7 @@
 use core::arch::naked_asm;
+use eonix_hal_traits::context::RawTaskContext;
 
-/// Necessary hardware states of task for context switch
+/// Necessary hardware states of task for doing context switches.
 #[repr(C)]
 #[derive(Debug, Default)]
 pub struct TaskContext {
@@ -34,73 +35,8 @@ impl TaskContext {
         }
     }
 
-    pub fn ip(&mut self, ip: usize) {
-        self.rip = ip as u64;
-    }
-
-    pub fn sp(&mut self, sp: usize) {
-        self.rsp = sp as u64;
-    }
-
-    pub fn call1(&mut self, func: unsafe extern "C" fn(usize) -> !, arg: [usize; 1]) {
-        self.ip(Self::do_call as _);
-        self.rbp = func as _;
-        self.r12 = arg[0] as _;
-    }
-
-    pub fn call2(&mut self, func: unsafe extern "C" fn(usize, usize) -> !, arg: [usize; 2]) {
-        self.ip(Self::do_call as _);
-        self.rbp = func as _;
-
-        (self.r12, self.r13) = (arg[0] as _, arg[1] as _);
-    }
-
-    pub fn call3(&mut self, func: unsafe extern "C" fn(usize, usize, usize) -> !, arg: [usize; 3]) {
-        self.ip(Self::do_call as _);
-        self.rbp = func as _;
-
-        (self.r12, self.r13, self.r14) = (arg[0] as _, arg[1] as _, arg[2] as _);
-    }
-
-    pub fn call4(
-        &mut self,
-        func: unsafe extern "C" fn(usize, usize, usize, usize) -> !,
-        arg: [usize; 4],
-    ) {
-        self.ip(Self::do_call as _);
-        self.rbp = func as _;
-
-        (self.r12, self.r13, self.r14, self.r15) =
-            (arg[0] as _, arg[1] as _, arg[2] as _, arg[3] as _);
-    }
-
-    pub fn call5(
-        &mut self,
-        func: unsafe extern "C" fn(usize, usize, usize, usize, usize) -> !,
-        arg: [usize; 5],
-    ) {
-        self.ip(Self::do_call as _);
-        self.rbp = func as _;
-
-        (self.r12, self.r13, self.r14, self.r15, self.rbx) = (
-            arg[0] as _,
-            arg[1] as _,
-            arg[2] as _,
-            arg[3] as _,
-            arg[4] as _,
-        );
-    }
-
-    pub fn interrupt(&mut self, is_enabled: bool) {
-        if is_enabled {
-            self.rflags |= 0x200; // IF = 1
-        } else {
-            self.rflags &= !0x200; // IF = 0
-        }
-    }
-
-    #[naked]
-    pub unsafe extern "C" fn switch(from: &mut Self, to: &mut Self) {
+    #[unsafe(naked)]
+    unsafe extern "C" fn _switch(from: &mut Self, to: &mut Self) {
         naked_asm!(
             "pop %rax",
             "pushf",
@@ -132,19 +68,52 @@ impl TaskContext {
         );
     }
 
-    #[naked]
-    /// Maximum of 5 arguments supported.
+    #[unsafe(naked)]
     unsafe extern "C" fn do_call() -> ! {
         naked_asm!(
             "mov %r12, %rdi",
-            "mov %r13, %rsi",
-            "mov %r14, %rdx",
-            "mov %r15, %rcx",
-            "mov %rbx, %r8",
-            "mov %rbp, %rax",
-            "xor %rbp, %rbp",
-            "jmp *%rax",
+            "push %rbp", // NULL return address.
+            "jmp *%rbx",
             options(att_syntax),
         );
     }
 }
+
+impl RawTaskContext for TaskContext {
+    fn new() -> Self {
+        Self::new()
+    }
+
+    fn set_program_counter(&mut self, pc: usize) {
+        self.rip = pc as u64;
+    }
+
+    fn set_stack_pointer(&mut self, sp: usize) {
+        self.rsp = sp as u64;
+    }
+
+    fn is_interrupt_enabled(&self) -> bool {
+        (self.rflags & 0x200) != 0 // IF = 1
+    }
+
+    fn set_interrupt_enabled(&mut self, is_enabled: bool) {
+        if is_enabled {
+            self.rflags |= 0x200; // IF = 1
+        } else {
+            self.rflags &= !0x200; // IF = 0
+        }
+    }
+
+    fn call(&mut self, func: unsafe extern "C" fn(usize) -> !, arg: usize) {
+        self.set_program_counter(Self::do_call as _);
+        self.rbx = func as _;
+        self.r12 = arg as _;
+        self.rbp = 0; // NULL previous stack frame
+    }
+
+    unsafe extern "C" fn switch(from: &mut Self, to: &mut Self) {
+        unsafe {
+            Self::_switch(from, to);
+        }
+    }
+}

+ 132 - 60
arch/src/x86_64/init.rs → crates/eonix_hal/src/arch/x86_64/cpu.rs

@@ -1,5 +1,12 @@
-use super::{enable_sse, GDTEntry, InterruptControl, GDT};
-use core::{pin::Pin, ptr::addr_of};
+use super::gdt::{GDTEntry, GDT};
+use super::interrupt::InterruptControl;
+use core::marker::PhantomPinned;
+use core::pin::Pin;
+use eonix_preempt::PreemptGuard;
+use eonix_sync_base::LazyLock;
+
+#[eonix_percpu::define_percpu]
+static LOCAL_CPU: LazyLock<CPU> = LazyLock::new(CPU::new);
 
 #[repr(C)]
 #[derive(Debug, Clone, Copy)]
@@ -20,27 +27,18 @@ pub(crate) struct TSS {
     _reserved5: u32,
     _reserved6: u16,
     iomap_base: u16,
+    _pinned: PhantomPinned,
 }
 
-impl TSS {
-    pub fn new() -> Self {
-        Self {
-            _reserved1: 0,
-            rsp: [TSS_SP { low: 0, high: 0 }; 3],
-            _reserved2: 0,
-            _reserved3: 0,
-            ist: [TSS_SP { low: 0, high: 0 }; 7],
-            _reserved4: 0,
-            _reserved5: 0,
-            _reserved6: 0,
-            iomap_base: 0,
-        }
-    }
-
-    pub fn set_rsp0(&mut self, rsp: u64) {
-        self.rsp[0].low = rsp as u32;
-        self.rsp[0].high = (rsp >> 32) as u32;
-    }
+#[derive(Debug, Clone)]
+pub enum UserTLS {
+    /// TODO: This is not used yet.
+    #[allow(dead_code)]
+    TLS64(u64),
+    TLS32 {
+        base: u64,
+        desc: GDTEntry,
+    },
 }
 
 /// Architecture-specific cpu status data.
@@ -48,7 +46,23 @@ pub struct CPU {
     cpuid: usize,
     gdt: GDT,
     tss: TSS,
-    pub interrupt: InterruptControl,
+    interrupt: InterruptControl,
+}
+
+impl UserTLS {
+    /// # Return
+    /// Returns the TLS descriptor and the index of the TLS segment.
+    pub fn new32(base: u32, limit: u32, is_limit_in_pages: bool) -> (Self, u32) {
+        let flags = if is_limit_in_pages { 0xc } else { 0x4 };
+
+        (
+            Self::TLS32 {
+                base: base as u64,
+                desc: GDTEntry::new(base, limit, 0xf2, flags),
+            },
+            7,
+        )
+    }
 }
 
 impl CPU {
@@ -67,19 +81,29 @@ impl CPU {
     ///
     /// # Safety
     /// Make sure preemption and interrupt are disabled before calling this function.
-    pub unsafe fn init(self: Pin<&mut Self>) {
-        enable_sse();
-
-        // SAFETY: We don't move the object.
-        let self_mut = self.get_unchecked_mut();
+    pub(crate) unsafe fn init(mut self: Pin<&mut Self>) {
+        let tss = &self.as_ref().get_ref().tss;
+        let tss_addr = tss as *const _ as u64;
+
+        let mut gdt = unsafe {
+            // SAFETY: We don't move the field out.
+            self.as_mut().map_unchecked_mut(|me| &mut me.gdt)
+        };
+
+        unsafe {
+            // SAFETY: We don't move `gdt` out.
+            gdt.as_mut().get_unchecked_mut().set_tss(tss_addr as u64);
+        }
+        gdt.load();
 
-        let tss_addr = addr_of!(self_mut.tss);
-        self_mut.gdt.set_tss(tss_addr as u64);
-        self_mut.gdt.load();
+        let mut interrupt = unsafe {
+            // SAFETY: We don't move the field out.
+            self.as_mut().map_unchecked_mut(|me| &mut me.interrupt)
+        };
 
         // SAFETY: `self` is pinned, so are its fields.
-        Pin::new_unchecked(&mut self_mut.interrupt).setup_idt();
-        self_mut.interrupt.setup_timer();
+        interrupt.as_mut().setup_idt();
+        interrupt.as_mut().setup_timer();
     }
 
     /// Bootstrap all CPUs.
@@ -88,45 +112,96 @@ impl CPU {
         self.interrupt.send_sipi();
     }
 
-    pub unsafe fn set_rsp0(&mut self, rsp: u64) {
-        self.tss.set_rsp0(rsp);
+    pub unsafe fn load_interrupt_stack(self: Pin<&mut Self>, rsp: u64) {
+        unsafe {
+            self.map_unchecked_mut(|me| &mut me.tss).set_rsp0(rsp);
+        }
     }
 
-    pub unsafe fn set_tls32(&mut self, desc: GDTEntry) {
-        self.gdt.set_tls32(desc);
+    pub fn set_tls32(self: Pin<&mut Self>, user_tls: &UserTLS) {
+        let UserTLS::TLS32 { desc, base } = user_tls else {
+            unimplemented!("TLS64 is not supported yet")
+        };
+
+        unsafe {
+            // SAFETY: We don't move the GDT object.
+            self.get_unchecked_mut().gdt.set_tls32(*desc);
+        }
+
+        const IA32_KERNEL_GS_BASE: u32 = 0xc0000102;
+        arch::wrmsr(IA32_KERNEL_GS_BASE, *base);
     }
 
     pub fn cpuid(&self) -> usize {
         self.cpuid
     }
+
+    pub fn end_of_interrupt(self: Pin<&mut Self>) {
+        unsafe {
+            // SAFETY: We don't move the `interrupt` field out.
+            self.map_unchecked_mut(|me| &mut me.interrupt)
+                .end_of_interrupt();
+        }
+    }
+
+    pub fn local() -> PreemptGuard<Pin<&'static mut Self>> {
+        unsafe {
+            // SAFETY: We pass the reference into a `PreemptGuard`, which ensures
+            //         that preemption is disabled.
+            PreemptGuard::new(Pin::new_unchecked(LOCAL_CPU.as_mut().get_mut()))
+        }
+    }
+}
+
+impl TSS {
+    pub fn new() -> Self {
+        Self {
+            _reserved1: 0,
+            rsp: [TSS_SP { low: 0, high: 0 }; 3],
+            _reserved2: 0,
+            _reserved3: 0,
+            ist: [TSS_SP { low: 0, high: 0 }; 7],
+            _reserved4: 0,
+            _reserved5: 0,
+            _reserved6: 0,
+            iomap_base: 0,
+            _pinned: PhantomPinned,
+        }
+    }
+
+    pub fn set_rsp0(self: Pin<&mut Self>, rsp: u64) {
+        unsafe {
+            // SAFETY: We don't move the TSS object.
+            let me = self.get_unchecked_mut();
+            me.rsp[0].low = rsp as u32;
+            me.rsp[0].high = (rsp >> 32) as u32;
+        }
+    }
 }
 
 #[macro_export]
 macro_rules! define_smp_bootstrap {
     ($cpu_count:literal, $ap_entry:ident, $alloc_kstack:tt) => {
-        #[no_mangle]
         static BOOT_SEMAPHORE: core::sync::atomic::AtomicU64 =
             core::sync::atomic::AtomicU64::new(0);
-        #[no_mangle]
         static BOOT_STACK: core::sync::atomic::AtomicU64 =
             core::sync::atomic::AtomicU64::new(0);
 
-        #[no_mangle]
         static CPU_COUNT: core::sync::atomic::AtomicU64 =
-            core::sync::atomic::AtomicU64::new(0);
+            core::sync::atomic::AtomicU64::new(1);
 
         core::arch::global_asm!(
             r#"
-        .pushsection .stage1.smp
+        .pushsection .stage1.smp, "ax", @progbits
         .code16
         .globl ap_bootstrap
         .type ap_bootstrap, @function
         ap_bootstrap:
-            ljmp $0x0, $.Lap1
+            ljmp $0x0, $2f
 
-        .Lap1:
+        2:
             # we use the shared gdt for cpu bootstrapping
-            lgdt .Lshared_gdt_desc
+            lgdt EARLY_GDT_DESCRIPTOR
 
             # set msr
             mov $0xc0000080, %ecx
@@ -148,14 +223,10 @@ macro_rules! define_smp_bootstrap {
             or $0x80010001, %eax
             mov %eax, %cr0
 
-            ljmp $0x08, $.Lap_bootstrap_end
-
-        .align 16
-        .Lshared_gdt_desc:
-            .8byte 0x0000000000005f
+            ljmp $0x08, $2f
 
         .code64
-        .Lap_bootstrap_end:
+        2:
             mov $0x10, %ax
             mov %ax, %ds
             mov %ax, %es
@@ -164,21 +235,21 @@ macro_rules! define_smp_bootstrap {
             xor %rsp, %rsp
             xor %rax, %rax
             inc %rax
-        1:
+        2:
             xchg %rax, {BOOT_SEMAPHORE}
             cmp $0, %rax
-            je 1f
+            je 2f
             pause
-            jmp 1b
+            jmp 2b
 
-        1:
+        2:
             mov {BOOT_STACK}, %rsp # Acquire
             cmp $0, %rsp
-            jne 1f
+            jne 2f
             pause
-            jmp 1b
+            jmp 2b
 
-        1:
+        2:
             xor %rax, %rax
             mov %rax, {BOOT_STACK} # Release
             xchg %rax, {BOOT_SEMAPHORE}
@@ -187,10 +258,11 @@ macro_rules! define_smp_bootstrap {
 
             xor %rbp, %rbp
             push %rbp # NULL return address
-            jmp {AP_ENTRY}
+            mov ${AP_ENTRY}, %rax
+            jmp *%rax
             .popsection
             "#,
-            KERNEL_PML4 = const 0x2000,
+            KERNEL_PML4 = const 0x1000,
             BOOT_SEMAPHORE = sym BOOT_SEMAPHORE,
             BOOT_STACK = sym BOOT_STACK,
             CPU_COUNT = sym CPU_COUNT,
@@ -200,7 +272,7 @@ macro_rules! define_smp_bootstrap {
 
         pub unsafe fn wait_cpus_online() {
             use core::sync::atomic::Ordering;
-            while CPU_COUNT.load(Ordering::Acquire) != $cpu_count - 1 {
+            while CPU_COUNT.load(Ordering::Acquire) != $cpu_count {
                 if BOOT_STACK.load(Ordering::Acquire) == 0 {
                     let stack_bottom = $alloc_kstack as u64;
                     BOOT_STACK.store(stack_bottom, Ordering::Release);

+ 28 - 23
arch/src/x86_64/gdt.rs → crates/eonix_hal/src/arch/x86_64/gdt.rs

@@ -1,11 +1,11 @@
-use crate::TSS;
-use core::arch::asm;
+use super::cpu::TSS;
+use core::{arch::asm, marker::PhantomPinned};
 
 #[repr(transparent)]
 #[derive(Debug, Clone, Copy)]
 pub struct GDTEntry(u64);
 
-pub struct GDT([GDTEntry; GDT::LEN]);
+pub struct GDT([GDTEntry; GDT::LEN], PhantomPinned);
 
 impl GDTEntry {
     const NULL: Self = Self(0);
@@ -50,18 +50,21 @@ impl GDT {
     const TSS_INDEX: usize = 8;
 
     pub fn new() -> Self {
-        Self([
-            GDTEntry::NULL,
-            GDTEntry::KERNEL_CODE64,
-            GDTEntry::KERNEL_DATA64,
-            GDTEntry::USER_CODE64,
-            GDTEntry::USER_DATA64,
-            GDTEntry::USER_CODE32,
-            GDTEntry::USER_DATA32,
-            GDTEntry::NULL, // User TLS 32bit
-            GDTEntry::NULL, // TSS Descriptor Low
-            GDTEntry::NULL, // TSS Descriptor High
-        ])
+        Self(
+            [
+                GDTEntry::NULL,
+                GDTEntry::KERNEL_CODE64,
+                GDTEntry::KERNEL_DATA64,
+                GDTEntry::USER_CODE64,
+                GDTEntry::USER_DATA64,
+                GDTEntry::USER_CODE32,
+                GDTEntry::USER_DATA32,
+                GDTEntry::NULL, // User TLS 32bit
+                GDTEntry::NULL, // TSS Descriptor Low
+                GDTEntry::NULL, // TSS Descriptor High
+            ],
+            PhantomPinned,
+        )
     }
 
     pub fn set_tss(&mut self, base: u64) {
@@ -74,18 +77,20 @@ impl GDT {
         self.0[Self::TLS32_INDEX] = desc;
     }
 
-    pub unsafe fn load(&self) {
+    pub fn load(&self) {
         let len = Self::LEN * 8 - 1;
         let descriptor: [u64; 2] = [(len as u64) << 48, self.0.as_ptr() as u64];
         assert!(len < 0x10000, "GDT too large");
 
         let descriptor_address = &descriptor as *const _ as usize + 6;
-        asm!(
-            "lgdt ({})",
-            "ltr %ax",
-            in(reg) descriptor_address,
-            in("ax") Self::TSS_INDEX as u16 * 8,
-            options(att_syntax)
-        );
+        unsafe {
+            asm!(
+                "lgdt ({})",
+                "ltr %ax",
+                in(reg) descriptor_address,
+                in("ax") Self::TSS_INDEX as u16 * 8,
+                options(att_syntax, readonly, preserves_flags),
+            );
+        }
     }
 }

+ 196 - 0
crates/eonix_hal/src/arch/x86_64/interrupt.rs

@@ -0,0 +1,196 @@
+use arch::{pause, rdmsr};
+use core::{arch::asm, marker::PhantomPinned, pin::Pin, ptr::NonNull};
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+struct IDTEntry {
+    offset_low: u16,
+    selector: u16,
+
+    interrupt_stack: u8,
+    attributes: u8,
+
+    offset_mid: u16,
+    offset_high: u32,
+    reserved: u32,
+}
+
+pub struct APICReg(*mut u32);
+pub struct APICRegs {
+    base: NonNull<u32>,
+}
+
+/// Architecture-specific interrupt control block.
+pub struct InterruptControl {
+    idt: [IDTEntry; 256],
+    apic_base: APICRegs,
+    _pinned: PhantomPinned,
+}
+
+impl IDTEntry {
+    const fn new(offset: usize, selector: u16, attributes: u8) -> Self {
+        Self {
+            offset_low: offset as u16,
+            selector,
+            interrupt_stack: 0,
+            attributes,
+            offset_mid: (offset >> 16) as u16,
+            offset_high: (offset >> 32) as u32,
+            reserved: 0,
+        }
+    }
+
+    const fn null() -> Self {
+        Self {
+            offset_low: 0,
+            selector: 0,
+            interrupt_stack: 0,
+            attributes: 0,
+            offset_mid: 0,
+            offset_high: 0,
+            reserved: 0,
+        }
+    }
+}
+
+impl APICReg {
+    fn new(pointer: *mut u32) -> Self {
+        Self(pointer)
+    }
+
+    pub fn read(&self) -> u32 {
+        unsafe { self.0.read_volatile() }
+    }
+
+    pub fn write(&self, value: u32) {
+        unsafe { self.0.write_volatile(value) }
+    }
+}
+
+impl APICRegs {
+    pub fn local_apic_id(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0x20).as_ptr()) }
+    }
+
+    pub fn task_priority(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0x80).as_ptr()) }
+    }
+
+    pub fn end_of_interrupt(&self) {
+        unsafe { APICReg::new(self.base.byte_offset(0xb0).as_ptr()).write(0) }
+    }
+
+    pub fn spurious(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0xf0).as_ptr()) }
+    }
+
+    pub fn interrupt_command(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0x300).as_ptr()) }
+    }
+
+    pub fn timer_register(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0x320).as_ptr()) }
+    }
+
+    pub fn timer_initial_count(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0x380).as_ptr()) }
+    }
+
+    pub fn timer_current_count(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0x390).as_ptr()) }
+    }
+
+    pub fn timer_divide(&self) -> APICReg {
+        unsafe { APICReg::new(self.base.byte_offset(0x3e0).as_ptr()) }
+    }
+}
+
+impl InterruptControl {
+    /// # Return
+    /// Returns a tuple of InterruptControl and the cpu id of the current cpu.
+    pub fn new() -> (Self, usize) {
+        let trap_stubs_base = super::trap::trap_stubs_start as usize;
+
+        let idt = core::array::from_fn(|idx| match idx {
+            0..0x80 => IDTEntry::new(trap_stubs_base + 8 * idx, 0x08, 0x8e),
+            0x80 => IDTEntry::new(trap_stubs_base + 8 * idx, 0x08, 0xee),
+            _ => IDTEntry::null(),
+        });
+
+        let apic_base = {
+            let apic_base = rdmsr(0x1b);
+            assert_eq!(apic_base & 0x800, 0x800, "LAPIC not enabled");
+
+            let apic_base = ((apic_base & !0xfff) + 0xffffff00_00000000) as *mut u32;
+            APICRegs {
+                // TODO: A better way to convert to physical address
+                base: NonNull::new(apic_base).expect("Invalid APIC base"),
+            }
+        };
+
+        // Make sure APIC is enabled.
+        apic_base.spurious().write(0x1ff);
+
+        let cpuid = apic_base.local_apic_id().read() >> 24;
+
+        (
+            Self {
+                idt,
+                apic_base,
+                _pinned: PhantomPinned,
+            },
+            cpuid as usize,
+        )
+    }
+
+    pub fn setup_timer(&self) {
+        self.apic_base.task_priority().write(0);
+        self.apic_base.timer_divide().write(0x3); // Divide by 16
+        self.apic_base.timer_register().write(0x20040);
+
+        // TODO: Get the bus frequency from...?
+        let freq = 200;
+        let count = freq * 1_000_000 / 16 / 100;
+        self.apic_base.timer_initial_count().write(count as u32);
+    }
+
+    pub fn setup_idt(self: Pin<&mut Self>) {
+        lidt(
+            self.idt.as_ptr() as usize,
+            (size_of::<IDTEntry>() * 256 - 1) as u16,
+        );
+    }
+
+    pub fn send_sipi(&self) {
+        let icr = self.apic_base.interrupt_command();
+
+        icr.write(0xc4500);
+        while icr.read() & 0x1000 != 0 {
+            pause();
+        }
+
+        icr.write(0xc4606);
+        while icr.read() & 0x1000 != 0 {
+            pause();
+        }
+    }
+
+    /// Send EOI to APIC so that it can send more interrupts.
+    pub fn end_of_interrupt(&self) {
+        self.apic_base.end_of_interrupt()
+    }
+}
+
+fn lidt(base: usize, limit: u16) {
+    let mut idt_descriptor = [0u16; 5];
+
+    idt_descriptor[0] = limit;
+    idt_descriptor[1] = base as u16;
+    idt_descriptor[2] = (base >> 16) as u16;
+    idt_descriptor[3] = (base >> 32) as u16;
+    idt_descriptor[4] = (base >> 48) as u16;
+
+    unsafe {
+        asm!("lidt ({})", in(reg) &idt_descriptor, options(att_syntax, nostack, preserves_flags));
+    }
+}

+ 98 - 0
crates/eonix_hal/src/arch/x86_64/link.x

@@ -0,0 +1,98 @@
+SECTIONS {
+    .low 0x500 (NOLOAD) :
+    {
+
+        KEEP(*(.low .low*));
+
+    } > LOWMEM
+
+    .mbr 0xe00 :
+    {
+        KEEP(*(.mbr));
+
+        /* avoid the MBR being overwritten */
+        . = ABSOLUTE(ADDR(.mbr) + 446);
+
+        /* avoid the MBR being overwritten */
+        . = ABSOLUTE(ADDR(.mbr) + 510);
+        BYTE(0x55);
+        BYTE(0xaa);
+    } > LOWMEM = 0x00
+
+    .stage1 0x6000 :
+    {
+        KEEP(*(.stage1.smp));
+
+        . = ALIGN(16);
+        KEEP(*(.stage1));
+
+        . = ABSOLUTE(ADDR(.stage1) + 512 * 7 - 4);
+        STAGE1_MAGIC = .;
+        LONG(ABSOLUTE(STAGE1_MAGIC_VALUE));
+
+        STAGE1_MAGIC_VALUE = 0x01145140;
+    } > LOWMEM AT> LOWMEM
+}
+
+SECTIONS {
+    .text.syscall_fns :
+    {
+
+        KEEP(*(.syscall_fns*));
+
+    } > REGION_TEXT
+}
+INSERT AFTER .text;
+
+SECTIONS {
+    .rodata.fixups :
+    {
+        . = ALIGN(16);
+        FIX_START = .;
+
+        KEEP(*(.fix));
+
+        FIX_END = .;
+    } > REGION_RODATA
+
+    .rodata.syscalls :
+    {
+        . = ALIGN(16);
+        __raw_syscall_handlers_start = .;
+
+        RAW_SYSCALL_HANDLERS = .;
+        KEEP(*(.raw_syscalls*));
+
+        __raw_syscall_handlers_end = .;
+
+        RAW_SYSCALL_HANDLERS_SIZE =
+            ABSOLUTE(__raw_syscall_handlers_end - __raw_syscall_handlers_start);
+    } > REGION_RODATA
+}
+INSERT AFTER .rodata;
+
+SECTIONS {
+    .percpu 0 : ALIGN(16)
+    {
+        __spercpu = .;
+
+        PERCPU_START = .;
+        QUAD(0); /* Reserved for x86 percpu pointer */
+
+        . = ALIGN(16);
+
+        *(.percpu .percpu*);
+
+        . = ALIGN(16);
+        __epercpu = .;
+    } > LOWMEM AT> REGION_RODATA
+
+    PERCPU_DATA_START = LOADADDR(.percpu);
+    PERCPU_LENGTH = ABSOLUTE(__epercpu - __spercpu);
+
+    KIMAGE_PAGES = (__edata - _stext + 0x1000 - 1) / 0x1000;
+    KIMAGE_32K_COUNT = (KIMAGE_PAGES + 8 - 1) / 8;
+
+    BSS_LENGTH = ABSOLUTE(__ebss - __sbss);
+}
+INSERT AFTER .rodata;

+ 11 - 0
crates/eonix_hal/src/arch/x86_64/memory.x

@@ -0,0 +1,11 @@
+MEMORY {
+    LOWMEM : org = 0x0000000000000000, len = 1M
+    KBSS   : org = 0xffffffffc0200000, len = 2M
+    KIMAGE : org = 0xffffffffffc00000, len = 2M
+}
+
+REGION_ALIAS("REGION_TEXT", KIMAGE);
+REGION_ALIAS("REGION_RODATA", KIMAGE);
+REGION_ALIAS("REGION_DATA", KIMAGE);
+REGION_ALIAS("REGION_BSS", KBSS);
+REGION_ALIAS("REGION_EHFRAME", KIMAGE);

+ 375 - 0
crates/eonix_hal/src/arch/x86_64/mm.rs

@@ -0,0 +1,375 @@
+use crate::traits::mm::Memory;
+use core::{marker::PhantomData, ptr::NonNull};
+use eonix_mm::{
+    address::{Addr as _, AddrOps as _, PAddr, PRange, PhysAccess, VAddr},
+    page_table::{
+        PageAttribute, PageTable, PageTableLevel, PagingMode, RawAttribute, RawPageTable,
+        TableAttribute, PTE,
+    },
+    paging::{NoAlloc, Page, PageBlock, PAGE_SIZE, PFN},
+};
+use eonix_sync_base::LazyLock;
+
+pub const PA_P: u64 = 0x001;
+pub const PA_RW: u64 = 0x002;
+pub const PA_US: u64 = 0x004;
+#[allow(dead_code)]
+pub const PA_PWT: u64 = 0x008;
+#[allow(dead_code)]
+pub const PA_PCD: u64 = 0x010;
+pub const PA_A: u64 = 0x020;
+pub const PA_D: u64 = 0x040;
+pub const PA_PS: u64 = 0x080;
+pub const PA_G: u64 = 0x100;
+pub const PA_COW: u64 = 0x200;
+pub const PA_MMAP: u64 = 0x400;
+pub const PA_ANON: u64 = 0x800;
+pub const PA_NXE: u64 = 0x8000_0000_0000_0000;
+pub const PA_MASK: u64 = 0xfff0_0000_0000_0fff;
+
+pub const P_KIMAGE_START: PAddr = PAddr::from_val(0x200000);
+pub const V_KERNEL_BSS_START: VAddr = VAddr::from(0xffffffffc0200000);
+
+const KERNEL_PML4_PFN: PFN = PFN::from_val(0x1000 >> 12);
+
+pub static GLOBAL_PAGE_TABLE: LazyLock<PageTable<ArchPagingMode, NoAlloc, ArchPhysAccess>> =
+    LazyLock::new(|| unsafe {
+        Page::with_raw(KERNEL_PML4_PFN, |root_table_page| {
+            PageTable::with_root_table(root_table_page.clone())
+        })
+    });
+
+#[repr(transparent)]
+pub struct PTE64(u64);
+
+#[derive(Clone, Copy)]
+pub struct PageAttribute64(u64);
+
+pub struct RawPageTable4Levels<'a>(NonNull<PTE64>, PhantomData<&'a ()>);
+
+pub struct PagingMode4Levels;
+
+pub struct ArchPhysAccess;
+
+pub struct ArchMemory;
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct E820MemMapEntry {
+    base: u64,
+    len: u64,
+    entry_type: u32,
+    acpi_attrs: u32,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct BootLoaderData {
+    entry_count: u32,
+    entry_length: u32,
+
+    block_count_1k: u32,
+    block_count_64k: u32,
+
+    all_entries: [E820MemMapEntry; 42],
+}
+
+impl PTE for PTE64 {
+    type Attr = PageAttribute64;
+
+    fn set(&mut self, pfn: PFN, attr: Self::Attr) {
+        let paddr = PAddr::from(pfn).addr();
+
+        self.0 = (paddr as u64 & !PA_MASK) | (attr.0 & PA_MASK);
+    }
+
+    fn get(&self) -> (PFN, Self::Attr) {
+        (
+            PFN::from(PAddr::from((self.0 & !PA_MASK) as usize)),
+            PageAttribute64(self.0 & PA_MASK),
+        )
+    }
+}
+
+impl PagingMode for PagingMode4Levels {
+    type Entry = PTE64;
+    type RawTable<'a> = RawPageTable4Levels<'a>;
+
+    const LEVELS: &'static [PageTableLevel] = &[
+        PageTableLevel::new(39, 9),
+        PageTableLevel::new(30, 9),
+        PageTableLevel::new(21, 9),
+        PageTableLevel::new(12, 9),
+    ];
+}
+
+impl<'a> RawPageTable<'a> for RawPageTable4Levels<'a> {
+    type Entry = PTE64;
+
+    fn index(&self, index: u16) -> &'a Self::Entry {
+        unsafe { &self.0.cast::<[PTE64; 512]>().as_ref()[index as usize] }
+    }
+
+    fn index_mut(&mut self, index: u16) -> &'a mut Self::Entry {
+        unsafe { &mut self.0.cast::<[PTE64; 512]>().as_mut()[index as usize] }
+    }
+
+    unsafe fn from_ptr(ptr: NonNull<PageBlock>) -> Self {
+        Self(ptr.cast(), PhantomData)
+    }
+}
+
+impl RawAttribute for PageAttribute64 {
+    fn null() -> Self {
+        Self(0)
+    }
+
+    fn as_table_attr(self) -> Option<TableAttribute> {
+        let mut table_attr = TableAttribute::empty();
+
+        if self.0 & PA_PS != 0 {
+            panic!("Encountered a huge page while parsing table attributes");
+        }
+
+        if self.0 & PA_P != 0 {
+            table_attr |= TableAttribute::PRESENT;
+        }
+        if self.0 & PA_G != 0 {
+            table_attr |= TableAttribute::GLOBAL;
+        }
+        if self.0 & PA_US != 0 {
+            table_attr |= TableAttribute::USER;
+        }
+        if self.0 & PA_A != 0 {
+            table_attr |= TableAttribute::ACCESSED;
+        }
+
+        Some(table_attr)
+    }
+
+    fn as_page_attr(self) -> Option<PageAttribute> {
+        let mut page_attr = PageAttribute::READ;
+
+        if self.0 & PA_P != 0 {
+            page_attr |= PageAttribute::PRESENT;
+        }
+
+        if self.0 & PA_RW != 0 {
+            page_attr |= PageAttribute::WRITE;
+        }
+
+        if self.0 & PA_NXE == 0 {
+            page_attr |= PageAttribute::EXECUTE;
+        }
+
+        if self.0 & PA_US != 0 {
+            page_attr |= PageAttribute::USER;
+        }
+
+        if self.0 & PA_A != 0 {
+            page_attr |= PageAttribute::ACCESSED;
+        }
+
+        if self.0 & PA_D != 0 {
+            page_attr |= PageAttribute::DIRTY;
+        }
+
+        if self.0 & PA_G != 0 {
+            page_attr |= PageAttribute::GLOBAL;
+        }
+
+        if self.0 & PA_COW != 0 {
+            page_attr |= PageAttribute::COPY_ON_WRITE;
+        }
+
+        if self.0 & PA_MMAP != 0 {
+            page_attr |= PageAttribute::MAPPED;
+        }
+
+        if self.0 & PA_ANON != 0 {
+            page_attr |= PageAttribute::ANONYMOUS;
+        }
+
+        if self.0 & PA_PS != 0 {
+            page_attr |= PageAttribute::HUGE;
+        }
+
+        Some(page_attr)
+    }
+}
+
+impl From<PageAttribute> for PageAttribute64 {
+    fn from(page_attr: PageAttribute) -> Self {
+        let mut raw_attr = PA_NXE;
+
+        for attr in page_attr.iter() {
+            match attr {
+                PageAttribute::PRESENT => raw_attr |= PA_P,
+                PageAttribute::READ => {}
+                PageAttribute::WRITE => raw_attr |= PA_RW,
+                PageAttribute::EXECUTE => raw_attr &= !PA_NXE,
+                PageAttribute::USER => raw_attr |= PA_US,
+                PageAttribute::ACCESSED => raw_attr |= PA_A,
+                PageAttribute::DIRTY => raw_attr |= PA_D,
+                PageAttribute::GLOBAL => raw_attr |= PA_G,
+                PageAttribute::COPY_ON_WRITE => raw_attr |= PA_COW,
+                PageAttribute::MAPPED => raw_attr |= PA_MMAP,
+                PageAttribute::ANONYMOUS => raw_attr |= PA_ANON,
+                PageAttribute::HUGE => raw_attr |= PA_PS,
+                _ => unreachable!("Invalid page attribute"),
+            }
+        }
+
+        Self(raw_attr)
+    }
+}
+
+impl From<TableAttribute> for PageAttribute64 {
+    fn from(table_attr: TableAttribute) -> Self {
+        let mut raw_attr = PA_RW;
+
+        for attr in table_attr.iter() {
+            match attr {
+                TableAttribute::PRESENT => raw_attr |= PA_P,
+                TableAttribute::GLOBAL => raw_attr |= PA_G,
+                TableAttribute::USER => raw_attr |= PA_US,
+                TableAttribute::ACCESSED => raw_attr |= PA_A,
+                _ => unreachable!("Invalid table attribute"),
+            }
+        }
+
+        Self(raw_attr)
+    }
+}
+
+pub type ArchPagingMode = PagingMode4Levels;
+
+impl ArchPhysAccess {
+    const PHYS_OFFSET: usize = 0xffff_ff00_0000_0000;
+}
+
+impl PhysAccess for ArchPhysAccess {
+    unsafe fn as_ptr<T>(paddr: PAddr) -> NonNull<T> {
+        let alignment: usize = align_of::<T>();
+        assert!(paddr.addr() % alignment == 0, "Alignment error");
+
+        unsafe {
+            // SAFETY: We can assume that we'll never have `self.addr()` equals
+            //         to `-PHYS_OFFSET`. Otherwise, the kernel might be broken.
+            NonNull::new_unchecked((Self::PHYS_OFFSET + paddr.addr()) as *mut T)
+        }
+    }
+
+    unsafe fn from_ptr<T>(ptr: NonNull<T>) -> PAddr {
+        let addr = ptr.addr().get();
+
+        assert!(addr % align_of::<T>() == 0, "Alignment error");
+        assert!(
+            addr >= Self::PHYS_OFFSET,
+            "Address is not a valid physical address"
+        );
+
+        PAddr::from_val(addr - Self::PHYS_OFFSET)
+    }
+}
+
+impl E820MemMapEntry {
+    const ENTRY_FREE: u32 = 1;
+    // const ENTRY_USED: u32 = 2;
+
+    const fn zeroed() -> Self {
+        Self {
+            base: 0,
+            len: 0,
+            entry_type: 0,
+            acpi_attrs: 0,
+        }
+    }
+
+    fn is_free(&self) -> bool {
+        self.entry_type == Self::ENTRY_FREE
+    }
+
+    // fn is_used(&self) -> bool {
+    //     self.entry_type == Self::ENTRY_USED
+    // }
+
+    fn range(&self) -> PRange {
+        PRange::from(PAddr::from(self.base as usize)).grow(self.len as usize)
+    }
+}
+
+impl BootLoaderData {
+    const fn zeroed() -> Self {
+        Self {
+            entry_count: 0,
+            entry_length: 0,
+            block_count_1k: 0,
+            block_count_64k: 0,
+            all_entries: [E820MemMapEntry::zeroed(); 42],
+        }
+    }
+
+    // fn memory_size(&self) -> usize {
+    //     // The initial 1M is not counted in the E820 map. We add them to the total as well.
+    //     ((self.block_count_1k + 64 * self.block_count_64k) * 1024 + 1 * 1024 * 1024) as usize
+    // }
+
+    fn entries(&self) -> &[E820MemMapEntry] {
+        &self.all_entries[..self.entry_count as usize]
+    }
+
+    fn free_entries(&self) -> impl Iterator<Item = &E820MemMapEntry> {
+        self.entries().iter().filter(|entry| entry.is_free())
+    }
+}
+
+#[unsafe(link_section = ".low")]
+pub static mut E820_MEM_MAP_DATA: BootLoaderData = BootLoaderData::zeroed();
+
+impl Memory for ArchMemory {
+    fn present_ram() -> impl Iterator<Item = PRange> {
+        let e820 = &raw const E820_MEM_MAP_DATA;
+
+        unsafe {
+            // SAFETY: We don't write to the E820 memory map after the bootstrap.
+            e820.as_ref()
+                .unwrap_unchecked()
+                .free_entries()
+                .map(|entry| entry.range())
+        }
+    }
+
+    fn free_ram() -> impl Iterator<Item = PRange> {
+        unsafe extern "C" {
+            fn KIMAGE_PAGES();
+        }
+
+        let kimage_pages = KIMAGE_PAGES as usize;
+
+        let paddr_after_kimage = P_KIMAGE_START + kimage_pages * PAGE_SIZE;
+        let paddr_after_kimage_aligned = paddr_after_kimage.ceil_to(0x200000);
+        let paddr_unused_start = paddr_after_kimage_aligned;
+
+        core::iter::once(PRange::new(
+            PAddr::from_val(0x100000),
+            PAddr::from_val(0x200000),
+        ))
+        .chain(core::iter::once(PRange::new(
+            paddr_after_kimage,
+            paddr_after_kimage_aligned,
+        )))
+        .chain(
+            Self::present_ram()
+                .filter(move |range| range.end() > paddr_unused_start)
+                .map(move |range| {
+                    if range.start() < paddr_unused_start {
+                        let (_, right) = range.split_at(paddr_unused_start);
+                        right
+                    } else {
+                        range
+                    }
+                }),
+        )
+    }
+}

+ 7 - 0
crates/eonix_hal/src/arch/x86_64/mod.rs

@@ -0,0 +1,7 @@
+pub mod bootstrap;
+pub mod context;
+pub mod cpu;
+pub mod gdt;
+pub mod interrupt;
+pub mod mm;
+pub mod trap;

+ 390 - 0
crates/eonix_hal/src/arch/x86_64/trap.rs

@@ -0,0 +1,390 @@
+mod trap_context;
+
+use super::context::TaskContext;
+use core::arch::{asm, global_asm, naked_asm};
+use eonix_hal_traits::{
+    context::RawTaskContext,
+    trap::{IrqState as IrqStateTrait, TrapReturn},
+};
+
+pub use trap_context::TrapContext;
+
+unsafe extern "C" {
+    fn _default_trap_handler(trap_context: &mut TrapContext);
+    pub fn trap_stubs_start();
+    fn _raw_trap_return();
+}
+
+#[eonix_percpu::define_percpu]
+static TRAP_HANDLER: unsafe extern "C" fn() = default_trap_handler;
+
+#[eonix_percpu::define_percpu]
+static CAPTURER_CONTEXT: TaskContext = TaskContext::new();
+
+/// This value will never be used.
+static mut DIRTY_TRAP_CONTEXT: TaskContext = TaskContext::new();
+
+/// State of the interrupt flag.
+pub struct IrqState(u64);
+
+global_asm!(
+    r"
+    .set RAX, 0x00
+    .set RBX, 0x08
+    .set RCX, 0x10
+    .set RDX, 0x18
+    .set RDI, 0x20
+    .set RSI, 0x28
+    .set R8, 0x30
+    .set R9, 0x38
+    .set R10, 0x40
+    .set R11, 0x48
+    .set R12, 0x50
+    .set R13, 0x58
+    .set R14, 0x60
+    .set R15, 0x68
+    .set RBP, 0x70
+    .set INT_NO, 0x78
+    .set ERRCODE, 0x80
+    .set RIP, 0x88
+    .set CS, 0x90
+    .set FLAGS, 0x98
+    .set RSP, 0xa0
+    .set SS, 0xa8
+
+    .macro cfi_all_same_value
+        .cfi_same_value %rax
+        .cfi_same_value %rbx
+        .cfi_same_value %rcx
+        .cfi_same_value %rdx
+        .cfi_same_value %rdi
+        .cfi_same_value %rsi
+        .cfi_same_value %r8
+        .cfi_same_value %r9
+        .cfi_same_value %r10
+        .cfi_same_value %r11
+        .cfi_same_value %r12
+        .cfi_same_value %r13
+        .cfi_same_value %r14
+        .cfi_same_value %r15
+        .cfi_same_value %rbp
+    .endm
+
+    .globl {trap_stubs_start}
+    {trap_stubs_start}:
+        .altmacro
+        .macro build_isr_no_err name
+            .align 8
+            .globl ISR\name
+            .type  ISR\name @function
+            ISR\name:
+                .cfi_startproc
+                .cfi_signal_frame
+                .cfi_def_cfa_offset 0x08
+                .cfi_offset %rsp, 0x10
+
+                cfi_all_same_value
+
+                push %rbp # push placeholder for error code
+                .cfi_def_cfa_offset 0x10
+
+                call _raw_trap_entry
+                .cfi_endproc
+        .endm
+
+        .altmacro
+        .macro build_isr_err name
+            .align 8
+            .globl ISR\name
+            .type  ISR\name @function
+            ISR\name:
+                .cfi_startproc
+                .cfi_signal_frame
+                .cfi_def_cfa_offset 0x10
+                .cfi_offset %rsp, 0x10
+
+                cfi_all_same_value
+
+                call _raw_trap_entry
+                .cfi_endproc
+        .endm
+
+        build_isr_no_err 0
+        build_isr_no_err 1
+        build_isr_no_err 2
+        build_isr_no_err 3
+        build_isr_no_err 4
+        build_isr_no_err 5
+        build_isr_no_err 6
+        build_isr_no_err 7
+        build_isr_err    8
+        build_isr_no_err 9
+        build_isr_err    10
+        build_isr_err    11
+        build_isr_err    12
+        build_isr_err    13
+        build_isr_err    14
+        build_isr_no_err 15
+        build_isr_no_err 16
+        build_isr_err    17
+        build_isr_no_err 18
+        build_isr_no_err 19
+        build_isr_no_err 20
+        build_isr_err    21
+        build_isr_no_err 22
+        build_isr_no_err 23
+        build_isr_no_err 24
+        build_isr_no_err 25
+        build_isr_no_err 26
+        build_isr_no_err 27
+        build_isr_no_err 28
+        build_isr_err    29
+        build_isr_err    30
+        build_isr_no_err 31
+
+        .set i, 32
+        .rept 0x80+1
+            build_isr_no_err %i
+            .set i, i+1
+        .endr
+    
+    .globl _raw_trap_entry
+    .type  _raw_trap_entry @function
+    _raw_trap_entry:
+        .cfi_startproc
+        .cfi_signal_frame
+        .cfi_def_cfa %rsp, 0x18
+        .cfi_offset %rsp, 0x10
+
+        cfi_all_same_value
+        
+        sub $0x78, %rsp
+        .cfi_def_cfa_offset CS
+        
+        mov %rax, RAX(%rsp)
+        .cfi_rel_offset %rax, RAX
+        mov %rbx, RBX(%rsp)
+        .cfi_rel_offset %rbx, RBX
+        mov %rcx, RCX(%rsp)
+        .cfi_rel_offset %rcx, RCX
+        mov %rdx, RDX(%rsp)
+        .cfi_rel_offset %rdx, RDX
+        mov %rdi, RDI(%rsp)
+        .cfi_rel_offset %rdi, RDI
+        mov %rsi, RSI(%rsp)
+        .cfi_rel_offset %rsi, RSI
+        mov %r8, R8(%rsp)
+        .cfi_rel_offset %r8, R8
+        mov %r9, R9(%rsp)
+        .cfi_rel_offset %r9, R9
+        mov %r10, R10(%rsp)
+        .cfi_rel_offset %r10, R10
+        mov %r11, R11(%rsp)
+        .cfi_rel_offset %r11, R11
+        mov %r12, R12(%rsp)
+        .cfi_rel_offset %r12, R12
+        mov %r13, R13(%rsp)
+        .cfi_rel_offset %r13, R13
+        mov %r14, R14(%rsp)
+        .cfi_rel_offset %r14, R14
+        mov %r15, R15(%rsp)
+        .cfi_rel_offset %r15, R15
+        mov %rbp, RBP(%rsp)
+        .cfi_rel_offset %rbp, RBP
+        
+        mov INT_NO(%rsp), %rcx
+        sub ${trap_stubs_start}, %rcx
+        shr $3, %rcx
+        mov %rcx, INT_NO(%rsp)
+        
+        cmpq $0x08, CS(%rsp)
+        je 2f
+        swapgs
+        
+        2:
+        mov %gs:0, %rcx
+        add ${handler}, %rcx
+        mov (%rcx), %rcx
+        
+        jmp *%rcx
+        .cfi_endproc
+    
+    _raw_trap_return:
+        .cfi_startproc
+        .cfi_def_cfa %rsp, CS
+        .cfi_rel_offset %rax, RAX
+        .cfi_rel_offset %rbx, RBX
+        .cfi_rel_offset %rcx, RCX
+        .cfi_rel_offset %rdx, RDX
+        .cfi_rel_offset %rdi, RDI
+        .cfi_rel_offset %rsi, RSI
+        .cfi_rel_offset %r8, R8
+        .cfi_rel_offset %r9, R9
+        .cfi_rel_offset %r10, R10
+        .cfi_rel_offset %r11, R11
+        .cfi_rel_offset %r12, R12
+        .cfi_rel_offset %r13, R13
+        .cfi_rel_offset %r14, R14
+        .cfi_rel_offset %r15, R15
+        .cfi_rel_offset %rbp, RBP
+        .cfi_rel_offset %rsp, RSP
+        
+        mov RAX(%rsp), %rax
+        .cfi_restore %rax
+        mov RBX(%rsp), %rbx
+        .cfi_restore %rbx
+        mov RCX(%rsp), %rcx
+        .cfi_restore %rcx
+        mov RDX(%rsp), %rdx
+        .cfi_restore %rdx
+        mov RDI(%rsp), %rdi
+        .cfi_restore %rdi
+        mov RSI(%rsp), %rsi
+        .cfi_restore %rsi
+        mov R8(%rsp), %r8
+        .cfi_restore %r8
+        mov R9(%rsp), %r9
+        .cfi_restore %r9
+        mov R10(%rsp), %r10
+        .cfi_restore %r10
+        mov R11(%rsp), %r11
+        .cfi_restore %r11
+        mov R12(%rsp), %r12
+        .cfi_restore %r12
+        mov R13(%rsp), %r13
+        .cfi_restore %r13
+        mov R14(%rsp), %r14
+        .cfi_restore %r14
+        mov R15(%rsp), %r15
+        .cfi_restore %r15
+        mov RBP(%rsp), %rbp
+        .cfi_restore %rbp
+        
+        cmpq $0x08, CS(%rsp)
+        je 2f
+        swapgs
+        
+        2:
+        lea RIP(%rsp), %rsp
+        .cfi_def_cfa %rsp, 0x08
+        .cfi_offset %rsp, 0x10
+        
+        iretq
+        .cfi_endproc
+    ",
+    trap_stubs_start = sym trap_stubs_start,
+    handler = sym _percpu_inner_TRAP_HANDLER,
+    options(att_syntax),
+);
+
+/// Default handler handles the trap on the current stack and returns
+/// to the context before interrut.
+#[unsafe(naked)]
+unsafe extern "C" fn default_trap_handler() {
+    naked_asm!(
+        ".cfi_startproc",
+        "mov %rsp, %rbx",
+        ".cfi_def_cfa_register %rbx",
+        "",
+        "and $~0xf, %rsp",
+        "",
+        "mov %rbx, %rdi",
+        "call {handle_trap}",
+        "",
+        "mov %rbx, %rsp",
+        ".cfi_def_cfa_register %rsp",
+        "",
+        "jmp {trap_return}",
+        ".cfi_endproc",
+        handle_trap = sym _default_trap_handler,
+        trap_return = sym _raw_trap_return,
+        options(att_syntax),
+    );
+}
+
+#[unsafe(naked)]
+unsafe extern "C" fn captured_trap_handler() {
+    naked_asm!(
+        "mov ${from_context}, %rdi",
+        "mov %gs:0, %rsi",
+        "add ${to_context}, %rsi",
+        "",
+        "mov %rdi, %rsp", // We need a temporary stack to use `switch()`.
+        "",
+        "jmp {switch}",
+        from_context = sym DIRTY_TRAP_CONTEXT,
+        to_context = sym _percpu_inner_CAPTURER_CONTEXT,
+        switch = sym TaskContext::switch,
+        options(att_syntax),
+    );
+}
+
+#[unsafe(naked)]
+unsafe extern "C" fn captured_trap_return(trap_context: usize) -> ! {
+    naked_asm!(
+        "jmp {trap_return}",
+        trap_return = sym _raw_trap_return,
+        options(att_syntax),
+    );
+}
+
+impl TrapReturn for TrapContext {
+    unsafe fn trap_return(&mut self) {
+        let irq_states = disable_irqs_save();
+        let old_handler = TRAP_HANDLER.swap(captured_trap_handler);
+
+        let mut to_ctx = TaskContext::new();
+        to_ctx.set_program_counter(captured_trap_return as _);
+        to_ctx.set_stack_pointer(&raw mut *self as usize);
+        to_ctx.set_interrupt_enabled(false);
+
+        unsafe {
+            TaskContext::switch(CAPTURER_CONTEXT.as_mut(), &mut to_ctx);
+        }
+
+        TRAP_HANDLER.set(old_handler);
+        irq_states.restore();
+    }
+}
+
+impl IrqStateTrait for IrqState {
+    fn restore(self) {
+        let Self(state) = self;
+
+        unsafe {
+            asm!(
+                "push {state}",
+                "popf",
+                state = in(reg) state,
+                options(att_syntax, nomem)
+            );
+        }
+    }
+}
+
+pub fn enable_irqs() {
+    unsafe {
+        asm!("sti", options(att_syntax, nomem, nostack));
+    }
+}
+
+pub fn disable_irqs() {
+    unsafe {
+        asm!("cli", options(att_syntax, nomem, nostack));
+    }
+}
+
+pub fn disable_irqs_save() -> IrqState {
+    let state: u64;
+    unsafe {
+        asm!(
+            "pushf",
+            "pop {state}",
+            "cli",
+            state = out(reg) state,
+            options(att_syntax, nomem)
+        );
+    }
+
+    IrqState(state)
+}

+ 130 - 0
crates/eonix_hal/src/arch/x86_64/trap/trap_context.rs

@@ -0,0 +1,130 @@
+use eonix_hal_traits::{
+    fault::{Fault, PageFaultErrorCode},
+    trap::{RawTrapContext, TrapType},
+};
+
+#[derive(Clone, Copy, Default)]
+#[repr(C, align(16))]
+pub struct TrapContext {
+    rax: u64,
+    rbx: u64,
+    rcx: u64,
+    rdx: u64,
+    rdi: u64,
+    rsi: u64,
+    r8: u64,
+    r9: u64,
+    r10: u64,
+    r11: u64,
+    r12: u64,
+    r13: u64,
+    r14: u64,
+    r15: u64,
+    rbp: u64,
+    int_no: u64,
+    errcode: u64,
+    rip: u64,
+    cs: u64,
+    flags: u64,
+    rsp: u64,
+    ss: u64,
+}
+
+impl TrapContext {
+    fn get_fault_type(&self) -> Fault {
+        match self.int_no {
+            6 | 8 => Fault::InvalidOp,
+            13 => Fault::BadAccess,
+            14 => {
+                let mut error_code = PageFaultErrorCode::empty();
+                if self.errcode & 1 != 0 {
+                    error_code |= PageFaultErrorCode::NonPresent;
+                }
+
+                if self.errcode & 2 != 0 {
+                    error_code |= PageFaultErrorCode::Write;
+                } else if self.errcode & 16 != 0 {
+                    error_code |= PageFaultErrorCode::InstructionFetch;
+                } else {
+                    error_code |= PageFaultErrorCode::Read;
+                }
+
+                if self.errcode & 4 != 0 {
+                    error_code |= PageFaultErrorCode::UserAccess;
+                }
+
+                Fault::PageFault(error_code)
+            }
+            code @ 0..0x20 => Fault::Unknown(code as usize),
+            _ => unreachable!(),
+        }
+    }
+}
+
+impl RawTrapContext for TrapContext {
+    fn new() -> Self {
+        Self {
+            ..Default::default()
+        }
+    }
+
+    fn trap_type(&self) -> TrapType {
+        match self.int_no {
+            0..0x20 => TrapType::Fault(self.get_fault_type()),
+            0x40 => TrapType::Timer,
+            0x80 => TrapType::Syscall {
+                no: self.rax as usize,
+                args: [
+                    self.rbx as usize,
+                    self.rcx as usize,
+                    self.rdx as usize,
+                    self.rsi as usize,
+                    self.rdi as usize,
+                    self.rbp as usize,
+                ],
+            },
+            no => TrapType::Irq(no as usize - 0x20),
+        }
+    }
+
+    fn get_program_counter(&self) -> usize {
+        self.rip as usize
+    }
+
+    fn get_stack_pointer(&self) -> usize {
+        self.rsp as usize
+    }
+
+    fn set_program_counter(&mut self, pc: usize) {
+        self.rip = pc as u64
+    }
+
+    fn set_stack_pointer(&mut self, sp: usize) {
+        self.rsp = sp as u64
+    }
+
+    fn is_interrupt_enabled(&self) -> bool {
+        self.flags & 0x200 != 0
+    }
+
+    fn set_interrupt_enabled(&mut self, enabled: bool) {
+        if enabled {
+            self.flags |= 0x200;
+        } else {
+            self.flags &= !0x200;
+        }
+    }
+
+    fn is_user_mode(&self) -> bool {
+        self.cs & 3 == 3
+    }
+
+    fn set_user_mode(&mut self, user: bool) {
+        self.cs = if user { 0x2b } else { 0x08 };
+        self.ss = if user { 0x33 } else { 0x10 };
+    }
+
+    fn set_user_return_value(&mut self, retval: usize) {
+        self.rax = retval as u64;
+    }
+}

+ 22 - 0
crates/eonix_hal/src/bootstrap.rs

@@ -0,0 +1,22 @@
+use crate::mm::{BasicPageAlloc, BasicPageAllocRef};
+use core::cell::RefCell;
+use eonix_mm::address::PRange;
+
+pub struct BootStrapData {
+    pub(crate) early_stack: PRange,
+    pub(crate) allocator: Option<RefCell<BasicPageAlloc>>,
+}
+
+impl BootStrapData {
+    pub fn get_alloc(&self) -> Option<BasicPageAllocRef<'_>> {
+        self.allocator.as_ref().map(BasicPageAllocRef::new)
+    }
+
+    pub fn take_alloc(&mut self) -> Option<BasicPageAlloc> {
+        self.allocator.take().map(RefCell::into_inner)
+    }
+
+    pub fn get_early_stack(&self) -> PRange {
+        self.early_stack
+    }
+}

+ 1 - 0
crates/eonix_hal/src/context.rs

@@ -0,0 +1 @@
+pub use crate::arch::context::TaskContext;

+ 14 - 0
crates/eonix_hal/src/lib.rs

@@ -0,0 +1,14 @@
+#![no_std]
+#![feature(allocator_api)]
+#![feature(doc_notable_trait)]
+
+pub(crate) mod arch;
+
+pub mod bootstrap;
+pub mod context;
+pub mod mm;
+pub mod processor;
+pub mod trap;
+
+pub use eonix_hal_macros::{ap_main, default_trap_handler, main};
+pub use eonix_hal_traits as traits;

+ 114 - 0
crates/eonix_hal/src/link.x.in

@@ -0,0 +1,114 @@
+PROVIDE(_stext = ORIGIN(REGION_TEXT));
+
+SECTIONS {
+    .text.dummy (NOLOAD) :
+    {
+        /*
+         * If we use _stext somewhere before its first appearance below, we
+         * need to define it as absolute here to avoid linker errors.
+         */
+        . = ABSOLUTE(_stext);
+
+    } > REGION_TEXT
+
+    .text _stext :
+    {
+        __stext = .;
+
+        *(.text .text.*);
+
+    } > REGION_TEXT
+
+    __etext = .;
+
+    .rodata : ALIGN(16)
+    {
+        __srodata = .;
+
+        *(.rodata .rodata.*);
+
+    } > REGION_RODATA
+
+    __erodata = .;
+
+    .data : ALIGN(16)
+    {
+        __sdata = .;
+
+        *(.data .data.*);
+        *(.got .got.plt);
+
+    } > REGION_DATA
+
+    __edata = .;
+
+    .bss (NOLOAD) : ALIGN(16)
+    {
+        __sbss = .;
+
+        *(.bss .bss.*);
+
+        . = ALIGN(0x1000);
+    } > REGION_BSS
+
+    __ebss = .;
+
+    .eh_frame : ALIGN(16)
+    {
+        __seh_frame = .;
+
+        KEEP(*(.eh_frame .eh_frame*));
+
+    } > REGION_EHFRAME
+
+    . = ALIGN(0x1000);
+    __eeh_frame = .;
+}
+
+SECTIONS {
+    /* Stabs debugging sections.  */
+    .stab          0 : { KEEP(*(.stab)); }
+    .stabstr       0 : { KEEP(*(.stabstr)); }
+    .stab.excl     0 : { KEEP(*(.stab.excl)); }
+    .stab.exclstr  0 : { KEEP(*(.stab.exclstr)); }
+    .stab.index    0 : { KEEP(*(.stab.index)); }
+    .stab.indexstr 0 : { KEEP(*(.stab.indexstr)); }
+    .comment       0 : { KEEP(*(.comment)); }
+    /* DWARF debug sections.
+       Symbols in the DWARF debugging sections are relative to the beginning
+       of the section so we begin them at 0.  */
+    /* DWARF 1 */
+    .debug          0 : { KEEP(*(.debug)); }
+    .line           0 : { KEEP(*(.line)); }
+    /* GNU DWARF 1 extensions */
+    .debug_srcinfo  0 : { KEEP(*(.debug_srcinfo)); }
+    .debug_sfnames  0 : { KEEP(*(.debug_sfnames)); }
+    /* DWARF 1.1 and DWARF 2 */
+    .debug_aranges  0 : { KEEP(*(.debug_aranges)); }
+    .debug_pubnames 0 : { KEEP(*(.debug_pubnames)); }
+    /* DWARF 2 */
+    .debug_info     0 : { KEEP(*(.debug_info)); }
+    .debug_abbrev   0 : { KEEP(*(.debug_abbrev)); }
+    .debug_line     0 : { KEEP(*(.debug_line)); }
+    .debug_frame    0 : { KEEP(*(.debug_frame)); }
+    .debug_str      0 : { KEEP(*(.debug_str)); }
+    .debug_loc      0 : { KEEP(*(.debug_loc)); }
+    .debug_macinfo  0 : { KEEP(*(.debug_macinfo)); }
+    /* SGI/MIPS DWARF 2 extensions */
+    .debug_weaknames 0 : { KEEP(*(.debug_weaknames)); }
+    .debug_funcnames 0 : { KEEP(*(.debug_funcnames)); }
+    .debug_typenames 0 : { KEEP(*(.debug_typenames)); }
+    .debug_varnames  0 : { KEEP(*(.debug_varnames)); }
+
+    /* DWARF Other */
+    .debug_ranges  0 : { KEEP(*(.debug_ranges)); }
+    .debug_line_str 0 : { KEEP(*(.debug_line_str)); }
+
+    /DISCARD/ :
+    {
+        *(.fini_array*)
+        *(.note*)
+        *(.dtors*)
+        *(.debug_gdb_scripts*)
+    }
+}

+ 198 - 0
crates/eonix_hal/src/mm.rs

@@ -0,0 +1,198 @@
+use core::{
+    alloc::{AllocError, Allocator, Layout},
+    cell::RefCell,
+    ptr::NonNull,
+};
+use eonix_mm::{
+    address::{AddrOps as _, PRange},
+    paging::{PageAlloc, UnmanagedRawPage, PAGE_SIZE, PFN},
+};
+
+pub use crate::arch::mm::{ArchMemory, ArchPagingMode, ArchPhysAccess, GLOBAL_PAGE_TABLE};
+
+pub struct BasicPageAlloc {
+    ranges: [Option<PRange>; Self::MAX],
+    head: usize,
+    tail: usize,
+}
+
+struct ScopedAllocInner<'a> {
+    _memory: &'a mut [u8],
+    current: NonNull<[u8]>,
+    allocated_count: usize,
+}
+
+pub struct ScopedAllocator<'a> {
+    inner: RefCell<ScopedAllocInner<'a>>,
+}
+
+impl BasicPageAlloc {
+    const MAX: usize = 32;
+
+    pub const fn new() -> Self {
+        Self {
+            ranges: [None; Self::MAX],
+            head: 0,
+            tail: 0,
+        }
+    }
+
+    fn alloc_one(&mut self) -> PFN {
+        assert_ne!(self.head, self.tail, "No free pages available");
+        let mut range = self.ranges[self.head].take().unwrap();
+        range = range.shrink(PAGE_SIZE);
+
+        let pfn = PFN::from(range.end());
+
+        if range.len() != 0 {
+            self.ranges[self.head] = Some(range);
+        } else {
+            self.head += 1;
+            self.head %= Self::MAX;
+        }
+
+        pfn
+    }
+
+    fn alloc_order(&mut self, order: u32) -> PFN {
+        assert!(order <= 4);
+        let me = core::mem::replace(self, Self::new());
+
+        let mut found = None;
+        for mut range in me.into_iter() {
+            if found.is_some() || range.len() < (PAGE_SIZE << order) {
+                self.add_range(range);
+                continue;
+            }
+
+            range = range.shrink(PAGE_SIZE << order);
+            found = Some(PFN::from(range.end()));
+
+            if range.len() != 0 {
+                self.add_range(range);
+            }
+        }
+
+        found.expect("No free pages available for the requested order")
+    }
+
+    pub fn add_range(&mut self, range: PRange) {
+        let tail = self.tail;
+
+        self.tail += 1;
+        self.tail %= Self::MAX;
+
+        if self.tail == self.head {
+            panic!("Page allocator is full");
+        }
+
+        self.ranges[tail] = Some(PRange::new(range.start().ceil(), range.end().floor()));
+    }
+
+    pub fn alloc(&mut self, order: u32) -> PFN {
+        match order {
+            0 => self.alloc_one(),
+            ..=4 => self.alloc_order(order),
+            _ => panic!("Order {} is too large for BasicPageAlloc", order),
+        }
+    }
+
+    pub fn into_iter(self) -> impl Iterator<Item = PRange> {
+        self.ranges
+            .into_iter()
+            .cycle()
+            .skip(self.head)
+            .map_while(|x| x)
+    }
+}
+
+#[derive(Clone)]
+pub struct BasicPageAllocRef<'a>(&'a RefCell<BasicPageAlloc>);
+
+impl<'a> BasicPageAllocRef<'a> {
+    pub const fn new(alloc: &'a RefCell<BasicPageAlloc>) -> Self {
+        Self(alloc)
+    }
+}
+
+impl PageAlloc for BasicPageAllocRef<'_> {
+    type RawPage = UnmanagedRawPage;
+
+    fn alloc_order(&self, order: u32) -> Option<Self::RawPage> {
+        Some(Self::RawPage::new(self.0.borrow_mut().alloc(order), order))
+    }
+
+    unsafe fn dealloc(&self, _: Self::RawPage) {
+        panic!("Dealloc is not supported in BasicPageAlloc");
+    }
+
+    fn has_management_over(&self, _: Self::RawPage) -> bool {
+        true
+    }
+}
+
+impl<'a> ScopedAllocator<'a> {
+    pub fn new(memory: &'a mut [u8]) -> Self {
+        ScopedAllocator {
+            inner: RefCell::new(ScopedAllocInner {
+                current: NonNull::new(memory).unwrap(),
+                _memory: memory,
+                allocated_count: 0,
+            }),
+        }
+    }
+
+    pub fn with_alloc<'b, 'r, O>(&'r self, func: impl FnOnce(&'b ScopedAllocator<'a>) -> O) -> O
+    where
+        'a: 'b,
+        'r: 'b,
+    {
+        func(self)
+    }
+}
+
+unsafe impl Allocator for &ScopedAllocator<'_> {
+    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
+        let mut inner = self.inner.borrow_mut();
+        let memory = &mut inner.current;
+
+        let addr: NonNull<u8> = memory.cast();
+        let offset = addr.align_offset(layout.align());
+
+        if offset + layout.size() > memory.len() {
+            return Err(AllocError);
+        }
+
+        let allocated = unsafe {
+            // SAFETY: `addr + offset` won't overflow.
+            NonNull::slice_from_raw_parts(addr.add(offset), layout.size())
+        };
+
+        unsafe {
+            // SAFETY: `allocated + layout.size()` won't overflow.
+            *memory = NonNull::slice_from_raw_parts(
+                allocated.cast::<u8>().add(layout.size()),
+                memory.len() - offset - layout.size(),
+            );
+        }
+
+        inner.allocated_count += 1;
+        Ok(allocated)
+    }
+
+    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {
+        self.inner.borrow_mut().allocated_count -= 1;
+    }
+}
+
+impl Drop for ScopedAllocator<'_> {
+    fn drop(&mut self) {
+        let inner = self.inner.borrow();
+        if inner.allocated_count > 0 {
+            panic!(
+                "Memory leak detected: {} allocations not deallocated",
+                inner.allocated_count
+            );
+        }
+    }
+}

+ 1 - 0
crates/eonix_hal/src/processor.rs

@@ -0,0 +1 @@
+pub use crate::arch::cpu::{UserTLS, CPU};

+ 5 - 0
crates/eonix_hal/src/trap.rs

@@ -0,0 +1,5 @@
+use eonix_hal_traits::trap::IsRawTrapContext;
+
+pub use crate::arch::trap::{disable_irqs, disable_irqs_save, enable_irqs, IrqState, TrapContext};
+
+struct _CheckTrapContext(IsRawTrapContext<TrapContext>);

+ 1 - 1
crates/eonix_mm/src/address.rs

@@ -7,7 +7,7 @@ mod vaddr;
 pub use addr::{Addr, AddrOps};
 pub use addr_range::AddrRange;
 pub use error::AddressError;
-pub use paddr::PAddr;
+pub use paddr::{PAddr, PhysAccess};
 pub use vaddr::VAddr;
 
 pub type PRange = AddrRange<PAddr>;

+ 2 - 0
crates/eonix_mm/src/address/addr.rs

@@ -1,6 +1,7 @@
 use crate::paging::PAGE_SIZE;
 use core::ops::{Add, Sub};
 
+#[doc(notable_trait)]
 pub trait Addr:
     Sized
     + Copy
@@ -17,6 +18,7 @@ pub trait Addr:
     fn addr(self) -> usize;
 }
 
+#[doc(notable_trait)]
 pub trait AddrOps: Sized {
     fn offset_in(self, size: usize) -> usize;
 

+ 24 - 0
crates/eonix_mm/src/address/paddr.rs

@@ -3,8 +3,32 @@ use crate::paging::{PAGE_SIZE_BITS, PFN};
 use core::{
     fmt,
     ops::{Add, Sub},
+    ptr::NonNull,
 };
 
+pub trait PhysAccess {
+    /// Translate the data that this address is pointing to into kernel
+    /// accessible pointer. Use it with care.
+    ///
+    /// # Panic
+    /// If the address is not properly aligned.
+    ///
+    /// # Safety
+    /// The caller must ensure that the data is of type `T`.
+    /// Otherwise, it may lead to undefined behavior.
+    unsafe fn as_ptr<T>(paddr: PAddr) -> NonNull<T>;
+
+    /// Translate the kernel accessible pointer back into a physical address.
+    ///
+    /// # Panic
+    /// If the pointer is not properly aligned.
+    ///
+    /// # Safety
+    /// The caller must ensure that the pointer is valid and points to a
+    /// valid physical memory location.
+    unsafe fn from_ptr<T>(ptr: NonNull<T>) -> PAddr;
+}
+
 #[repr(transparent)]
 #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
 pub struct PAddr(usize);

+ 1 - 0
crates/eonix_mm/src/lib.rs

@@ -1,4 +1,5 @@
 #![no_std]
+#![feature(doc_notable_trait)]
 
 pub mod address;
 pub mod page_table;

+ 54 - 22
crates/eonix_mm/src/page_table/page_table.rs

@@ -42,10 +42,27 @@ where
     A: PageAlloc,
     X: PageAccess,
 {
-    pub fn new_in<A1: PageAlloc>(kernel_root_table_page: &Page<A1>, alloc: A) -> Self {
+    pub fn with_root_table(root_table_page: Page<A>) -> Self {
+        Self {
+            root_table_page,
+            phantom: PhantomData,
+        }
+    }
+
+    pub fn clone_global<'b, B>(&self) -> PageTable<'b, M, B, X>
+    where
+        B: GlobalPageAlloc,
+    {
+        self.clone_in(B::global())
+    }
+
+    pub fn clone_in<'b, B>(&self, alloc: B) -> PageTable<'b, M, B, X>
+    where
+        B: PageAlloc,
+    {
         let new_root_table_page = Page::alloc_in(alloc);
         let new_table_data = X::get_ptr_for_page(&new_root_table_page);
-        let kernel_table_data = X::get_ptr_for_page(kernel_root_table_page);
+        let kernel_table_data = X::get_ptr_for_page(&self.root_table_page);
 
         unsafe {
             // SAFETY: `new_table_data` and `kernel_table_data` are both valid pointers
@@ -65,10 +82,7 @@ where
             root_page_table.index_mut(idx).take();
         }
 
-        Self {
-            root_table_page: new_root_table_page,
-            phantom: PhantomData,
-        }
+        PageTable::with_root_table(new_root_table_page)
     }
 
     pub fn addr(&self) -> PAddr {
@@ -87,7 +101,7 @@ where
     }
 
     pub fn iter_kernel(&self, range: VRange) -> impl Iterator<Item = &mut M::Entry> {
-        Self::iter_kernel_levels(self, range, M::LEVELS)
+        self.iter_kernel_levels(range, M::LEVELS)
     }
 
     /// Iterates over the kernel space entries in the page table for the specified levels.
@@ -115,17 +129,47 @@ where
         range: VRange,
         levels: &'static [PageTableLevel],
     ) -> impl Iterator<Item = &mut M::Entry> {
-        let alloc = self.root_table_page.allocator();
+        self.iter_kernel_in(range, levels, self.root_table_page.allocator())
+    }
+
+    /// Iterates over the kernel space entries in the page table for the specified levels
+    /// with a given page allocator.
+    ///
+    /// # Parameters
+    /// - `range`: The virtual address range to iterate over.
+    /// - `levels`: A slice of `PageTableLevel` that specifies which levels of the page table
+    ///   should be included in the iteration. Each level corresponds to a level in the page
+    ///   table hierarchy, and the iterator will traverse entries at these levels.
+    /// - `alloc`: A page allocator that provides memory for the page table entries.
+    ///
+    /// # Returns
+    /// An iterator over mutable references to the page table entries (`M::Entry`) within the
+    /// specified range and levels.
+    ///
+    /// # Example
+    /// ```no_run
+    /// let range = VRange::new(0x1234000, 0x1300000);
+    /// let levels = &M::LEVELS[..2];
+    /// for pte in page_table.iter_kernel_in(range, levels, NoAlloc) {
+    ///     // Process each entry
+    /// }
+    /// ```
+    pub fn iter_kernel_in<A1: PageAlloc>(
+        &self,
+        range: VRange,
+        levels: &'static [PageTableLevel],
+        alloc: A1,
+    ) -> impl Iterator<Item = &mut M::Entry> {
         let page_table_ptr = X::get_ptr_for_page(&self.root_table_page);
         let root_page_table = unsafe {
             // SAFETY: `page_table_ptr` is a valid pointer to a page table.
             M::RawTable::from_ptr(page_table_ptr)
         };
 
-        PageTableIterator::<M, A, X, KernelIterator>::with_levels(
+        PageTableIterator::<M, A1, X, KernelIterator>::with_levels(
             root_page_table,
             range,
-            alloc.clone(),
+            alloc,
             levels,
         )
     }
@@ -161,18 +205,6 @@ where
     }
 }
 
-impl<'a, M, A, X> PageTable<'a, M, A, X>
-where
-    M: PagingMode,
-    M::Entry: 'a,
-    A: GlobalPageAlloc,
-    X: PageAccess,
-{
-    pub fn new<A1: PageAlloc>(kernel_root_table_page: &Page<A1>) -> Self {
-        Self::new_in(kernel_root_table_page, A::global())
-    }
-}
-
 impl<'a, M, A, X> Drop for PageTable<'a, M, A, X>
 where
     M: PagingMode,

+ 1 - 5
crates/eonix_mm/src/page_table/paging_mode.rs

@@ -1,15 +1,11 @@
 use super::{RawPageTable, PTE};
-use crate::{
-    address::{Addr as _, VAddr},
-    paging::PFN,
-};
+use crate::address::{Addr as _, VAddr};
 
 pub trait PagingMode {
     type Entry: PTE;
     type RawTable<'a>: RawPageTable<'a, Entry = Self::Entry>;
 
     const LEVELS: &'static [PageTableLevel];
-    const KERNEL_ROOT_TABLE_PFN: PFN;
 }
 
 #[derive(Clone, Copy, PartialOrd, PartialEq)]

+ 4 - 13
crates/eonix_mm/src/page_table/pte.rs

@@ -23,10 +23,12 @@ bitflags! {
         const COPY_ON_WRITE = 256;
         const MAPPED = 512;
         const ANONYMOUS = 1024;
+        const HUGE = 2048;
     }
 }
 
-pub trait RawAttribute: Copy {
+#[doc(notable_trait)]
+pub trait RawAttribute: Copy + From<PageAttribute> + From<TableAttribute> {
     /// Create a new attribute representing a non-present page.
     fn null() -> Self;
 
@@ -43,20 +45,9 @@ pub trait RawAttribute: Copy {
     /// # Panic
     /// The implementor should panic if invalid combinations of flags are present.
     fn as_page_attr(self) -> Option<PageAttribute>;
-
-    /// Convert the attribute to a raw value.
-    ///
-    /// # Panic
-    /// The implementor should panic if invalid combinations of flags are present.
-    fn from_table_attr(table_attr: TableAttribute) -> Self;
-
-    /// Convert the attribute to a raw value.
-    ///
-    /// # Panic
-    /// The implementor should panic if invalid combinations of flags are present.
-    fn from_page_attr(page_attr: PageAttribute) -> Self;
 }
 
+#[doc(notable_trait)]
 pub trait PTE: Sized {
     type Attr: RawAttribute;
 

+ 1 - 1
crates/eonix_mm/src/page_table/pte_iterator.rs

@@ -41,7 +41,7 @@ pub trait IteratorType<M: PagingMode> {
 
             pte.set(
                 page.into_raw(),
-                <M::Entry as PTE>::Attr::from_table_attr(Self::page_table_attributes()),
+                <M::Entry as PTE>::Attr::from(Self::page_table_attributes()),
             );
 
             unsafe {

+ 2 - 2
crates/eonix_mm/src/paging.rs

@@ -4,6 +4,6 @@ mod pfn;
 mod raw_page;
 
 pub use page::{Page, PageAccess, PageBlock, PAGE_SIZE, PAGE_SIZE_BITS};
-pub use page_alloc::{GlobalPageAlloc, PageAlloc};
+pub use page_alloc::{GlobalPageAlloc, NoAlloc, PageAlloc};
 pub use pfn::PFN;
-pub use raw_page::RawPage;
+pub use raw_page::{RawPage, UnmanagedRawPage};

+ 17 - 3
crates/eonix_mm/src/paging/page.rs

@@ -1,5 +1,5 @@
 use super::{GlobalPageAlloc, PageAlloc, RawPage as _, PFN};
-use crate::address::{AddrRange, PAddr};
+use crate::address::{AddrRange, PAddr, PhysAccess};
 use core::{fmt, mem::ManuallyDrop, ptr::NonNull, sync::atomic::Ordering};
 
 pub const PAGE_SIZE: usize = 4096;
@@ -14,6 +14,7 @@ pub const PAGE_SIZE_BITS: u32 = PAGE_SIZE.trailing_zeros();
 pub struct PageBlock([u8; PAGE_SIZE]);
 
 /// A trait that provides the kernel access to the page.
+#[doc(notable_trait)]
 pub trait PageAccess {
     /// Returns a kernel-accessible pointer to the page referenced by the given
     /// physical frame number.
@@ -99,7 +100,7 @@ where
     where
         F: FnOnce(&Self) -> O,
     {
-        unsafe { Self::with_raw_in(pfn, func, A::global()) }
+        unsafe { Self::with_raw_in(pfn, A::global(), func) }
     }
 
     /// Do some work with the page without touching the reference count with the same
@@ -187,7 +188,7 @@ where
     ///
     /// # Safety
     /// Check `from_raw_in()` for the safety requirements.
-    pub unsafe fn with_raw_in<F, O>(pfn: PFN, func: F, alloc: A) -> O
+    pub unsafe fn with_raw_in<F, O>(pfn: PFN, alloc: A, func: F) -> O
     where
         F: FnOnce(&Self) -> O,
     {
@@ -304,3 +305,16 @@ impl<A: PageAlloc> fmt::Debug for Page<A> {
         )
     }
 }
+
+impl<T> PageAccess for T
+where
+    T: PhysAccess,
+{
+    unsafe fn get_ptr_for_pfn(pfn: PFN) -> NonNull<PageBlock> {
+        unsafe {
+            // SAFETY: The physical address of a existing page must be
+            //         aligned to the page size.
+            T::as_ptr(PAddr::from(pfn))
+        }
+    }
+}

+ 28 - 1
crates/eonix_mm/src/paging/page_alloc.rs

@@ -1,4 +1,4 @@
-use super::RawPage;
+use super::{raw_page::UnmanagedRawPage, RawPage};
 
 /// A trait for allocating and deallocating pages of memory.
 ///
@@ -6,6 +6,7 @@ use super::RawPage;
 /// behavior, meaning that the allocators are to be passed around by value and stored in
 /// managed data structures. This is because the allocator may be used to deallocate the
 /// pages it allocates.
+#[doc(notable_trait)]
 pub trait PageAlloc: Clone {
     type RawPage: RawPage;
 
@@ -37,11 +38,15 @@ pub trait PageAlloc: Clone {
 /// A trait for global page allocators.
 ///
 /// Global means that we can get an instance of the allocator from anywhere in the kernel.
+#[doc(notable_trait)]
 pub trait GlobalPageAlloc: PageAlloc + 'static {
     /// Get the global page allocator.
     fn global() -> Self;
 }
 
+#[derive(Clone)]
+pub struct NoAlloc;
+
 impl<'a, A> PageAlloc for &'a A
 where
     A: PageAlloc,
@@ -60,3 +65,25 @@ where
         (*self).has_management_over(raw_page)
     }
 }
+
+impl PageAlloc for NoAlloc {
+    type RawPage = UnmanagedRawPage;
+
+    fn alloc_order(&self, _: u32) -> Option<Self::RawPage> {
+        panic!("`NoAlloc` cannot allocate pages");
+    }
+
+    unsafe fn dealloc(&self, _: Self::RawPage) {
+        panic!("`NoAlloc` cannot free pages");
+    }
+
+    fn has_management_over(&self, _: Self::RawPage) -> bool {
+        true
+    }
+}
+
+impl GlobalPageAlloc for NoAlloc {
+    fn global() -> Self {
+        Self
+    }
+}

+ 41 - 0
crates/eonix_mm/src/paging/raw_page.rs

@@ -3,9 +3,50 @@ use core::sync::atomic::AtomicUsize;
 
 /// A `RawPage` represents a page of memory in the kernel. It is a low-level
 /// representation of a page that is used by the kernel to manage memory.
+#[doc(notable_trait)]
 pub trait RawPage: Clone + Copy + From<PFN> + Into<PFN> {
     fn order(&self) -> u32;
     fn refcount(&self) -> &AtomicUsize;
 
     fn is_present(&self) -> bool;
 }
+
+#[derive(Clone, Copy)]
+pub struct UnmanagedRawPage(PFN, u32);
+
+/// Unmanaged raw pages should always have a non-zero refcount to
+/// avoid `free()` from being called.
+static UNMANAGED_RAW_PAGE_CLONE_COUNT: AtomicUsize = AtomicUsize::new(1);
+
+impl UnmanagedRawPage {
+    pub const fn new(pfn: PFN, order: u32) -> Self {
+        Self(pfn, order)
+    }
+}
+
+impl From<PFN> for UnmanagedRawPage {
+    fn from(value: PFN) -> Self {
+        Self::new(value, 0)
+    }
+}
+
+impl Into<PFN> for UnmanagedRawPage {
+    fn into(self) -> PFN {
+        let Self(pfn, _) = self;
+        pfn
+    }
+}
+
+impl RawPage for UnmanagedRawPage {
+    fn order(&self) -> u32 {
+        self.1
+    }
+
+    fn refcount(&self) -> &AtomicUsize {
+        &UNMANAGED_RAW_PAGE_CLONE_COUNT
+    }
+
+    fn is_present(&self) -> bool {
+        true
+    }
+}

+ 1 - 6
crates/eonix_percpu/Cargo.toml

@@ -3,10 +3,5 @@ name = "eonix_percpu"
 version = "0.1.0"
 edition = "2024"
 
-[lib]
-proc-macro = true
-
 [dependencies]
-proc-macro2 = "1.0"
-quote = "1.0"
-syn = { version = "2.0", features = ["full"] }
+eonix_percpu_macros = { path = "./eonix_percpu_macros" }

+ 14 - 0
crates/eonix_percpu/eonix_percpu_macros/Cargo.toml

@@ -0,0 +1,14 @@
+[package]
+name = "eonix_percpu_macros"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+arch_macros = { path = "../../../arch/arch_macros" }
+
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "2.0", features = ["full"] }

+ 42 - 13
arch/percpu-macros/src/lib.rs → crates/eonix_percpu/eonix_percpu_macros/src/lib.rs

@@ -1,18 +1,19 @@
 extern crate proc_macro;
 
-use proc_macro::TokenStream;
+use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
-use syn::{parse_macro_input, ItemStatic};
+use syn::{parse2, Ident, ItemStatic, Type};
 
-mod arch;
-
-#[proc_macro_attribute]
-pub fn define_percpu(attrs: TokenStream, item: TokenStream) -> TokenStream {
+fn define_percpu_impl(
+    attrs: TokenStream,
+    item: TokenStream,
+    get_percpu_pointer: fn(&Ident, &Type) -> TokenStream,
+) -> TokenStream {
     if !attrs.is_empty() {
         panic!("`define_percpu` attribute does not take any arguments");
     }
 
-    let item = parse_macro_input!(item as ItemStatic);
+    let item = parse2::<ItemStatic>(item).unwrap();
     let vis = &item.vis;
     let ident = &item.ident;
     let ty = &item.ty;
@@ -53,7 +54,7 @@ pub fn define_percpu(attrs: TokenStream, item: TokenStream) -> TokenStream {
         quote! {}
     };
 
-    let as_ptr = arch::get_percpu_pointer(&inner_ident, &ty);
+    let as_ptr = get_percpu_pointer(&inner_ident, &ty);
 
     quote! {
         #[link_section = ".percpu"]
@@ -116,13 +117,16 @@ pub fn define_percpu(attrs: TokenStream, item: TokenStream) -> TokenStream {
     .into()
 }
 
-#[proc_macro_attribute]
-pub fn define_percpu_shared(attrs: TokenStream, item: TokenStream) -> TokenStream {
+fn define_percpu_shared_impl(
+    attrs: TokenStream,
+    item: TokenStream,
+    get_percpu_pointer: fn(&Ident, &Type) -> TokenStream,
+) -> TokenStream {
     if !attrs.is_empty() {
         panic!("`define_percpu_shared` attribute does not take any arguments");
     }
 
-    let item = parse_macro_input!(item as ItemStatic);
+    let item = parse2::<ItemStatic>(item).unwrap();
     let vis = &item.vis;
     let ident = &item.ident;
     let ty = &item.ty;
@@ -131,7 +135,7 @@ pub fn define_percpu_shared(attrs: TokenStream, item: TokenStream) -> TokenStrea
     let inner_ident = format_ident!("_percpu_shared_inner_{}", ident);
     let access_ident = format_ident!("_access_shared_{}", ident);
 
-    let as_ptr = arch::get_percpu_pointer(&inner_ident, &ty);
+    let as_ptr = get_percpu_pointer(&inner_ident, &ty);
 
     quote! {
         #[link_section = ".percpu"]
@@ -153,7 +157,7 @@ pub fn define_percpu_shared(attrs: TokenStream, item: TokenStream) -> TokenStrea
 
             pub fn get_for_cpu(&self, cpuid: usize) -> Option<& #ty > {
                 let offset = & #inner_ident as *const _ as usize;
-                let base = ::arch::PercpuArea::get_for(cpuid);
+                let base = ::eonix_percpu::PercpuArea::get_for(cpuid);
                 base.map(|base| unsafe { base.byte_add(offset).cast().as_ref() })
             }
         }
@@ -177,5 +181,30 @@ pub fn define_percpu_shared(attrs: TokenStream, item: TokenStream) -> TokenStrea
             }
         }
     }
+}
+
+#[proc_macro_attribute]
+pub fn define_percpu_x86_64(
+    attrs: proc_macro::TokenStream,
+    item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    define_percpu_impl(
+        attrs.into(),
+        item.into(),
+        arch_macros::x86_64::percpu::get_percpu_pointer,
+    )
+    .into()
+}
+
+#[proc_macro_attribute]
+pub fn define_percpu_shared_x86_64(
+    attrs: proc_macro::TokenStream,
+    item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    define_percpu_shared_impl(
+        attrs.into(),
+        item.into(),
+        arch_macros::x86_64::percpu::get_percpu_pointer,
+    )
     .into()
 }

+ 0 - 24
crates/eonix_percpu/src/arch.rs

@@ -1,24 +0,0 @@
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::{Ident, Type};
-
-/// Get the base address for percpu variables of the current thread.
-pub fn get_percpu_pointer(percpu: &Ident, ty: &Type) -> TokenStream {
-    quote! {
-        {
-            #[cfg(target_arch = "x86_64")]
-            {
-                let base: *mut #ty;
-                ::core::arch::asm!(
-                    "mov %gs:0, {address}",
-                    "add ${percpu_pointer}, {address}",
-                    percpu_pointer = sym #percpu,
-                    address = out(reg) base,
-                    options(att_syntax)
-                );
-                base
-            }
-        }
-    }
-    .into()
-}

+ 58 - 160
crates/eonix_percpu/src/lib.rs

@@ -1,181 +1,79 @@
-extern crate proc_macro;
+#![no_std]
 
-use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, ItemStatic};
+use core::alloc::Layout;
+use core::ptr::null_mut;
+use core::ptr::NonNull;
+use core::sync::atomic::AtomicPtr;
+use core::sync::atomic::Ordering;
 
-mod arch;
+#[cfg(target_arch = "x86_64")]
+pub use eonix_percpu_macros::define_percpu_x86_64 as define_percpu;
 
-#[proc_macro_attribute]
-pub fn define_percpu(attrs: TokenStream, item: TokenStream) -> TokenStream {
-    if !attrs.is_empty() {
-        panic!("`define_percpu` attribute does not take any arguments");
-    }
-
-    let item = parse_macro_input!(item as ItemStatic);
-    let vis = &item.vis;
-    let ident = &item.ident;
-    let ty = &item.ty;
-    let expr = &item.expr;
+#[cfg(target_arch = "x86_64")]
+pub use eonix_percpu_macros::define_percpu_shared_x86_64 as define_percpu_shared;
 
-    let is_bool = quote!(#ty).to_string().as_str() == "bool";
-    let is_integer =
-        ["u8", "u16", "u32", "u64", "usize"].contains(&quote!(#ty).to_string().as_str());
+const MAX_CPUS: usize = 256;
 
-    let is_atomic_like = is_bool || is_integer || quote!(#ty).to_string().contains("NonNull");
+#[repr(align(16))]
+pub struct PercpuData();
 
-    let inner_ident = format_ident!("_percpu_inner_{}", ident);
-    let access_ident = format_ident!("_access_{}", ident);
+pub struct PercpuArea {
+    data: NonNull<PercpuData>,
+}
 
-    let integer_methods = if is_integer {
-        quote! {
-            pub fn add(&self, value: #ty) {
-                *unsafe { self.as_mut() } += value;
-            }
+static PERCPU_POINTERS: [AtomicPtr<PercpuData>; MAX_CPUS] =
+    [const { AtomicPtr::new(null_mut()) }; MAX_CPUS];
 
-            pub fn sub(&self, value: #ty) {
-                *unsafe { self.as_mut() } -= value;
-            }
+impl PercpuArea {
+    fn len() -> usize {
+        unsafe extern "C" {
+            fn PERCPU_LENGTH();
         }
-    } else {
-        quote! {}
-    };
-
-    let preempt_disable = if !is_atomic_like {
-        quote! { eonix_preempt::disable(); }
-    } else {
-        quote! {}
-    };
-
-    let preempt_enable = if !is_atomic_like {
-        quote! { eonix_preempt::enable(); }
-    } else {
-        quote! {}
-    };
-
-    let as_ptr = arch::get_percpu_pointer(&inner_ident, &ty);
-
-    quote! {
-        #[link_section = ".percpu"]
-        #[allow(non_upper_case_globals)]
-        static mut #inner_ident: #ty = #expr;
-        #[allow(non_camel_case_types)]
-        #vis struct #access_ident;
-        #vis static #ident: #access_ident = #access_ident;
-
-        impl #access_ident {
-            /// # Safety
-            /// This function is unsafe because it allows for mutable aliasing of the percpu
-            /// variable.
-            /// Make sure that preempt is disabled when calling this function.
-            pub unsafe fn as_ptr(&self) -> *mut #ty {
-                #as_ptr
-            }
-
-            pub fn get(&self) -> #ty {
-                #preempt_disable
-                let value = unsafe { self.as_ptr().read() };
-                #preempt_enable
-                value
-            }
-
-            pub fn set(&self, value: #ty) {
-                #preempt_disable
-                unsafe { self.as_ptr().write(value) }
-                #preempt_enable
-            }
-
-            pub fn swap(&self, mut value: #ty) -> #ty {
-                #preempt_disable
-                unsafe { self.as_ptr().swap(&mut value) }
-                #preempt_enable
-                value
-            }
-
-            /// # Safety
-            /// This function is unsafe because it allows for immutable aliasing of the percpu
-            /// variable.
-            /// Make sure that preempt is disabled when calling this function.
-            pub unsafe fn as_ref(&self) -> & #ty {
-                // SAFETY: This is safe because `as_ptr()` is guaranteed to be valid.
-                self.as_ptr().as_ref().unwrap()
-            }
-
-            /// # Safety
-            /// This function is unsafe because it allows for mutable aliasing of the percpu
-            /// variable.
-            /// Make sure that preempt is disabled when calling this function.
-            pub unsafe fn as_mut(&self) -> &mut #ty {
-                // SAFETY: This is safe because `as_ptr()` is guaranteed to be valid.
-                self.as_ptr().as_mut().unwrap()
-            }
-
-            #integer_methods
+        let len = PERCPU_LENGTH as usize;
+
+        assert_ne!(len, 0, "Percpu length should not be zero.");
+        len
+    }
+
+    fn data_start() -> NonNull<u8> {
+        unsafe extern "C" {
+            fn PERCPU_DATA_START();
         }
+
+        let addr = PERCPU_DATA_START as usize;
+        NonNull::new(addr as *mut _).expect("Percpu data should not be null.")
     }
-    .into()
-}
 
-#[proc_macro_attribute]
-pub fn define_percpu_shared(attrs: TokenStream, item: TokenStream) -> TokenStream {
-    if !attrs.is_empty() {
-        panic!("`define_percpu_shared` attribute does not take any arguments");
+    fn layout() -> Layout {
+        Layout::from_size_align(Self::len(), align_of::<PercpuData>()).expect("Invalid layout.")
     }
 
-    let item = parse_macro_input!(item as ItemStatic);
-    let vis = &item.vis;
-    let ident = &item.ident;
-    let ty = &item.ty;
-    let expr = &item.expr;
-
-    let inner_ident = format_ident!("_percpu_shared_inner_{}", ident);
-    let access_ident = format_ident!("_access_shared_{}", ident);
-
-    let as_ptr = arch::get_percpu_pointer(&inner_ident, &ty);
-
-    quote! {
-        #[link_section = ".percpu"]
-        #[allow(non_upper_case_globals)]
-        static #inner_ident: #ty = #expr;
-        #[allow(non_camel_case_types)]
-        #vis struct #access_ident;
-        #vis static #ident: #access_ident = #access_ident;
-
-        impl #access_ident {
-            fn as_ptr(&self) -> *const #ty {
-                unsafe { ( #as_ptr ) }
-            }
-
-            pub fn get_ref(&self) -> & #ty {
-                // SAFETY: This is safe because `as_ptr()` is guaranteed to be valid.
-                unsafe { self.as_ptr().as_ref().unwrap() }
-            }
-
-            pub fn get_for_cpu(&self, cpuid: usize) -> Option<& #ty > {
-                let offset = & #inner_ident as *const _ as usize;
-                let base = ::arch::PercpuArea::get_for(cpuid);
-                base.map(|base| unsafe { base.byte_add(offset).cast().as_ref() })
-            }
-        }
+    pub fn new<F>(allocate: F) -> Self
+    where
+        F: FnOnce(Layout) -> NonNull<u8>,
+    {
+        let data_pointer = allocate(Self::layout());
 
-        impl ::core::ops::Deref for #access_ident {
-            type Target = #ty;
+        unsafe {
+            // SAFETY: The `data_pointer` is of valid length and properly aligned.
+            data_pointer.copy_from_nonoverlapping(Self::data_start(), Self::len());
+        }
 
-            fn deref(&self) -> &Self::Target {
-                self.get_ref()
-            }
+        Self {
+            data: data_pointer.cast(),
         }
+    }
 
-        impl<T> ::core::convert::AsRef<T> for #access_ident
-        where
-            <Self as ::core::ops::Deref>::Target: ::core::convert::AsRef<T>,
-        {
-            fn as_ref(&self) -> &T {
-                use ::core::ops::Deref;
+    pub fn register(self, cpuid: usize) {
+        PERCPU_POINTERS[cpuid].store(self.data.as_ptr(), Ordering::Release);
+    }
 
-                self.deref().as_ref()
-            }
-        }
+    pub fn get_for(cpuid: usize) -> Option<NonNull<()>> {
+        let pointer = PERCPU_POINTERS[cpuid].load(Ordering::Acquire);
+        NonNull::new(pointer.cast())
+    }
+
+    pub fn setup(&mut self, func: impl FnOnce(NonNull<PercpuData>)) {
+        func(self.data)
     }
-    .into()
 }

+ 1 - 1
crates/eonix_preempt/Cargo.toml

@@ -4,4 +4,4 @@ version = "0.1.0"
 edition = "2024"
 
 [dependencies]
-arch = { path = "../../arch" }
+eonix_percpu = { path = "../eonix_percpu" }

+ 42 - 11
crates/eonix_preempt/src/lib.rs

@@ -1,8 +1,18 @@
 #![no_std]
 
-use core::sync::atomic::{compiler_fence, Ordering};
+use core::{
+    ops::{Deref, DerefMut},
+    sync::atomic::{compiler_fence, Ordering},
+};
 
-#[arch::define_percpu]
+pub struct PreemptGuard<T>
+where
+    T: ?Sized,
+{
+    value: T,
+}
+
+#[eonix_percpu::define_percpu]
 static PREEMPT_COUNT: usize = 0;
 
 #[inline(always)]
@@ -66,17 +76,38 @@ macro_rules! assert_preempt_count_eq {
     }};
 }
 
-#[unsafe(no_mangle)]
-pub extern "C" fn r_preempt_disable() {
-    disable();
+impl<T> PreemptGuard<T> {
+    pub fn new(value: T) -> Self {
+        disable();
+        Self { value }
+    }
+}
+
+impl<T> Deref for PreemptGuard<T>
+where
+    T: ?Sized,
+{
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.value
+    }
 }
 
-#[unsafe(no_mangle)]
-pub extern "C" fn r_preempt_enable() {
-    enable();
+impl<T> DerefMut for PreemptGuard<T>
+where
+    T: ?Sized,
+{
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.value
+    }
 }
 
-#[unsafe(no_mangle)]
-pub extern "C" fn r_preempt_count() -> usize {
-    count()
+impl<T> Drop for PreemptGuard<T>
+where
+    T: ?Sized,
+{
+    fn drop(&mut self) {
+        enable();
+    }
 }

+ 2 - 0
crates/eonix_runtime/Cargo.toml

@@ -6,7 +6,9 @@ edition = "2024"
 [dependencies]
 arch = { path = "../../arch" }
 atomic_unique_refcell = { path = "../atomic_unique_refcell" }
+eonix_hal = { path = "../eonix_hal" }
 eonix_log = { path = "../eonix_log" }
+eonix_percpu = { path = "../eonix_percpu" }
 eonix_preempt = { path = "../eonix_preempt" }
 eonix_sync = { path = "../eonix_sync" }
 pointers = { path = "../pointers" }

+ 10 - 10
crates/eonix_runtime/src/context.rs

@@ -1,51 +1,51 @@
 use core::{cell::UnsafeCell, mem::transmute};
+use eonix_hal::context::TaskContext;
+use eonix_hal::traits::context::RawTaskContext;
 
 #[derive(Debug)]
-pub struct ExecutionContext(UnsafeCell<arch::TaskContext>);
+pub struct ExecutionContext(UnsafeCell<TaskContext>);
 
 unsafe impl Sync for ExecutionContext {}
 
 impl ExecutionContext {
     pub const fn new() -> Self {
-        Self(UnsafeCell::new(arch::TaskContext::new()))
+        Self(UnsafeCell::new(TaskContext::new()))
     }
 
     pub fn set_ip(&mut self, ip: usize) {
         let Self(context) = self;
-        context.get_mut().ip(ip);
+        context.get_mut().set_program_counter(ip);
     }
 
     pub fn set_sp(&mut self, sp: usize) {
         let Self(context) = self;
-        context.get_mut().sp(sp);
+        context.get_mut().set_stack_pointer(sp);
     }
 
     pub fn set_interrupt(&mut self, is_enabled: bool) {
         let Self(context) = self;
-        context.get_mut().interrupt(is_enabled);
+        context.get_mut().set_interrupt_enabled(is_enabled);
     }
 
     pub fn call1<T>(&mut self, func: unsafe extern "C" fn(T) -> !, arg: usize) {
         let Self(context) = self;
         context
             .get_mut()
-            .call1(unsafe { transmute(func as *mut ()) }, [arg]);
+            .call(unsafe { transmute(func as *mut ()) }, arg);
     }
 
     pub fn switch_to(&self, to: &Self) {
         let Self(from_ctx) = self;
         let Self(to_ctx) = to;
         unsafe {
-            arch::TaskContext::switch(&mut *from_ctx.get(), &mut *to_ctx.get());
+            TaskContext::switch(&mut *from_ctx.get(), &mut *to_ctx.get());
         }
     }
 
     pub fn switch_noreturn(&self) -> ! {
-        let mut from_ctx = arch::TaskContext::new();
         let Self(to_ctx) = self;
         unsafe {
-            arch::TaskContext::switch(&mut from_ctx, &mut *to_ctx.get());
+            TaskContext::switch_to_noreturn(&mut *to_ctx.get());
         }
-        unreachable!("We should never return from switch_to_noreturn");
     }
 }

+ 1 - 1
crates/eonix_runtime/src/ready_queue.rs

@@ -2,7 +2,7 @@ use crate::task::Task;
 use alloc::{collections::VecDeque, sync::Arc};
 use eonix_sync::Spin;
 
-#[arch::define_percpu_shared]
+#[eonix_percpu::define_percpu_shared]
 static READYQUEUE: Spin<FifoReadyQueue> = Spin::new(FifoReadyQueue::new());
 
 pub trait ReadyQueue {

+ 3 - 2
crates/eonix_runtime/src/scheduler.rs

@@ -18,10 +18,10 @@ use eonix_sync::{LazyLock, Spin, SpinIrq as _};
 use intrusive_collections::RBTree;
 use pointers::BorrowedArc;
 
-#[arch::define_percpu]
+#[eonix_percpu::define_percpu]
 static CURRENT_TASK: Option<NonNull<Task>> = None;
 
-#[arch::define_percpu]
+#[eonix_percpu::define_percpu]
 static LOCAL_SCHEDULER_CONTEXT: ExecutionContext = ExecutionContext::new();
 
 static TASKS: LazyLock<Spin<RBTree<TaskAdapter>>> =
@@ -92,6 +92,7 @@ impl Scheduler {
             let context: &mut ExecutionContext = LOCAL_SCHEDULER_CONTEXT.as_mut();
             context.set_ip(local_scheduler as _);
             context.set_sp(stack.get_bottom().addr().get() as usize);
+            context.set_interrupt(true);
             eonix_preempt::enable();
         }
 

+ 6 - 7
crates/eonix_sync/Cargo.toml

@@ -1,13 +1,12 @@
+[workspace]
+members = ["eonix_spin", "eonix_sync_rt", "eonix_sync_base"]
+
 [package]
 name = "eonix_sync"
 version = "0.1.0"
 edition = "2024"
 
 [dependencies]
-arch = { path = "../../arch" }
-eonix_preempt = { path = "../eonix_preempt" }
-intrusive-collections = "0.9.7"
-
-[features]
-default = []
-no_check_locked = []
+eonix_spin = { path = "./eonix_spin" }
+eonix_sync_base = { path = "./eonix_sync_base" }
+eonix_sync_rt = { path = "./eonix_sync_rt" }

+ 8 - 0
crates/eonix_sync/eonix_spin/Cargo.toml

@@ -0,0 +1,8 @@
+[package]
+name = "eonix_spin"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+eonix_preempt = { path = "../../eonix_preempt" }
+eonix_sync_base = { path = "../eonix_sync_base" }

+ 1 - 1
crates/eonix_sync/src/spin/guard.rs → crates/eonix_sync/eonix_spin/src/guard.rs

@@ -1,12 +1,12 @@
 use super::{
     ContextUnlock, DisablePreemption, Relax, Spin, SpinContext, SpinRelax, UnlockedContext,
 };
-use crate::{marker::NotSend, UnlockableGuard, UnlockedGuard};
 use core::{
     marker::PhantomData,
     mem::ManuallyDrop,
     ops::{Deref, DerefMut},
 };
+use eonix_sync_base::{NotSend, UnlockableGuard, UnlockedGuard};
 
 pub struct SpinGuard<'a, T, C = DisablePreemption, R = SpinRelax>
 where

+ 11 - 4
crates/eonix_sync/src/spin.rs → crates/eonix_sync/eonix_spin/src/lib.rs

@@ -1,16 +1,15 @@
+#![no_std]
+
 mod guard;
-mod relax;
-mod spin_irq;
 
 use core::{
     cell::UnsafeCell,
     marker::PhantomData,
     sync::atomic::{AtomicBool, Ordering},
 };
+use eonix_sync_base::{Relax, SpinRelax};
 
 pub use guard::{SpinGuard, UnlockedSpinGuard};
-pub use relax::{LoopRelax, Relax, SpinRelax};
-pub use spin_irq::SpinIrq;
 
 pub trait SpinContext {
     fn save() -> Self;
@@ -57,6 +56,14 @@ where
             _phantom: PhantomData,
         }
     }
+
+    pub fn into_inner(mut self) -> T {
+        assert!(
+            !*self.locked.get_mut(),
+            "Spin::take(): Cannot take a locked Spin"
+        );
+        self.value.into_inner()
+    }
 }
 
 impl<T, R> Spin<T, R>

+ 10 - 0
crates/eonix_sync/eonix_sync_base/Cargo.toml

@@ -0,0 +1,10 @@
+[package]
+name = "eonix_sync_base"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+
+[features]
+default = []
+no_check_locked = []

+ 0 - 0
crates/eonix_sync/src/guard.rs → crates/eonix_sync/eonix_sync_base/src/guard.rs


+ 0 - 0
crates/eonix_sync/src/lazy_lock.rs → crates/eonix_sync/eonix_sync_base/src/lazy_lock.rs


+ 13 - 0
crates/eonix_sync/eonix_sync_base/src/lib.rs

@@ -0,0 +1,13 @@
+#![no_std]
+
+mod guard;
+mod lazy_lock;
+mod locked;
+mod marker;
+mod relax;
+
+pub use guard::{UnlockableGuard, UnlockedGuard};
+pub use lazy_lock::LazyLock;
+pub use locked::{AsProof, AsProofMut, Locked, Proof, ProofMut};
+pub use marker::{NotSend, NotSync};
+pub use relax::{LoopRelax, Relax, SpinRelax};

+ 0 - 0
crates/eonix_sync/src/locked.rs → crates/eonix_sync/eonix_sync_base/src/locked.rs


+ 0 - 0
crates/eonix_sync/src/locked/proof.rs → crates/eonix_sync/eonix_sync_base/src/locked/proof.rs


+ 0 - 0
crates/eonix_sync/src/marker.rs → crates/eonix_sync/eonix_sync_base/src/marker.rs


+ 0 - 0
crates/eonix_sync/src/spin/relax.rs → crates/eonix_sync/eonix_sync_base/src/relax.rs


+ 13 - 0
crates/eonix_sync/eonix_sync_rt/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "eonix_sync_rt"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+arch = { path = "../../../arch" }
+eonix_hal = { path = "../../eonix_hal" }
+eonix_preempt = { path = "../../eonix_preempt" }
+eonix_spin = { path = "../eonix_spin" }
+eonix_sync_base = { path = "../eonix_sync_base" }
+
+intrusive-collections = "0.9.7"

+ 11 - 0
crates/eonix_sync/eonix_sync_rt/src/lib.rs

@@ -0,0 +1,11 @@
+#![no_std]
+
+mod mutex;
+mod rwlock;
+mod spin_irq;
+mod wait_list;
+
+pub use mutex::{Mutex, MutexGuard};
+pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
+pub use spin_irq::SpinIrq;
+pub use wait_list::{WaitHandle, WaitList};

+ 0 - 0
crates/eonix_sync/src/mutex.rs → crates/eonix_sync/eonix_sync_rt/src/mutex.rs


+ 1 - 1
crates/eonix_sync/src/mutex/guard.rs → crates/eonix_sync/eonix_sync_rt/src/mutex/guard.rs

@@ -1,9 +1,9 @@
 use super::Mutex;
-use crate::{UnlockableGuard, UnlockedGuard};
 use core::{
     ops::{Deref, DerefMut},
     sync::atomic::Ordering,
 };
+use eonix_sync_base::{UnlockableGuard, UnlockedGuard};
 
 pub struct MutexGuard<'a, T>
 where

+ 0 - 0
crates/eonix_sync/src/rwlock.rs → crates/eonix_sync/eonix_sync_rt/src/rwlock.rs


Vissa filer visades inte eftersom för många filer har ändrats