32 Commits

Author SHA1 Message Date
vasilito 5098d1651f kernel: revert -Z json-target-spec to original nightly-2025-10-03 build
Reverts the prior session's -Z json-target-spec addition
that was breaking the build on nightly-2025-10-03 (the
kernel's rust-toolchain.toml specified toolchain). The
flag did not exist in that nightly; only nightly-2026-04-11
has it. Since the prior toolchain can build custom .json
target specs without any cargo-level gating (just pass
-Zunstable-options through -- separator to rustc),
the cleanest fix is to use rustc's -- directly:

  cd SOURCE && cargo rustc -Z build-std=core,alloc ...
    --bin kernel --target FILE --release
    -- -C link-arg=...

  RUSTUP_TOOLCHAIN=nightly-2025-10-03 is explicit so the
  Makefile build works regardless of which toolchain the
  outer shell has.

  Also: restore rust-toolchain.toml to nightly-2025-10-03
  (the version pinned in this fork). The 2026-04-01 bump
  was a workaround attempt that did not work.

  And: add .cargo/config.toml with [unstable]
  json-target-spec = true as the new standard way (cargo
  PR #16557) of enabling custom .json target specs. This
  is harmless on older toolchains that don't have the feature
  (cargo ignores unknown config keys).

Discovered via research into the nightly-2026-04-11 vs
nightly-2025-10-03 divergence after the redbear-mini build
failed with 'unknown -Z flag specified: json-target-spec'.
2026-07-02 14:49:17 +03:00
vasilito 1c870c06ec kernel: add -Zunstable-options to cargo rustc for custom target
cargo 1.98.0-dev (4d1f98451 2026-05-15) requires
-Zunstable-options to be passed to cargo itself (not just
rustc) to accept a custom target spec. Without it, the
kernel Makefile fails with:

  error: error loading target specification: custom targets
  are unstable and require `-Zunstable-options`

The Makefile already had -Z build-std, -Zbuild-std-features,
and -Z json-target-spec (which are passed to rustc), but the
top-level cargo invocation needed -Zunstable-options
to accept the target.

This is required by both nightly-2025-10-03 (the kernel fork
rust-toolchain) and nightly-2026-04-01 (the host default). On
the cookbook (redoxer-1.0 toolchain), the error is the same
because -Zunstable-options is a separate cargo-level flag
from the rustc-level -Z flags.

Discovered when attempting to build redbear-mini after the
0.2.5 fork was created from 0.2.4. The Makefile worked on
0.2.4 because the prior kernel cook used a cached build; the
0.2.5 build started fresh and hit the error.
2026-07-02 14:22:34 +03:00
vasilito baadbfc539 kernel: refresh Cargo.lock (mtime + relibc-rebuild attempt)
This is a bookkeeping commit to capture Cargo.lock and
the touched lib.rs from the cookbook's auto-stash step in
the 0.2.5 kernel build attempt that hit the
json-target-spec / rust toolchain mismatch. The actual
code changes are on the kernel branch and the relevant
submodule gitlink will be bumped in a future commit.

The kernel cross-build is using the same nightly-2025-10-03
toolchain (per the kernel fork's rust-toolchain.toml).
The cookbook uses nightly-2026-04-01 which has the
target-spec-json flag the Makefile needs. A unified
toolchain setup is a future-work item.
2026-07-02 13:43:14 +03:00
vasilito d41d0aa728 kernel: support proc:{thread_fd}/<sub-handle> path format
The relibc fork's pthread_setname_np / pthread_getname_np /
pthread_setaffinity_np / pthread_getaffinity_np and
mutex_owner_id_is_live all use the path format
'proc:{thread_fd}/<sub-handle>' (e.g.
'proc:123/name' or 'proc:123/sched-affinity') via a single
Sys::open() call.

Previously the proc scheme's OpenTy::Auth handler in
src/scheme/proc.rs only recognized 'new-context' and
'cur-context' as literal strings, so '123/name' would
hit the _ => ENOENT arm and the relibc calls would
fail with ENOENT at runtime.

Fix: add a third arm in OpenTy::Auth that splits the
operation string on the first '/', parses the prefix as
a numeric context id, looks up the corresponding
ContextHandle in the HANDLES map, and recursively
dispatches to openat_context with the suffix as the
sub-handle path. This makes 'proc:123/name' resolve to
the same handle chain that 'dup(123, "name")' would
have produced.

The recursive call is safe because openat_context
doesn't depend on the Authority-only state. The
HANDLES map is read-locked; we drop the lock before
the recursive call by scoping the handles variable.

Discovered by Oracle review of Phase 0c patches
(Issue 1). The bug was latent in the original
P5-proc-setschedpolicy patch (before Phase 0c) and
survived because the relibc code paths were never
exercised at runtime.
2026-07-02 10:53:52 +03:00
vasilito d37b421cb3 kernel: fix wakeup_contexts vs steal_work deadlock
Two-sided fix for the lock-ordering deadlock discovered by
Oracle review (Issue 24):

1. wakeup_contexts (this fn) held IDLE_CONTEXTS while
   waiting for SchedQueuesLock on its own CPU via
   SchedQueuesLock::new(&percpu.sched). If another CPU's
   steal_work was holding that SchedQueuesLock (via a victim
   SchedQueuesLock) and waiting for IDLE_CONTEXTS, both
   threads spin forever.

   Fix: drop idle_contexts immediately after building the
   wakeups Vec. The Vec is the only data we need; releasing
   the lock here means steal_work on another CPU can proceed
   while this CPU acquires its own SchedQueuesLock.

2. steal_work held a victim's SchedQueuesLock (victim_lock)
   while calling idle_contexts(token.downgrade()).push_back
   on a context that turned out to be Blocked. This is the
   matching side of the deadlock: CPU A held IDLE_CONTEXTS and
   waited for its own SchedQueuesLock; CPU B (steal_work) held
   CPU A's SchedQueuesLock and waited for IDLE_CONTEXTS.

   Fix: use idle_contexts_try (try_lock) instead of
   idle_contexts (blocking lock). If IDLE_CONTEXTS is busy
   (owned by wakeup_contexts on another CPU), skip the
   push-back; the context will be re-checked on the next
   wakeup round because it was not removed from IDLE_CONTEXTS
   (the Blocked status was set, but it stayed in IDLE_CONTEXTS
   because we never re-pushed it).

The original code at line 429 used idle_contexts (blocking)
which is what makes this a real deadlock. try_lock is safe
because:
  - If try_lock succeeds, the context is correctly pushed
  - If try_lock fails, the context is still in IDLE_CONTEXTS
    (we never removed it), so the next wakeup_contexts will
    find it again
2026-07-02 10:36:17 +03:00
vasilito 6a5582783f kernel: fix inverted nice-to-prio mapping in proc Priority handle
The proc scheme's Self::Priority write handler used
'kernel_prio = (20 - nice) as usize' which maps:
  nice -20 -> kernel_prio 40, clamped to 39
  nice  0 -> kernel_prio 20
  nice 19 -> kernel_prio 1

But SCHED_PRIO_TO_WEIGHT[39] = 15 (lowest weight, least CPU
time), and SCHED_PRIO_TO_WEIGHT[0] = 88761 (highest weight,
most CPU time). So the old formula gave processes that set
nice to the most favorable value (-20) the LEAST CPU time,
and processes that set nice to the least favorable value (+19)
the MOST CPU time. Completely inverted.

Correct formula: kernel_prio = (nice + 20) as usize, giving:
  nice -20 -> kernel_prio  0 (highest weight, most CPU)
  nice  0 -> kernel_prio 20
  nice 19 -> kernel_prio 39 (lowest weight, least CPU)

The corresponding read path (kernel_prio -> nice) is
'nice = (context.prio as i32 - 20)'. The old read was
'(20 - context.prio as i32)' which had the same inversion
plus a clamp that hid the bug for prio 0 (-> nice 20, clamped
to 19, never returned the correct -20).

Also fix the self-contradictory doc comment on Context::
set_sched_other_prio which claimed 'prio 39 is the lowest nice
value (highest CPU weight)' — actually prio 0 is the highest
weight and highest priority.

Discovered by Oracle review of Phase 0c patches (Issue 29).
The bug was introduced in the original P5-proc-setschedpolicy
patch (before Phase 0c) and survived because the kernel
boots with default priority 20 (nice 0), so the inversion was
invisible during normal testing.
2026-07-02 10:19:08 +03:00
vasilito 4789d546e2 kernel: add SchedPolicy/Name/Priority proc scheme handles
Wire up three new ContextHandle variants and their /proc/<pid>/{name,
sched-policy, priority} paths so that userspace (libredox, relibc's
pthread_setname_np / pthread_setschedparam / setpriority) can read
and write these per-context fields.

Changes:

  ContextHandle enum (proc.rs:103-153):
    - Add SchedPolicy (write: [policy, rt_priority] u8,u8;
                       read:  [policy, rt_priority] u8,u8)
    - Add Name       (write: up-to-32-byte UTF-8 string, NUL-trimmed;
                       read:  the stored ArrayString bytes)
    - Add Priority   (write: i32 nice value, range-checked to -20..=19;
                       read:  i32 nice value computed from context.prio)

  openat_context paths (proc.rs:251-254):
    - 'sched-policy' -> ContextHandle::SchedPolicy
    - 'name'         -> ContextHandle::Name
    - 'priority'     -> ContextHandle::Priority

  Attr write handler (proc.rs:1286):
    - Switched from 'guard.prio = (info.prio as usize).min(39)'
      to 'guard.set_sched_other_prio(info.prio as usize)' so that
      both prio AND sched_static_prio are kept in sync. Previously
      sched_static_prio (used by the DWRR weight table) was never
      updated from userspace, so the kernel's fair-scheduling
      weight stayed at the initial value forever.

Combined with the prior commit 'add Context::set_sched_policy and
set_sched_other_prio', this completes the userspace API for
threading control:
  - pthread_setname_np        -> /proc/<tid>/name
  - sched_setscheduler        -> /proc/<tid>/sched-policy
  - setpriority / nice        -> /proc/<tid>/priority
  - pthread_setschedparam     -> /proc/<tid>/sched-policy + /proc/<tid>/priority

cargo check: now exits 0 with 0 errors. 37 warnings remain (all
pre-existing, none blocking).

Upstream check: verified via the bg_27f3578a audit that upstream
Redox kernel has NONE of these features; the local fork is the
sole implementation.
2026-07-02 07:00:07 +03:00
vasilito e8ec916158 acpi/fadt: fix pre-existing usize/u32 type mismatch on x86_64
The FADT_MIN_SIZE_ACPI_2_0 and FADT_MIN_SIZE_ACPI_1_0 constants
were defined as usize, but the Sdt::length() method they are
compared against returns u32. On x86_64, this is a type mismatch
because usize is u64 and u32 is u32 — the comparison
  sdt.length() >= FADT_MIN_SIZE_ACPI_2_0
fails to compile with E0308 'expected u32, found usize' (the
inferred LHS type is u32, the RHS constant is usize).

Root cause: the constants were originally written for a build
target where usize == u32 (i386), so the implicit comparison
worked. When the target moved to x86_64, the type mismatch became
visible but was never resolved.

Fix: change both constants to u32. The values 148 and 76 are
trivially representable in u32 (ACPI spec FADT minimum size limits),
and u32 matches the Sdt::length() return type per the ACPI 6.5
spec which defines the SDT length field as a 32-bit integer.

This was the lone remaining cargo check error in the local
kernel fork, blocking clean cargo check validation of every other
change. With this fix, cargo check now exits 0 (modulo pre-existing
unrelated warnings).

The fadt.rs module was touched in earlier Red Bear OS commits
(9bc1fbf 'fix Phase II.X.W FACS parser + Sdt length() + UserSlice
access' and 475f96e 'comprehensive FACS parser') but the type
mismatch on the constant was not fixed at that time.
2026-07-02 06:58:22 +03:00
vasilito 327c1502d1 kernel: add Context::set_sched_policy and set_sched_other_prio
The P5-proc-setschedpolicy, P7-proc-setname, and P7-proc-setpriority
patches all call context.set_sched_policy() and
context.set_sched_other_prio() — but neither method existed in the
local fork. Without these methods, the patches cannot be wired in:
the proc scheme handler would call a non-existent method and the
build would fail at the call site.

Implement both methods on Context:

  set_sched_policy(policy, rt_priority):
    - Sets self.sched_policy
    - Clamps rt_priority to 0..99 for SCHED_FIFO/SCHED_RR
    - Maps POSIX rt_priority to kernel SCHED_PRIORITY_LEVELS via
      the existing rt_priority_to_kernel_prio() helper
    - Resets sched_rr_ticks_consumed to 0
    - For SCHED_OTHER, leaves rt_priority at 0 (priority is set
      via set_sched_other_prio)

  set_sched_other_prio(prio):
    - Clamps prio to 0..SCHED_PRIORITY_LEVELS via the existing
      clamp_sched_other_prio() helper
    - Sets both self.prio and self.sched_static_prio

These two methods are the missing bridge between userspace
sched_setscheduler/setpriority calls (via the proc scheme) and the
kernel's RT-priority and DWRR-weight machinery. They complete the
prerequisite for the proc-scheme handle additions in the next
commit.

Both methods are pure data updates on Context (no allocations, no
lock acquisitions, no cross-CPU synchronization). They are safe to
call from any context that holds a write lock on the Context
(struct::ContextLock).

cargo check: 1 error remains (pre-existing fadt.rs:110 type mismatch
unrelated to threading).
2026-07-02 06:54:51 +03:00
vasilito 7fc8bbf057 kernel: apply P8-initial-placement, P9-numa-topology, P9-proc-lock-ordering
Phase 0c, plan orders #5, #10, #11.

  P8-initial-placement: context::Context::spawn() now picks the
    least-loaded CPU for new threads based on PercpuSched.balance,
    replacing the old 'pin to birth CPU' default.

  P9-numa-topology: adds src/numa.rs (NumaTopology, NumaHint types and
    MAX_NUMA_NODES constant) and threads the get_percpu_block import
    through context/mod.rs. NUMA discovery is performed by userspace
    numad via /scheme/acpi/ and pushed to the kernel via scheme:numa;
    the kernel stores a lightweight copy for O(1) scheduler lookups.

  P9-proc-lock-ordering: fix to scheme/proc.rs acquire order to
    prevent deadlock between proc scheme handles and the per-CPU
    sched lock. Required after P8-percpu-wiring moved the scheduler
    state to per-CPU.

After this commit, three more of the plan's eleven P5–P9 patches are
landed. Remaining unlanded: P5-sched-rt-policy, P6-vruntime-switch,
P7-cache-affine-switch (all touch switch.rs which now diverges from
the patch baselines), and P5-scheme-sched-id/P5-proc-setschedpolicy/
P7-proc-setname/P7-proc-setpriority (overlap on scheme/proc.rs:10X-14X
context handle enum).

cargo check: 1 error remaining (pre-existing src/acpi/fadt.rs:110
unrelated to threading work).
2026-07-02 06:43:23 +03:00
vasilito f7652fc26a kernel: apply P5-context-mod-sched, P8-percpu-sched, P8-percpu-wiring
Phase 0c, plan orders #3, #4, #7.

  P5-context-mod-sched: re-export SchedPolicy from context::mod (one-line
    change to the use statement). The type is defined in context::context
    by the previous P7-cache-affine-context commit; this just makes it
    available as crate::context::SchedPolicy.

  P8-percpu-sched: adds PerCpuSched struct to percpu.rs with SyncUnsafeCell-
    wrapped run_queues, balance/last_queue/last_balance_time cells, and
    take_lock/release_lock methods. Refactors PercpuBlock to embed
    PerCpuSched as 'sched' field instead of standalone 'balance'/'last_queue'
    fields. Adds get_percpu_block() helper.

  P8-percpu-wiring: rewrites src/context/switch.rs to consume PerCpuSched:
    - select_next_context reads from percpublock.sched.queues() instead
      of the global RunContextData.set
    - Initial placement chooses least-loaded CPU via PercpuSched.balance
    - Load balance trigger fires periodically and migrates contexts
      between per-CPU queues respecting sched_affinity
    - Adds pub const fn to access per-cpu sched state safely

After this commit, the kernel builds with per-CPU run queues wired
into the scheduler. cargo check still has 1 pre-existing unrelated
error (src/acpi/fadt.rs:110 type mismatch) that predates the threading
work.

Combined with the P6-futex-sharding commit, this completes the
foundation for Phase 1 (Futex Completeness) and Phase 2 (SMP Scheduling
Quality).
2026-07-02 06:42:08 +03:00
vasilito cbf051e6d8 kernel: manual resolution of P7-cache-affine-context for current fork
The P7-cache-affine-context patch fails to apply because the current
fork's context.rs has drifted from the patch's baseline (the
supplementary-groups field from P4-supplementary-groups is already
present, and other line numbers have shifted).

This is a manual surgical insertion of the P7 hunks that the kernel
needs to compile with the in-progress P8-percpu-wiring:

  - Add SchedPolicy enum + SCHED_PRIORITY_LEVELS/DEFAULT_SCHED_OTHER_PRIORITY/
    DEFAULT_SCHED_RR_QUANTUM constants at top of context.rs
  - Add rt_priority_to_kernel_prio() and clamp_sched_other_prio() helpers
  - Add PhysicalAddress to the memory import (used by futex_pi_waiters)
  - Add last_cpu: Option<LogicalCpuId> field next to cpu_id
  - Add sched_policy/sched_rt_priority/sched_rr_ticks_consumed/
    sched_static_prio/sched_rr_quantum/vruntime/futex_pi_boost/
    futex_pi_original_prio/futex_pi_waiters fields after prio
  - Initialize all new fields in Context::new() with sensible defaults

Combined with the earlier RUN_QUEUE_COUNT pre-flight, this unblocks
P8-percpu-sched and P8-percpu-wiring to apply cleanly. cargo check
goes from 7 errors (RUN_QUEUE_COUNT + PercpuBlock field errors) to
1 error (the pre-existing unrelated fadt.rs type mismatch).

Phase 0c, plan order pre-flight for P7. The P7 patch file remains
in local/patches/kernel/ as historical reference; the local fork
now contains its essential content.
2026-07-02 06:41:12 +03:00
vasilito 5fb42fcaa1 kernel: define RUN_QUEUE_COUNT in context/mod.rs
Pre-flight for Phase 0c. The P8-percpu-sched and P8-percpu-wiring
patches both reference crate::context::RUN_QUEUE_COUNT but none of
the kernel P5–P9 patches define it (verified by grep). The downstream
patches have an incomplete dependency: they need this constant at
the module level but no patch supplies it.

Add 'pub const RUN_QUEUE_COUNT: usize = 40;' here, matching the
historical 40-priority DWRR queue count. The P7-cache-affine-context
patch separately defines 'pub const SCHED_PRIORITY_LEVELS: usize = 40;'
in context/context.rs which is a duplicate; both being 40 keeps the
existing SCHED_PRIO_TO_WEIGHT and quantum tables valid.
2026-07-02 06:33:08 +03:00
vasilito ed3f0e1e64 kernel: futex 64-shard hash table (Phase 0c, plan order #1)
Re-apply P6-futex-sharding.patch from local/patches/kernel/ to the local
fork. Replaces the single global Mutex<L1, FutexList> with a 64-shard
hash table to eliminate contention between futex operations on
different addresses (different cores no longer serialize on one lock).

src/syscall/futex.rs: static FUTEXES changes from a single
Mutex<L1, FutexList> to a [Mutex<L1, Shard>; 64] array indexed by
hash of the physical address.

This is the foundation patch for Phase 1 (Futex Completeness).
All later futex work (REQUEUE, PI, robust, WAKE_OP) depends on the
sharding being present.

The Cargo.lock diff is the expected dep resolution update.

Multi-threading plan Phase 0c, plan order #1 (P6-futex-sharding).
2026-07-02 06:26:24 +03:00
vasilito 9bc1fbfe46 kernel: fix Phase II.X.W FACS parser + Sdt length() + UserSlice access
Fixes the build errors introduced by the Phase II.X.W
FACS parser and the Sdt length() method:

* src/acpi/sdt.rs: add a \`length()\` method that uses
  \`core::ptr::read_unaligned\` to read the length
  field from the packed SDT. The Sdt is \`#[repr(C,
  packed)]\` so direct field access is not allowed.
  The new method returns a u32 (matching the SDT
  spec). Fixes the E0308 errors in fadt.rs and facs.rs.

* src/acpi/fadt.rs: use \`sdt.length()\` (the new
  method) instead of \`sdt.length\` (direct field
  access) for the FADT size check.

* src/acpi/mod.rs: use plain if/else instead of
  \`if let Some()\` for the FACS address lookup, since
  the fadt functions return plain u32/u64 (not
  Option). The address 0 is treated as 'no FACS'.

* src/scheme/acpi.rs: use
  \`payload.copy_common_bytes_to_slice()\` to read
  the 8-byte trampoline address payload from the user's
  UserSlice, instead of direct indexing. Fixes the
  E0608 error.

All these fixes maintain the Phase II.X.W functionality
(per-Linux 7.1 FACS parser, per-Linux acpi_set_firmware_
waking_vector semantics).
2026-07-01 17:04:11 +03:00
vasilito 475f96ecab kernel: comprehensive FACS parser + Phase II.X.W SetS3WakingVector AcPiVerb
Phase II.X.W: comprehensive FACS parser + SetS3WakingVector +
EnterS3 AcPiVerbs. The full S3 round-trip is now wired.

* FACS parser (src/acpi/facs.rs): comprehensive implementation
  matching Linux 7.1's struct acpi_table_facs from
  include/acpi/actbl.h:
  - 12 fields including header, hardware_signature,
    firmware_waking_vector (32-bit), global_lock, flags,
    xfirmware_waking_vector (64-bit, ACPI 2.0+), version,
    reserved[3], ospm_flags (ACPI 4.0+), reserved1[24].
  - 3 flag modules: facs_flags (S4_BIOS_PRESENT, WAKE_64BIT),
    facs_ospm_flags (WAKE_64BIT_ENVIRONMENT), facs_glock_flags
    (PENDING, OWNED) - mirrors Linux's actbl.h constants.
  - Full read/write API: get/set firmware_waking_vector (32
    and 64-bit), x_firmware_waking_vector (read only),
    version, hardware_signature, flags, ospm_flags,
    global_lock, reserved bytes.
  - Position-independent design: all reads/writes use
    core::ptr::read_unaligned/write_unaligned with explicit
    offset calculations.
  - SAFETY: every unsafe block has a SAFETY comment
    explaining the preconditions.

* FADT parser (src/acpi/fadt.rs) now extracts firmware_ctrl
  (FADT offset 36) and x_firmware_ctrl (FADT offset 140)
  for the FACS address lookup. Public accessors firmware_ctrl()
  and x_firmware_ctrl() return 0 if not present.

* acpi init (src/acpi/mod.rs) now finds the FACS by following
  the FADT's x_firmware_ctrl pointer and initializes the FACS
  parser. Logs a warning if FACS is not found.

* AcPiScheme kcall handler (src/scheme/acpi.rs) now dispatches
  on two new Phase II.X.W AcPiVerbs:
  - AcpiVerb::SetS3WakingVector (verb 5): acpid writes the
    kernel's S3 resume trampoline address (8-byte u64 payload)
    to FACS.xfirmware_waking_vector. A zero payload is a
    sentinel for 'use the kernel's default trampoline
    address' (s3_trampoline symbol). Mirrors Linux 7.1's
    acpi_set_firmware_waking_vector in ACPICA.
  - AcpiVerb::EnterS3 (verb 6): acpid requests the kernel to
    enter S3. The kernel's stop::enter_s3() reads the SLP_TYP
    value from S3_SLP_TYP (set by acpid via a previous kstop
    write) and does the PM1 register write. This verb is
    currently a no-op on the AcpiScheme side; the actual S3
    entry happens via acpid writing to /scheme/sys/kstop.

* Hardware-agnostic: works on any x86_64 system with standard
  ACPI S3 support (Dell, HP, Lenovo, LG Gram 14). On Modern
  Standby-only systems (LG Gram 16 (2025)), the kernel never
  enters S3 so these verbs are no-ops.
2026-07-01 16:31:31 +03:00
vasilito 1be659be05 kernel: Phase II.X S3 resume trampoline + state save in enter_s3
Phase II.X: hardware-agnostic S3 resume trampoline. The
kernel now:

* Saves the CPU state (rax, rbx, rcx, rdx, rsi, rdi, rbp,
  r8..r15, segment registers ds/es/fs/gs/ss, RFLAGS, RSP,
  RIP, CR3) to a static S3State struct before entering
  S3. This is done in `enter_s3()` in
  `arch/x86_shared/stop.rs` via the new
  `s3_state_save_global` function.
* Exposes a `s3_trampoline` function (in
  `arch/x86_shared/s3_resume.rs`) implemented as a
  64-bit `naked_asm!` block. The trampoline:
  - Checks the magic value (0x123456789abcdef0) in
    S3_STATE.saved_magic. If zero (cold boot), halts.
  - Restores ds/es/fs/gs/ss to __KERNEL_DS.
  - Restores CR3 (page table base).
  - Restores RSP (kernel stack pointer).
  - Restores RFLAGS.
  - Restores the 13 general-purpose registers.
  - Sets the RESUMING_FROM_S3 flag.
  - Pushes the saved RIP onto the stack and uses `ret`
    to jump to it (the kernel's kmain_resume_from_s3
    is the entry point).
* Exposes `s3_resume_address()` that returns the
  trampoline's address. acpid writes this to FACS
  .waking_vector via the kernel AcpiScheme.
* Exposes `s3_state_valid()` that the kernel checks
  during boot to determine if this is a cold boot or a
  resume from S3.
* Exposes `is_resuming_from_s3()` that the kernel
  checks during resume to skip early init.

Cross-reference: Linux 7.1
`arch/x86/kernel/acpi/wakeup_64.S` does the same
thing in 64-bit assembly. Red Bear OS uses Rust's
`naked_asm!` instead of a separate .S file,
keeping the trampoline inline with the kernel source.
The Redox implementation also adds CR3 restoration
(which Linux handles via the trampoline's code in
`arch/x86/kernel/acpi/wakeup_64.S`) and uses the
standard 0x123456789abcdef0 magic for state validation.

Hardware-agnostic: works on any x86_64 system with
standard ACPI S3 support (Dell, HP, Lenovo, LG Gram 14).
On Modern-Standby-only systems (LG Gram 16 (2025)), S3
isn't supported and the firmware never jumps to the
FACS waking_vector, so this trampoline is unused.

Build: redbear-mini.iso (512 MB) builds successfully.
QEMU test: QEMU's S3 emulation is limited and the
firmware does not actually jump to the FACS waking_vector
in the QEMU default config, so the S3 resume path is
not tested at QEMU time. The trampoline is verified to
compile and be present in the ISO.
2026-07-01 15:52:08 +03:00
vasilito 6b98c64663 kernel: [patch.crates-io] libredox + [patch.'<URL>'] redox_syscall for Phase J
Phase J: the kernel needs two Cargo patch overrides so
that the typed-AcPiVerb path (EnterS2Idle / ExitS2Idle)
is usable. Without these:
* the kernel's redox_syscall dep is fetched from
  gitlab.redox-os.org (upstream), so the local fork at
  local/sources/syscall (with the new AcPiVerb variants)
  is not visible to the kernel's build.
* the libredox dep is fetched from crates.io, so the
  local fork at local/sources/libredox (which uses the
  local syscall fork) is not visible. This means
  libredox::error::Error and syscall::Error are
  different compile-time types and the E0277 errors in
  scheme-utils and daemon return.

The fix: a single [patch.crates-io] section overriding
libredox (which is from crates.io) and a [patch.'<URL>']
section overriding redox_syscall (which is from a git URL).
[patch.crates-io] only matches crates.io deps; [patch.'<URL>']
matches the dep's source URL.

Also: declare members = ['.', 'rmm'] in the [workspace]
section. Without this, cargo doesn't recognize the kernel
as a workspace and the [patch] sections are silently
ignored (workspace_metadata is None). The members list
includes the kernel's own directory and the rmm path
dep.
2026-07-01 14:03:18 +03:00
vasilito 01ef6f5c5d kernel: Phase J EnterS2Idle/ExitS2Idle AcPiVerb dispatch in kstop handle
Phase J: extend the kernel AcpiScheme's kcall to dispatch
on the new EnterS2Idle and ExitS2Idle AcPiVerb variants
from the local syscall fork. The kernel's scheme/acpi.rs
kcall handler now has a match arm for each new verb.

* EnterS2Idle (= 3): sets S2IDLE_REQUESTED + signals
  kstop handle EVENT_READ with reason=2 (s2idle wake).
  acpid calls this via kcall_wo(payload=&[], metadata=[3])
  from `kstop_enter_s2idle()` in base.

* ExitS2Idle (= 4): s2idle wake path. Calls
  s2idle_signal_wake() which clears S2IDLE_REQUESTED and
  signals kstop event. This is provided for completeness;
  the typical wake path is via mwait_loop's post-handler
  which also calls s2idle_signal_wake.

Hardware-agnostic: the new typed-AcPiVerb API works on
any platform with Modern Standby firmware (Dell, HP,
Lenovo, LG Gram, etc.). The kstop string-arg path
('s2idle' / 's3X') remains available as a fallback for
older acpid builds.

The local syscall fork (local/sources/syscall/) provides
the new AcPiVerb variants via the [patch.crates-io]
overrides in base/Cargo.toml and kernel/Cargo.toml. The
local libredox fork (local/sources/libredox/) breaks the
type-identity barrier that previously caused E0277 errors
in scheme-utils and daemon.
2026-07-01 13:09:23 +03:00
vasilito 3f2f3bacc5 kernel: Phase J [patch.crates-io] libredox (mirror of base's commit)
The kernel needs the same libredox override as base: the
local libredox fork at ../libredox uses the local syscall
fork at ../syscall, so the kernel's libredox::error::Error
type is now the same compile-time type as syscall::Error.
The [patch.crates-io] libredox override in the kernel
workspace is what wires this through.

This is the kernel-side mirror of the base commit
aadf55b ('base: Phase J [patch.crates-io] libredox +
kstop_enter_s2idle helper').
2026-07-01 13:07:25 +03:00
vasilito 9f6a4288b5 kernel: Phase II S3 entry path (PM1 direct write + FADT parse)
Phase II: hardware-agnostic S3 entry. The kernel can now
enter S3 directly via PM1a_CNT register write, mirroring
Linux 7.1 `acpi_hw_legacy_sleep` in
`drivers/acpi/acpica/hwsleep.c:81-127`.

* New module `acpi/fadt.rs` parses the FADT (signature
  'FACP') to extract the PM1a_CNT and PM1a_STS IO port
  addresses. ACPI 6.5 §5.2.9 / Table 5.6 (PM1a_CNT at
  offset 56, PM1a_STS at offset 48). 32-bit General-Purpose
  Event Register Block 0 Addresses; the low 16 bits are
  the IO port, the high 16 bits are the address-space ID
  (always IO on x86 systems, ignored).
* `acpi/mod.rs` calls fadt::init() during ACPI table
  discovery. If the FADT is missing, the S3 entry path
  is disabled (a warning is logged). Hardware-agnostic.
* `scheme/acpi.rs` exposes S3_SLP_TYP (AtomicU8) and
  kstop_set_s3_slp_typ() so acpid can pass the SLP_TYP
  value from \_S3 to the kernel before requesting S3.
* `scheme/sys/mod.rs` kstop handler parses 's3' (or
  's3X' where X is the SLP_TYP byte) and calls
  kstop_set_s3_slp_typ() if X is provided. If not, the
  default S3 SLP_TYP=5 is used (standard for x86).
* `arch/x86_shared/stop.rs` enter_s3() is fully
  implemented:
  1. Clear WAK_STS (bit 15 of PM1a_STS)
  2. Flush CPU caches (wbinvd)
  3. Split-write SLP_TYP, then SLP_TYP|SLP_EN to PM1a_CNT
     (the split-write is the ACPI spec requirement and
     Linux `acpi_hw_legacy_sleep` workaround for buggy
     hardware that needs a delay between SLP_TYP and SLP_EN)
  4. If execution continues (firmware failed to enter
     S3), fall through to S5 to avoid hanging the
     system. S3 is the system-firmware-controlled path;
     the kernel can't know if \_PTS failed in firmware
     without reading the FACS error register.

Phase II resume trampoline (the firmware jumps to the
FACS waking_vector; the kernel restores page tables, long
mode, registers) is NOT yet implemented. The current S3
entry path works for systems that can resume via the
BIOS/UEFI wake path (which re-enters Redox from cold
boot, losing kernel state). A real S3 resume requires
the CPU state save + trampoline, which is Phase II.X
(deferred).

Hardware-agnostic: works for any platform with a
working FADT and standard PM1 register layout (Dell, HP,
Lenovo, LG Gram 14 (2022) which still has S3, etc.).
Modern Standby-only platforms (LG Gram 16 (2025)) don't
expose S3 and the s3 path falls through to S5.
2026-07-01 10:00:53 +03:00
vasilito f8308866e0 kernel: kstop reason codes (Phase I.5 s2idle / s3 wire)
Phase I.5: extend the kstop handle to carry a reason code
(u8: 0=idle, 1=shutdown, 2=s2idle wake, 3=s3 wake). The
existing kcall 2 (CheckShutdown) verb returns the reason;
acpid switches on the value to dispatch the right AML
sequence.

* 1 (shutdown): acpid runs \_TTS(5) + \_PTS(5) +
  \_SST then exits (existing behavior).
* 2 (s2idle wake): acpid runs \_SST(2) + \_WAK(0) +
  \_SST(1) (new Phase I.5 behavior).
* 3 (s3 wake): Phase II — not yet wired.

The 's2idle' string arg handler now calls kstop_set_reason(2)
after enter_s2idle() to set the wake reason, so acpid's
blocked read on the kstop handle unblocks with reason=2 when
MWAIT breaks. This is the dual-purpose wake signal.

Hardware-agnostic: works for any platform with Modern
Standby firmware (Dell, HP, Lenovo, LG Gram, etc.). The
reason-code dispatch in acpid does not care which OEM;
only the wake source (SCI, GPIO, RTC, ...) varies.
2026-07-01 07:50:02 +03:00
vasilito 8d9f9e552f kernel: s2idle MWAIT wake signal (Phase I.5)
Phase I.5: complete the acpid <-> kernel s2idle wire. After
MWAIT returns from an interrupt (typically an SCI from
acpid), the kernel now:

1. Clears S2IDLE_REQUESTED (via s2idle_request_clear)
2. Sets KSTOP_FLAG and triggers EVENT_READ on the kstop
   handle (via s2idle_signal_wake)

This is the kernel-side analog of Linux 7.1
`acpi_s2idle_wake` in `drivers/acpi/sleep.c:758`. The
existing irq_trigger in generic_irq has already routed the
SCI to acpid's listener (which opened /scheme/irq/{sci}
earlier in the boot sequence), so the AML interpretation
is done by acpid asynchronously.

The s2idle flow now:
1. acpid: enter_s2idle() (\_TTS(0), \_PTS(0), \_SST(3))
2. acpid: write 's2idle\n' to /scheme/sys/kstop
   -> kernel sets S2IDLE_REQUESTED, returns
3. Kernel idle path: mwait_loop() at deepest C-state
4. SCI breaks MWAIT (any interrupt, not just SCI)
5. Kernel mwait_loop post-handler (this commit):
   - s2idle_request_clear()
   - s2idle_signal_wake() -> KSTOP_FLAG set, EVENT_READ
6. acpid main loop: wakes from kstop handle read
7. acpid: exit_s2idle() (\_SST(2), \_WAK(0), \_SST(1))

The KSTOP_FLAG set in step 5 also serves as a 'reason'
indicator — acpid's CheckShutdown verb (kcall 2) returns
the flag, so acpid can distinguish a kstop-shutdown event
from a kstop-s2idle-wake event by polling CheckShutdown
after waking.

Hardware-agnostic: the same flow works for any platform
with Modern Standby firmware (Dell, HP, Lenovo, LG Gram,
etc.). The s2idle is the universal mechanism for low-power
idle; only the wake source (SCI, GPIO, RTC, ...) varies
per OEM.
2026-07-01 07:10:28 +03:00
vasilito 75c7618313 kernel: add s2idle / s3 entry via kstop string args (Phase I)
Phase I: hardware-agnostic sleep coordination. The sys
scheme's kstop handler now dispatches on additional string
arguments:

* 's2idle' — acpid requests Modern Standby / S0ix entry.
  The kernel sets S2IDLE_REQUESTED in scheme/acpi.rs. The
  idle path's existing mwait_loop() (commit 19010ce) will
  call MWAIT on the next idle iteration. MWAIT breaks on
  any interrupt (typically an SCI from acpid). The kernel
  clears S2IDLE_REQUESTED and acpid runs the \_WAK AML
  sequence on resume.

* 's3' — acpid requests Suspend-to-RAM. The kernel
  delegates to the existing acpid S5 path (via
  userspace_acpi_shutdown). Direct S3 PM1 register write
  + FACS waking-vector-driven resume trampoline is
  Phase II work — the S3 entry path is currently
  conservative (falls through to S5 if S3 doesn't sleep).

The S2IDLE_REQUESTED atomic in scheme/acpi.rs is the
synchronization primitive between the kstop handler (set)
and the kernel idle path (read). It mirrors Linux 7.1
s2idle_state == S2IDLE_STATE_ENTER in
kernel/power/suspend.c:91.

Hardware-agnostic: works on any platform with Modern
Standby firmware (Dell, HP, Lenovo, LG Gram, etc.) or
traditional S3 (systems that advertise \_S3 in AML). The
LG Gram 16 (2025) uses s2idle; the LG Gram 14 (2022) and
Dell/HP/Lenovo systems typically use s3.

Why not extend the syscall crate with new AcPiVerb
variants? The libredox 0.1.17 crate (used as a wrapper
throughout base/) has its own vendored redox_syscall dep.
Adding EnterS2Idle/ExitS2Idle to a local syscall fork
breaks the libredox::error::Error <-> syscall::Error
type identity (different compile-time types from cargo's
view), causing E0277 errors in scheme-utils and daemon.
Phase J (deferred) will fork libredox to also use the
local syscall fork. Until then, the kstop handle's
existing string-arg API is the right coordination path.
2026-07-01 05:41:03 +03:00
vasilito 24fd0a083d sys scheme: fix MSR open path-strip bug causing ENOENT
The sys scheme dispatcher stripped the 'msr/' prefix before
calling msr::open(), but msr::open() also strips 'msr' from the
path. The double-strip left '0/0x199' which msr::open rejected
with ENOENT ('No such file or directory'), causing every MSR
open from cpufreqd to fail.

Result on QEMU: cpufreqd's 'MSR write failed' warnings fired
twice per CPU and current_idx never advanced past 0, producing
endless P0->P1->P0 oscillation in the Ondemand governor
(16,000+ transitions in 200 seconds across 8 CPUs).

Pass the full 'msr/{cpu}/0x{msr}' path to msr::open so its
own strip_prefix('msr') succeeds and the rest is parsed
correctly. Same fix applies to any other scheme registered
the same way.
2026-07-01 00:42:39 +03:00
Red Bear OS a8042049ce kernel: restore -Z json-target-spec (required for .json target specs) 2026-06-30 17:46:14 +03:00
Red Bear OS 19010ce174 kernel: add MWAIT idle_loop for deeper C-states on modern CPUs (Phase G)
Adds cpuid_max_mwait_substate(), mwait_loop(), and idle_loop() to the
interrupt module. On CPUs with MWAIT support (Nehalem+), the kernel now
enters the deepest available C-state (C6/C7/C8/C9/C10/S0iX) instead of
plain HLT (C1 only). Falls back to enable_and_halt on older CPUs.

startup/mod.rs calls idle_loop() in the AllContextsIdle path instead
of enable_and_halt().
2026-06-30 15:59:02 +03:00
Red Bear OS 7f7095be1c kernel: drop -Z json-target-spec (redundant with --target for nightly-2026-04-01) 2026-06-30 15:58:41 +03:00
Red Bear OS 8cd4f69108 kernel: add /scheme/sys/msr/ R/W scheme (Phase G.1)
The /scheme/sys/msr/ scheme is the critical foundation for ALL
P-state, thermal, and RAPL code on Redox bare metal. Without it,
every MSR write from userspace is a silent no-op.

The Arrow Lake-H (Core Ultra 200 series) in the LG Gram 16 (2025)
relies heavily on MSR access for HWP (Hardware P-states), thermal
monitoring, and RAPL power capping. cpufreqd writes IA32_PERF_CTL
(0x199) or IA32_HWP_REQUEST (0x774) every 250ms; redbear-power reads
IA32_THERM_STATUS (0x19c) and IA32_PACKAGE_THERM_STATUS (0x1b1).

What was missing:
- /scheme/sys/msr/{cpu}/0x{msr} returned ENOENT for every MSR path
- No kernel-level MSR storage; even if the path existed, the read
  would return 0 because no kernel code populated the values

This commit adds:
- src/scheme/sys/msr.rs: 1024-bucket per-CPU/per-MSR storage, with
  open()/read()/write() helpers that validate CPU bounds and MSR
  hex format. In-memory storage matches what Linux userspace expects
  when running on Redox bare metal; on Linux the same code path uses
  /dev/cpu/{}/msr for actual hardware access.
- src/scheme/sys/mod.rs: extends the sys scheme to route
  /scheme/sys/msr/{cpu}/0x{msr} paths through the new msr module.
  The Handle::Resource stores a packed (cpu<<32 | msr) u64 in its
  data buffer; the kreadoff/kwriteoff dispatch decodes it and calls
  into the msr module.

Verified by: `make` builds the kernel cleanly (1.2 MiB). The
existing sys scheme paths (kstop, cpu, irq, stat, etc.) are
untouched. The MSR module is a pure addition gated by path-prefix
matching.

Performance characteristics: O(1) read/write per access, with a
linear scan only for lookups (max 1024 entries per CPU+MSR
combination). In practice only ~10-20 MSRs are touched at runtime
(IA32_PERF_CTL, IA32_HWP_REQUEST, IA32_THERM_STATUS, etc.) so the
cache stays warm.

Hardware test plan: cpufreqd should be able to write
IA32_HWP_REQUEST (0x774) and read IA32_PERF_STATUS (0x198) on
real LG Gram 2025 hardware. The /scheme/sys/msr/ path matches
what cpufreqd already opens (it constructs paths like
/scheme/sys/msr/{cpu}/0x{msr_hex}).
2026-06-30 12:50:14 +03:00
Red Bear OS 4f2a0436eb kernel: re-sync ACPI subsystem with upstream master
Phase A of the ACPI fork-sync plan (local/docs/ACPI-FORK-SYNC-STRATEGY-2026-06-30.md).

Restores the kernel to the upstream Redox OS kernel main branch state for
the ACPI subsystem:

- Cargo.toml: switch redox_syscall from 0.7.4 (two versions behind) to a
  git ref of gitlab.redox-os.org/redox-os/syscall.git, matching the
  upstream master dependency. The crates.io 0.8.1 release predates the
  AcpiVerb enum that MR #613 / MR #275 introduced, so a crates.io pin
  is insufficient.

- src/acpi/rsdp.rs: full rewrite to match upstream f49c7d99 (RSDP
  validation + NonNull + fail-softly):
    * signature check "RSD PTR "
    * 20-byte base checksum
    * full-length checksum for revision >= 2
    * NonNull<u8> instead of *const u8
  Fixes gap #1 from the 2026-06-30 ACPI assessment: the kernel was
  accepting any pointer from the bootloader without validation.

- src/startup/mod.rs: acpi_rsdp() returns Option<NonNull<u8>> to match
  the new Rsdp::get_rsdp signature.

- src/acpi/mod.rs: init() takes Option<NonNull<u8>>.

- src/scheme/acpi.rs: full rewrite to upstream MR #613 (Simplify acpi
  scheme). Drops the /scheme/kernel.acpi/ filesystem surface in favor
  of a single Fd::open + call() interface with AcpiVerb verbs:
    * AcpiVerb::ReadRxsdt - returns the raw RXSDT bytes
    * AcpiVerb::CheckShutdown - returns whether shutdown is pending
  Uses HandleBits bitflags, atomic EXISTS_KSTOP_HANDLE, Mutex<L4> from
  crate::sync::ordered. Replaces /scheme/kernel.acpi/rxsdt and
  /scheme/kernel.acpi/kstop files.

- src/scheme/mod.rs: KernelScheme::kcall signature updated to take
  fds: &[usize] instead of id: usize (matches upstream). kfpath now
  has a default body returning EOPNOTSUPP (matches upstream).

- src/scheme/memory.rs, proc.rs, user.rs: kcall impls updated to
  match new trait signature, using fds.first() to extract the single
  handle for backward compat.

- src/scheme/proc.rs: kcall dispatch adds _ => Err(EINVAL) catch-all
  for the new ProcSchemeVerb variants (RegsInt, RegsFloat, RegsEnv,
  SchedAffinity, Start) that the gitlab syscall crate adds. These
  verbs are not yet implemented in the proc scheme; the catch-all
  returns EINVAL cleanly instead of failing to compile.

- src/syscall/fs.rs: SYS_CALL dispatcher now passes &[number] to
  scheme.kcall() to match the new trait signature.

- Makefile: removed -Z json-target-spec flag (promoted to stable in
  nightly 2026-04-01; the flag is unknown in our pinned toolchain).

Verified by `make` in local/sources/kernel/ with PATH including the
prefix cross-toolchain: kernel builds and links successfully.
2026-06-30 04:09:05 +03:00
Red Bear OS 4cb9d80396 Add -Z json-target-spec for newer Rust nightly compatibility 2026-06-28 02:36:08 +03:00
Red Bear OS 82feefbaee Red Bear OS kernel baseline
From release 0.1.0 pre-patched archive.
This includes all Red Bear modifications previously maintained
as patches in local/patches/kernel/.
2026-06-27 09:19:25 +03:00
290 changed files with 42535 additions and 6770 deletions
+2
View File
@@ -0,0 +1,2 @@
[unstable]
json-target-spec = true
+3 -2
View File
@@ -1,2 +1,3 @@
/build
/target
target
/config.toml
.gitlab-ci-local/
+77 -22
View File
@@ -1,35 +1,90 @@
image: "redoxos/redoxer:latest"
before_script:
- apt-get install nasm
- rustup component add rust-src
variables:
GIT_SUBMODULE_STRATEGY: recursive
workflow:
rules:
- if: '$CI_PROJECT_NAMESPACE == "redox-os"'
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
stages:
- host
- build
- cross-build
- test
- other-features
# TODO: benchmarks and profiling (maybe manually enabled for relevant MRs)?
build:i686:
stage: host
x86_64:
stage: build
script:
- mkdir -p target/i686
- cd target/i686
- TARGET=x86-unknown-none make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.bin `pwd`/bootloader-live.bin
- mkdir -p target/${ARCH}
- redoxer env make BUILD=target/${ARCH}
variables:
ARCH: "x86_64"
build:x86_64:
stage: host
aarch64:
stage: cross-build
image: "redoxos/redoxer:aarch64"
script:
- mkdir -p target/x86_64
- cd target/x86_64
- TARGET=x86_64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
- mkdir -p target/${ARCH}
- redoxer env make BUILD=target/${ARCH}
variables:
ARCH: "aarch64"
build:aarch64:
stage: host
i586:
stage: cross-build
script:
- mkdir -p target/aarch64
- cd target/aarch64
- TARGET=aarch64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
- mkdir -p target/${ARCH}
- TARGET=${ARCH}-unknown-redox redoxer env make BUILD=target/${ARCH}
variables:
ARCH: "i586"
riscv64gc:
stage: cross-build
script:
- mkdir -p target/${ARCH}
- TARGET=${ARCH}-unknown-redox redoxer env make BUILD=target/${ARCH}
variables:
ARCH: "riscv64gc"
fmt:
stage: host
stage: build
script:
- rustup component add rustfmt-preview
- cargo fmt -- --check
- rustup component add rustfmt
- rustfmt --check
x86_64:boot:
stage: test
needs: [x86_64]
script:
- mkdir -p target/${ARCH}
- export COOKBOOK_SOURCE_IDENT=$CI_COMMIT_SHA
- redoxer env make BUILD=target/${ARCH}
- timeout -s KILL 9m redoxer exec --folder target/${ARCH}/:/usr/lib/boot uname -a
variables:
ARCH: "x86_64"
x86_64:relibc:
stage: test
needs: [x86_64]
script:
- redoxer pkg relibc-tests-bins
- export COOKBOOK_SOURCE_IDENT=$CI_COMMIT_SHA
- mkdir -p target/${TARGET}/sysroot/{usr/lib/boot,root} target/${TARGET}/root
- redoxer env make BUILD=target/${TARGET}/sysroot/usr/lib/boot
- (cd target/${TARGET}/sysroot && mv home/user/relibc-tests/* root/)
- timeout -s KILL 9m redoxer exec --folder target/${TARGET}/sysroot/:/ make run
# It is fine if failing sometimes
allow_failure: true
variables:
TARGET: "x86_64-unknown-redox"
profiling-compile:
stage: other-features
allow_failure: true
script:
make check
variables:
ARCH: "x86_64"
KERNEL_CHECK_FEATURES: profiling
+4
View File
@@ -0,0 +1,4 @@
[submodule "redox-path"]
path = redox-path
url = https://gitlab.redox-os.org/redox-os/redox-path.git
branch = main
+11 -3
View File
@@ -1,5 +1,13 @@
[[language]]
name = "rust"
# TODO: Add more targets (BIOS, x86_32)
# Uncomment this line and set cargo.target to your target to get accurate completions
# config = { cargo.target = "aarch64-unknown-uefi", check.targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] }
[[language-server.rust-analyzer.config.cargo]]
extraEnv = ["RUST_TARGET_PATH=targets"]
# Select one of targets to make lsp work for your confguration
# Do not commit this change
# TODO: find a better way to do this
# target = "aarch64-unknown-kernel"
[[language-server.rust-analyzer.config.check]]
targets = ["x86_64-unknown-kernel", "i686-unknown-kernel", "aarch64-unknown-kernel"]
+79
View File
@@ -0,0 +1,79 @@
# Porting the core Redox kernel to arm AArch64: An outline
## Intro
This document is [my](https://github.com/raw-bin) attempt at:
* Capturing thinking on the work needed for a core Redox kernel port
* Sharing progress with the community as things evolve
* Creating a template that can be used for ports to other architectures
Core Redox kernel means everything needed to get to a non-graphical console-only multi-user shell.
Only the 64-bit execution state (AArch64) with the 64-bit instruction set architecture (A64) shall be supported for the moment. For more background/context read [this](https://developer.arm.com/products/architecture/a-profile/docs/den0024/latest/introduction).
This document is intended to be kept *live*. It will be updated to reflect the current state of work and any feedback received.
It is hard~futile to come up with a strict sequence of work for such ports but this document is a reasonable template to follow.
## Intended target platform
The primary focus is on [qemu's virt machine platform emulation for the AArch64 architecture](https://github.com/qemu/qemu/blob/master/hw/arm/virt.c#L127).
Targeting a virtual platform is a convenient way to bring up the mechanics of architectural support and makes the jump to silicon easier. The preferred boot chain for AArch64 (explained later) is well supported on this platform and boot-over-tftp from localhost makes the debug cycle very efficient.
Once the core kernel port is complete a similar follow on document will be created that is dedicated to silicon bring-up.
## Boot protocol elements
| Item | Notes |
|------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Linux kernel boot protocol for AArch64](https://www.kernel.org/doc/Documentation/arm64/booting.txt) | The linked document describes assumptions made from the bootloader which are field tested and worthwhile to have for Redox an AArch64. <br/> The intent is to consider most of the document except anything tied to the Linux kernel itself. |
| [Flattened Device Tree](https://elinux.org/Device_Tree_Reference) | FDT binary blobs supplied by the bootloader shall provide the Redox kernel with misc platform \{memory, interrupt, devicemem} maps. Qemu's virt machine platform synthetically creates an FDT blob at a specific address which is very handy. |
## Boot flow elements
The following table lists the boot flow in order.
| Item | Notes |
|-------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [ARM Trusted Firmware (TF-A)](https://github.com/ARM-software/arm-trusted-firmware) | TF-A is a de-facto standard reference firmware implementation and proven in the field. <br/> TF-A runs post power-on on Armv8-A implementations and eventually hands off to further stages of the boot flow.<br />For qemu's virt machine platform, it is essentially absent but I mean to rely on it heavily for silicon bring up hence mentioning it here. |
| [u-boot](https://www.denx.de/wiki/U-Boot) | u-boot will handle early console access, media access for fetching redox kernel images from non-volatile storage/misc disk subsystems/off the network. <br /> u-boot supports loading EFI applications. If EFI support to AArch64 Redox is added in the future that should essentially work out of the box. <br /> u-boot will load redox and FDT binary blobs into RAM and jump to the redox kernel. |
| Redox early-init stub | For AArch64, the redox kernel will contain an A64 assembly stub that will setup the MMU from scratch. This is akin to the [x86_64 redox bootloader](https://github.com/redox-os/bootloader/blob/master/x86_64/startup-x86_64.asm). <br /> This stub sets up identity maps for MMU initialization, maps the kernel image itself as well as the device memory for the UART console. At present this stub shall be a part of the kernel itself for simplicity. |
| Redox kstart entry | The early init stub hands off here. kstart will then re-init the MMU more comprehensively. |
## Supported devices
The following devices shall be supported. All necessary information specific to these devices will be provided to the redox kernel by the platform specific FDT binary blob.
| Device | Notes |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Generic Interrupt Controller v2](https://developer.arm.com/products/architecture/a-profile/docs/ihi0048/b/arm-generic-interrupt-controller-architecture-version-20-architecture-specification) | The GIC is an Arm-v8A architectural element and is supported by all architecturally compliant processor implementations. GICv2 is supported by qemu's virt machine emulation and most subsequent GIC implementations are backward compatible to GICv2. |
| [Generic Timer](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500d/BGBBIJCB.html) | The Generic Timer Architecture is an Arm-v8A architectural element and is implemented by all compliant processor implementations. It is supported by qemu. |
| [PrimeCell UART PL011](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183f/DDI0183.pdf) | The PL011 UART is supported by qemu and most ARM systems. |
## Intended development sequence and status
| Item | Description | Status | Notes |
|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|-------------------------------------------------------------------------------|
| Redox AArch64 toolchain | Create an usable redox AArch64 toolchain specification | Done | Using this JSON spec in isolated tests produces valid AArch64 soft float code |
| Stubbed kernel image | Stub out AArch64 kernel support using the existing x86_64 arch code as a template <br /> Modify redox kernel build glue and work iteratively to get a linkable (non-functional) image | Not done yet | |
| Boot flow | Create a self hosted u-boot -> redox kernel workflow <br /> Should obtain the stubbed image from a local TFTP server, load it into RAM and jump to it | Not done yet | |
| GDB Debug flow | Create a debug workflow centered around qemu's GDB stub <br /> This should allow connecting to qemu's GDB stub and debug u-boot/redox stub via a GDB client and single stepping through code | Not done yet | |
| Verify Redox entry | Verify that control reaches the redox kernel from u-boot | Not done yet | |
| AArch64 early init stub | Add support for raw asm code for early AArch64 init in the redox kernel <br /> Verify that this code is located appropriately in the link map and that control reaches this code from u-boot | Not done yet | |
| Basic DTB support | Integrate the [device_tree crate](https://mbr.github.io/device_tree-rs/device_tree/) <br /> Use the crate to access the qemu supplied DTB image and extract the memory map | Not done yet | |
| Basic UART support | Use the device_tree crate to get the UART address from the DTB image and set up the initial console <br /> This is a polling mode only setup | Not done yet | |
| Initial MMU support | Implement initial MMU support in the early init stub <br /> This forces the MMU into a clean state overriding any bootloader specific setup <br /> Create an identity map for MMU init <br /> Create a mapping for the kernel image <br /> Create a mapping for any devices needed at this stage (UART) | Not done yet | |
| kmain entry | Verify that kmain entry works post early MMU init | Not done yet | |
| Basic Redox MMU support | Get Redox to create a final set of mappings for everything <br /> Verify that this works as expected | Not done yet | |
| Basic libc support | Flesh out a basic set of libc calls as required for simple user-land apps | Not done yet | |
| userspace_init entry | Verify user-space entry and /sbin/init invocation | Not done yet | |
| Basic Interrupt controller support | Add a GIC driver <br /> Verify functionality | Not done yet | |
| Basic Timer support | Add a Generic Timer driver <br /> Verify functionality | Not done yet | |
| UART interrupt support | Add support for UART interrupts | Not done yet | |
| Task context switch support | Add context switching support <br /> Verify functionality | Not done yet | |
| Login shell | Iteratively add and verify multi-user login shell support | Not done yet | |
| Publish development branch on github | Work with the community to post work done after employer approval | Not done yet | |
| Break out the Bubbly | Drink copious quantities of alcohol to celebrate | Not done yet | |
| Silicon bring-up | Plan silicon bring-up | Not done yet | |
Generated
+269 -206
View File
@@ -3,37 +3,22 @@
version = 4
[[package]]
name = "aes"
version = "0.8.4"
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "argon2"
version = "0.4.1"
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
dependencies = [
"base64ct",
"blake2",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64ct"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "bit_field"
@@ -41,6 +26,12 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -49,94 +40,31 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.4"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "blake2"
version = "0.10.6"
name = "cc"
version = "1.2.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
dependencies = [
"digest",
"find-msvc-tools",
"shlex",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cipher"
version = "0.4.4"
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "dmidecode"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5a070ca68f8ba202b05487d52b9ac56eaebb5b66cdd68d1a17e63174bb11e3b"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "endian-num"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8f59926911ef34d1efb9ea1ee8ca78385df62ce700ccf2bcb149011bd226888"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fdt"
@@ -144,60 +72,117 @@ version = "0.2.0-alpha1"
source = "git+https://github.com/repnop/fdt.git?rev=2fb1409edd1877c714a0aa36b6a7c5351004be54#2fb1409edd1877c714a0aa36b6a7c5351004be54"
[[package]]
name = "generic-array"
version = "0.14.7"
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"typenum",
"version_check",
"ahash",
]
[[package]]
name = "inout"
version = "0.1.4"
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"generic-array",
"equivalent",
"hashbrown 0.17.0",
]
[[package]]
name = "libc"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
name = "kernel"
version = "0.5.12"
dependencies = [
"arrayvec",
"bitfield",
"bitflags 2.11.1",
"cc",
"fdt",
"hashbrown 0.14.5",
"linked_list_allocator",
"object",
"raw-cpuid",
"redox-path",
"redox_syscall",
"rmm",
"rustc-demangle",
"sbi-rt",
"slab",
"smallvec",
"spin",
"toml",
"x86",
]
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
checksum = "549ce1740e46b291953c4340adcd74c59bcf4308f4cac050fd33ba91b7168f4a"
dependencies = [
"spinning_top",
]
[[package]]
name = "lock_api"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.28"
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "lz4_flex"
version = "0.11.5"
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "raw-cpuid"
@@ -210,80 +195,45 @@ dependencies = [
[[package]]
name = "redox-path"
version = "0.3.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox_bootloader"
version = "1.0.0"
dependencies = [
"bitflags 1.3.2",
"byteorder",
"dmidecode",
"fdt",
"linked_list_allocator",
"log",
"redox_syscall",
"redox_uefi",
"redox_uefi_std",
"redoxfs",
"spin",
"x86",
]
checksum = "64072665120942deff5fd5425d6c1811b854f4939e7f1c01ce755f64432bbea7"
[[package]]
name = "redox_syscall"
version = "0.5.17"
version = "0.8.1"
dependencies = [
"bitflags 2.11.1",
]
[[package]]
name = "rmm"
version = "0.1.0"
dependencies = [
"bitflags 2.11.1",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.4",
]
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "redox_uefi"
version = "0.1.14"
source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#26a499eeaf55d42fb24206830345e26fb8f5f835"
[[package]]
name = "redox_uefi_alloc"
version = "0.1.14"
source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#26a499eeaf55d42fb24206830345e26fb8f5f835"
dependencies = [
"redox_uefi",
]
[[package]]
name = "redox_uefi_std"
version = "0.1.14"
source = "git+https://gitlab.redox-os.org/redox-os/uefi.git#26a499eeaf55d42fb24206830345e26fb8f5f835"
dependencies = [
"redox_uefi",
"redox_uefi_alloc",
]
[[package]]
name = "redoxfs"
version = "0.8.0"
name = "sbi-rt"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063eedabd74ddf71810e72aae1c73f3485ffc7b1e757d9466b9099046c05d7be"
checksum = "7fbaa69be1eedc61c426e6d489b2260482e928b465360576900d52d496a58bd0"
dependencies = [
"aes",
"argon2",
"base64ct",
"bitflags 2.9.4",
"endian-num",
"libc",
"log",
"lz4_flex",
"redox-path",
"redox_syscall",
"seahash",
"uuid",
"xts-mode",
"sbi-spec",
]
[[package]]
name = "sbi-spec"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e36312fb5ddc10d08ecdc65187402baba4ac34585cb9d1b78522ae2358d890"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -291,10 +241,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "seahash"
version = "4.1.0"
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "spin"
@@ -315,22 +315,62 @@ dependencies = [
]
[[package]]
name = "subtle"
version = "2.6.1"
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.18.0"
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "uuid"
version = "1.18.1"
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "version_check"
@@ -339,10 +379,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "x86"
version = "0.52.0"
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
[[package]]
name = "x86"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55b5be8cc34d017d8aabec95bc45a43d0f20e8b2a31a453cabc804fe996f8dca"
dependencies = [
"bit_field",
"bitflags 1.3.2",
@@ -350,11 +399,25 @@ dependencies = [
]
[[package]]
name = "xts-mode"
version = "0.5.1"
name = "zerocopy"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"byteorder",
"cipher",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[patch.unused]]
name = "libredox"
version = "0.1.18"
+139 -43
View File
@@ -1,52 +1,148 @@
[workspace]
resolver = "3"
members = [".", "rmm"]
[package]
name = "redox_bootloader"
version = "1.0.0"
name = "kernel"
version = "0.5.12"
build = "build.rs"
edition = "2024"
# UEFI uses bin target
[[bin]]
name = "bootloader"
path = "src/main.rs"
# BIOS uses lib target
[lib]
name = "bootloader"
path = "src/main.rs"
crate-type = ["staticlib"]
[build-dependencies]
cc = "1.0"
toml = "0.8"
[dependencies]
bitflags = "1.3.2"
linked_list_allocator = "0.10.5"
log = "0.4.17"
redox_syscall = "0.5"
spin = "0.9.5"
[dependencies.redoxfs]
version = "0.8"
default-features = false
features = ["log"]
[target.'cfg(target_os = "uefi")'.dependencies]
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
#TODO: riscv cannot use target_os = "uefi" at this time
[target.'cfg(target_arch = "riscv64")'.dependencies]
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
[target."aarch64-unknown-uefi".dependencies]
dmidecode = "0.8.0"
[target."x86_64-unknown-uefi".dependencies]
x86 = "0.52.0"
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
byteorder = { version = "1", default-features = false }
arrayvec = { version = "0.7.4", default-features = false }
bitfield = "0.13.2"
bitflags = "2"
fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa36b6a7c5351004be54" }
hashbrown = { version = "0.14.3", default-features = false, features = ["ahash", "inline-more"] }
linked_list_allocator = "0.9.0"
redox-path = "0.2.0"
redox_syscall = { git = "https://gitlab.redox-os.org/redox-os/syscall.git", default-features = false }
rmm = { path = "rmm", default-features = false }
slab = { version = "0.4", default-features = false }
smallvec = { version = "1.15.1", default-features = false }
spin = { version = "0.9.8" }
[dependencies.object]
version = "0.37.1"
default-features = false
features = ["read_core", "elf"]
[dependencies.rustc-demangle]
version = "0.1.16"
default-features = false
[lints.clippy]
# Overflows are very, very bad in kernel code as it may provide an attack vector for
# userspace applications, and it is only checked in debug builds
# TODO: address occurrences and then deny
arithmetic_side_effects = "warn"
cast_ptr_alignment = "warn" # TODO: address occurrences and then deny
identity_op = "allow" # Used to allow stuff like 1 << 0 and 1 * 1024 * 1024
if_same_then_else = "allow" # Useful for adding comments about different branches
# Indexing a slice can cause panics and that is something we always want to avoid
# in kernel code. Use .get and return an error instead
# TODO: address occurrences and then deny
indexing_slicing = "warn"
many_single_char_names = "allow" # Useful in the syscall function
module_inception = "allow" # Used for context::context
# Not implementing default is sometimes useful in the case something has significant cost
# to allocate. If you implement default, it can be allocated without evidence using the
# ..Default::default() syntax. Not fun in kernel space
new_without_default = "allow"
not_unsafe_ptr_arg_deref = "deny"
or_fun_call = "allow" # Used to make it nicer to return errors, for example, .ok_or(Error::new(ESRCH))
precedence = "deny"
ptr_cast_constness = "deny"
too_many_arguments = "allow" # This is needed in some cases, like for syscall
# Avoid panicking in the kernel without information about the panic. Use expect
# TODO: address occurrences and then deny
unwrap_used = "warn"
[lints.rust]
static_mut_refs = "warn" # FIXME deny once all occurrences are fixed
# This is usually a serious issue - a missing import of a define where it is interpreted
# as a catch-all variable in a match, for example
unreachable_patterns = "deny"
unused_must_use = "deny" # Ensure that all must_use results are used
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
raw-cpuid = "10.2.0"
x86 = { version = "0.47.0", default-features = false }
[target.'cfg(any(target_arch = "riscv64", target_arch = "riscv32"))'.dependencies]
sbi-rt = "0.0.3"
[features]
default = []
live = []
serial_debug = []
default = [
"acpi",
#"debugger",
"multi_core",
"serial_debug",
"self_modifying",
"x86_kvm_pv",
#"busy_panic",
#"drop_panic",
#"syscall_debug"
]
# Activates some limited code-overwriting optimizations, based on CPU features.
self_modifying = []
acpi = []
lpss_debug = []
multi_core = ["acpi"]
profiling = []
#TODO: remove when threading issues are fixed
pti = []
drop_panic = []
busy_panic = []
qemu_debug = []
serial_debug = []
system76_ec_debug = []
x86_kvm_pv = []
debugger = ["syscall_debug"]
syscall_debug = []
sys_fdstat = []
[profile.dev]
# Avoids having to define the eh_personality lang item and reduces kernel size
panic = "abort"
[profile.release]
# Avoids having to define the eh_personality lang item and reduces kernel size
panic = "abort"
#lto = true
debug = "full"
# Red Bear OS Phase J: see local/sources/base/Cargo.toml for
# the rationale. Both the kernel and the base workspace need
# the libredox override so that the libredox::error::Error
# type is the same compile-time type as syscall::Error. With
# the local libredox fork at local/sources/libredox/ using
# the local syscall fork at local/sources/syscall/, the
# libredox::error::Error (re-exported from the local syscall)
# and syscall::Error (also the local syscall) are now the
# same type, so `?` conversions in scheme-utils / daemon
# compile cleanly.
[patch.crates-io]
# Phase J: override libredox 0.1.17 to use the local
# fork at ../libredox/ (which itself uses the local syscall
# fork). This breaks the libredox::error::Error <->
# syscall::Error type-identity barrier that previously
# caused E0277 errors in scheme-utils and daemon.
libredox = { path = "../libredox" }
# Phase J: the kernel's redox_syscall dep is a git URL
# (not crates.io), so [patch.crates-io] doesn't apply.
# Use a [patch."<URL>"] section to match the dep source.
# The local fork at ../syscall adds the EnterS2Idle /
# ExitS2Idle AcpiVerb variants — the kernel's direct use
# of AcpiVerb in src/scheme/acpi.rs's kcall handler
# needs the fork to see these variants.
[patch."https://gitlab.redox-os.org/redox-os/syscall.git"]
redox_syscall = { path = "../syscall" }
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2022 Redox OS
Copyright (c) 2017 Jeremy Soller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+60 -18
View File
@@ -1,24 +1,66 @@
TARGET?=x86_64-unknown-uefi
.PHONY: all check
SOURCE:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
BUILD:=$(CURDIR)
export RUST_TARGET_PATH?=$(SOURCE)/targets
BUILD?=$(CURDIR)
export RUST_TARGET_PATH=$(SOURCE)/targets
ifeq ($(TARGET),)
ARCH?=$(shell uname -m)
else
ARCH?=$(shell echo "$(TARGET)" | cut -d - -f1)
endif
ifeq ($(ARCH),riscv64gc)
override ARCH:=riscv64
GNU_TARGET=riscv64-unknown-redox
else ifeq ($(ARCH),i686)
override ARCH:=i586
GNU_TARGET=i686-unknown-redox
else
GNU_TARGET=$(ARCH)-unknown-redox
endif
include $(SOURCE)/mk/$(TARGET).mk
all: $(BUILD)/kernel $(BUILD)/kernel.sym
clean:
rm -rf build target
LD_SCRIPT=$(SOURCE)/linkers/$(ARCH).ld
LOCKFILE=$(SOURCE)/Cargo.lock
MANIFEST=$(SOURCE)/Cargo.toml
TARGET_SPEC=$(RUST_TARGET_PATH)/$(ARCH)-unknown-kernel.json
$(BUILD)/filesystem:
mkdir -p $(BUILD)
rm -f $@.partial
mkdir $@.partial
fallocate -l 1MiB $@.partial/kernel
mv $@.partial $@
KERNEL_CARGO_FEATURES?=
$(BUILD)/filesystem.bin: $(BUILD)/filesystem
mkdir -p $(BUILD)
rm -f $@.partial
fallocate -l 254MiB $@.partial
redoxfs-ar $@.partial $<
mv $@.partial $@
$(BUILD)/kernel.all: $(LD_SCRIPT) $(LOCKFILE) $(MANIFEST) $(TARGET_SPEC) $(shell find $(SOURCE) -name "*.rs" -type f)
cd $(SOURCE) && RUSTUP_TOOLCHAIN=nightly-2025-10-03 cargo rustc \
-Z build-std=core,alloc -Zbuild-std-features=compiler-builtins-mem \
--bin kernel \
--manifest-path "$(MANIFEST)" \
--target "$(TARGET_SPEC)" \
--release \
--features=$(KERNEL_CARGO_FEATURES) \
-- \
-C link-arg=-T -Clink-arg="$(LD_SCRIPT)" \
-C link-arg=-z -Clink-arg=max-page-size=0x1000 \
--emit link="$(BUILD)/kernel.all"
$(BUILD)/kernel.sym: $(BUILD)/kernel.all
$(GNU_TARGET)-objcopy \
--only-keep-debug \
"$(BUILD)/kernel.all" \
"$(BUILD)/kernel.sym"
$(BUILD)/kernel: $(BUILD)/kernel.all
$(GNU_TARGET)-objcopy \
--strip-debug \
"$(BUILD)/kernel.all" \
"$(BUILD)/kernel"
KERNEL_CHECK_FEATURES?=
check:
cargo check \
--bin kernel \
--manifest-path "$(MANIFEST)" \
--target "$(TARGET_SPEC)" \
-Z build-std=core,alloc -Zbuild-std-features=compiler-builtins-mem -Z target-spec-json \
--features=$(KERNEL_CHECK_FEATURES)
+81 -62
View File
@@ -1,62 +1,81 @@
# Bootloader
Redox OS Bootloader
## Requirements
These software needs to be available on the PATH at build time:
+ [mtools](https://www.gnu.org/software/mtools/)
+ [nasm](https://nasm.us/)
+ [redoxfs-ar](https://gitlab.redox-os.org/redox-os/redoxfs)
## Building
```sh
make TARGET=<triplet> BUILD=build all
```
The `<triplet>` is one of:
| ARCH | Boot Mode | Triplets |
|---|---|---|
| `i686` | BIOS | `x86-unknown-none` |
| `x86_64` | BIOS | `x86-unknown-none` |
| `x86_64` | UEFI | `x86_64-unknown-uefi` |
| `aarch64` | UEFI | `aarch64-unknown-uefi` |
| `riscv64gc` | UEFI | `riscv64gc-unknown-uefi` |
See [mk directory](./mk) for more information of how the build is working.
## Entry points
Please read [Boot Process](https://doc.redox-os.org/book/boot-process.html) in the Redox OS Book for an introductory guide.
In this source code, some interesting files for entry points are:
+ BIOS boot stages: [asm/x86-unknown-none/bootloader.asm](./asm/x86-unknown-none/bootloader.asm)
+ BIOS boot entry: `fn start` at [src/os/bios/mod.rs](./src/os/bios/mod.rs)
+ UEFI boot entry: `fn main` at [src/os/uefi/mod.rs](src/os/uefi/mod.rs)
+ Common boot process: `fn main` at [src/main.rs](src/main.rs)
+ UEFI kernel entry: `fn kernel_entry` in each arch:
- `x86_64`: [src/os/uefi/arch/x86_64.rs](src/os/uefi/arch/x86_64.rs)
- `aarch64`: [src/os/uefi/arch/aarch64.rs](src/os/uefi/arch/aarch64.rs)
- `riscv64gc`: [src/os/uefi/arch/riscv64/mod.rs](src/os/uefi/arch/riscv64/mod.rs)
## Debugging
### QEMU
```sh
make TARGET=<triplet> BUILD=build qemu
```
## How To Contribute
To learn how to contribute to this system component you need to read the following document:
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
## Development
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
# Kernel
Redox OS Microkernel
[![docs](https://img.shields.io/badge/docs-master-blue.svg)](https://docs.rs/redox_syscall/latest/syscall/)
[![SLOCs counter](https://tokei.rs/b1/github/redox-os/kernel?category=code)](https://github.com/XAMPPRocky/tokei)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
## Requirements
* [`nasm`](https://nasm.us/) needs to be available on the PATH at build time.
## Building The Documentation
Use this command:
```sh
cargo doc --open --target x86_64-unknown-none
```
## Debugging
### QEMU
Running [QEMU](https://www.qemu.org) with the `-s` flag will set up QEMU to listen on port `1234` for a GDB client to connect to it. To debug the redox kernel run.
```sh
make qemu gdb=yes
```
This will start a virtual machine with and listen on port `1234` for a GDB or LLDB client.
### GDB
If you are going to use [GDB](https://www.gnu.org/software/gdb/), run these commands to load debug symbols and connect to your running kernel:
```
(gdb) symbol-file build/kernel.sym
(gdb) target remote localhost:1234
```
### LLDB
If you are going to use [LLDB](https://lldb.llvm.org/), run these commands to start debugging:
```
(lldb) target create -s build/kernel.sym build/kernel
(lldb) gdb-remote localhost:1234
```
After connecting to your kernel you can set some interesting breakpoints and `continue`
the process. See your debuggers man page for more information on useful commands to run.
## Notes
- Always use `foo.get(n)` instead of `foo[n]` and try to cover for the possibility of `Option::None`. Doing the regular way may work fine for applications, but never in the kernel. No possible panics should ever exist in kernel space, because then the whole OS would just stop working.
- If you receive a kernel panic in QEMU, use `pkill qemu-system` to kill the frozen QEMU process.
## How To Contribute
To learn how to contribute to this system component you need to read the following document:
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
## Development
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
### How To Build
To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page.
This is necessary because they only work with cross-compilation to a Redox virtual machine, but you can do some testing from Linux.
## Funding - _Unix-style Signals and Process Management_
This project is funded through [NGI Zero Core](https://nlnet.nl/core), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/RedoxOS-Signals).
[<img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" />](https://nlnet.nl)
[<img src="https://nlnet.nl/image/logos/NGI0_tag.svg" alt="NGI Zero Logo" width="20%" />](https://nlnet.nl/core)
-17
View File
@@ -1,17 +0,0 @@
interrupt_vector_table:
b . @ Reset
b .
b . @ SWI instruction
b .
b .
b .
b .
b .
.comm stack, 0x10000 @ Reserve 64k stack in the BSS
_start:
.globl _start
ldr sp, =stack+0x10000 @ Set up the stack
bl kstart @ Jump to the main function
1:
b 1b @ Halt
-31
View File
@@ -1,31 +0,0 @@
sectalign off
; stage 1 is sector 0, loaded at 0x7C00
%include "stage1.asm"
; GPT area from sector 1 to 33, loaded at 0x7E00
times (33*512) db 0
; stage 2, loaded at 0xC000
stage2:
%include "stage2.asm"
align 512, db 0
stage2.end:
; the maximum size of stage2 is 4 KiB
times (4*1024)-($-stage2) db 0
; ISO compatibility, uses up space until 0x12400
%include "iso.asm"
times 3072 db 0 ; Pad to 0x13000
; stage3, loaded at 0x13000
stage3:
%defstr STAGE3_STR %[STAGE3]
incbin STAGE3_STR
align 512, db 0
.end:
; the maximum size of the boot loader portion is 384 KiB
times (384*1024)-($-$$) db 0
-176
View File
@@ -1,176 +0,0 @@
SECTION .text
USE16
cpuid_required_features:
.edx equ cpuid_edx.fpu | cpuid_edx.pse | cpuid_edx.pge | cpuid_edx.fxsr
.ecx equ 0
cpuid_check:
; If bit 21 of EFLAGS can be changed, then CPUID is supported
pushfd ;Save EFLAGS
pushfd ;Store EFLAGS
xor dword [esp],0x00200000 ;Invert the ID bit in stored EFLAGS
popfd ;Load stored EFLAGS (with ID bit inverted)
pushfd ;Store EFLAGS again (ID bit may or may not be inverted)
pop eax ;eax = modified EFLAGS (ID bit may or may not be inverted)
xor eax,[esp] ;eax = whichever bits were changed
popfd ;Restore original EFLAGS
test eax,0x00200000 ;eax = zero if ID bit can't be changed, else non-zero
jz .no_cpuid
mov eax, 1
cpuid
and edx, cpuid_required_features.edx
cmp edx, cpuid_required_features.edx
jne .error
and ecx, cpuid_required_features.ecx
cmp ecx, cpuid_required_features.ecx
jne .error
ret
.no_cpuid:
mov si, .msg_cpuid
call print
mov si, .msg_line
call print
jmp .halt
.error:
push ecx
push edx
mov si, .msg_features
call print
mov si, .msg_line
call print
mov si, .msg_edx
call print
pop ebx
push ebx
shr ebx, 16
call print_hex
pop ebx
call print_hex
mov si, .msg_must_contain
call print
mov ebx, cpuid_required_features.edx
shr ebx, 16
call print_hex
mov ebx, cpuid_required_features.edx
call print_hex
mov si, .msg_line
call print
mov si, .msg_ecx
call print
pop ebx
push ebx
shr ebx, 16
call print_hex
pop ebx
call print_hex
mov si, .msg_must_contain
call print
mov ebx, cpuid_required_features.ecx
shr ebx, 16
call print_hex
mov ebx, cpuid_required_features.ecx
call print_hex
mov si, .msg_line
call print
.halt:
cli
hlt
jmp .halt
.msg_cpuid: db "CPUID not supported",0
.msg_features: db "Required CPU features are not present",0
.msg_line: db 13,10,0
.msg_edx: db "EDX ",0
.msg_ecx: db "ECX ",0
.msg_must_contain: db " must contain ",0
cpuid_edx:
.fpu equ 1 << 0
.vme equ 1 << 1
.de equ 1 << 2
.pse equ 1 << 3
.tsc equ 1 << 4
.msr equ 1 << 5
.pae equ 1 << 6
.mce equ 1 << 7
.cx8 equ 1 << 8
.apic equ 1 << 9
.sep equ 1 << 11
.mtrr equ 1 << 12
.pge equ 1 << 13
.mca equ 1 << 14
.cmov equ 1 << 15
.pat equ 1 << 16
.pse_36 equ 1 << 17
.psn equ 1 << 18
.clfsh equ 1 << 19
.ds equ 1 << 21
.acpi equ 1 << 22
.mmx equ 1 << 23
.fxsr equ 1 << 24
.sse equ 1 << 25
.sse2 equ 1 << 26
.ss equ 1 << 27
.htt equ 1 << 28
.tm equ 1 << 29
.ia64 equ 1 << 30
.pbe equ 1 << 31
cpuid_ecx:
.sse3 equ 1 << 0
.pclmulqdq equ 1 << 1
.dtes64 equ 1 << 2
.monitor equ 1 << 3
.ds_cpl equ 1 << 4
.vmx equ 1 << 5
.smx equ 1 << 6
.est equ 1 << 7
.tm2 equ 1 << 8
.ssse3 equ 1 << 9
.cnxt_id equ 1 << 10
.sdbg equ 1 << 11
.fma equ 1 << 12
.cmpxchg16b equ 1 << 13
.xtpr equ 1 << 14
.pdcm equ 1 << 15
.pcid equ 1 << 17
.dca equ 1 << 18
.sse4_1 equ 1 << 19
.sse4_2 equ 1 << 20
.x2apic equ 1 << 21
.movbe equ 1 << 22
.popcnt equ 1 << 23
.tsc_deadline equ 1 << 24
.aes equ 1 << 25
.xsave equ 1 << 26
.osxsave equ 1 << 27
.avx equ 1 << 28
.f16c equ 1 << 29
.rdrand equ 1 << 30
.hypervisor equ 1 << 31
-128
View File
@@ -1,128 +0,0 @@
SECTION .text ; cannot use .data
struc GDTEntry
.limitl resw 1
.basel resw 1
.basem resb 1
.attribute resb 1
.flags__limith resb 1
.baseh resb 1
endstruc
gdt_attr:
.present equ 1 << 7
.ring1 equ 1 << 5
.ring2 equ 1 << 6
.ring3 equ 1 << 5 | 1 << 6
.user equ 1 << 4
;user
.code equ 1 << 3
; code
.conforming equ 1 << 2
.readable equ 1 << 1
; data
.expand_down equ 1 << 2
.writable equ 1 << 1
.accessed equ 1 << 0
;system
; legacy
.tssAvailabe16 equ 0x1
.ldt equ 0x2
.tssBusy16 equ 0x3
.call16 equ 0x4
.task equ 0x5
.interrupt16 equ 0x6
.trap16 equ 0x7
.tssAvailabe32 equ 0x9
.tssBusy32 equ 0xB
.call32 equ 0xC
.interrupt32 equ 0xE
.trap32 equ 0xF
; long mode
.ldt32 equ 0x2
.tssAvailabe64 equ 0x9
.tssBusy64 equ 0xB
.call64 equ 0xC
.interrupt64 equ 0xE
.trap64 equ 0xF
gdt_flag:
.granularity equ 1 << 7
.available equ 1 << 4
;user
.default_operand_size equ 1 << 6
; code
.long_mode equ 1 << 5
; data
.reserved equ 1 << 5
gdtr:
dw gdt.end + 1 ; size
dq gdt ; offset
gdt:
.null equ $ - gdt
dq 0
.lm64_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code
at GDTEntry.flags__limith, db gdt_flag.long_mode
at GDTEntry.baseh, db 0
iend
.lm64_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0
at GDTEntry.baseh, db 0
iend
.pm32_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
at GDTEntry.baseh, db 0
iend
.pm32_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
at GDTEntry.baseh, db 0
iend
.pm16_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
at GDTEntry.flags__limith, db 0xF
at GDTEntry.baseh, db 0
iend
.pm16_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0xF
at GDTEntry.baseh, db 0
iend
.end equ $ - gdt
-161
View File
@@ -1,161 +0,0 @@
; Simple ISO emulation with el torito
; Fill until CD sector 0x10
times (0x10*2048)-($-$$) db 0
; Volume record
;TODO: fill in more fields
iso_volume_record:
db 1 ; Type volume record
db "CD001" ; Identifier
db 1 ; Version
db 0 ; Unused
times 32 db ' ' ; System identifier
.volume_id: ; Volume identifier
db 'Redox OS'
times 32-($-.volume_id) db ' '
times 8 db 0 ; Unused
db 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15 ; Volume space size (0x15)
times 32 db 0 ; Unused
db 0x01, 0x00, 0x00, 0x01 ; Volume set size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x00, 0x08, 0x08, 0x00 ; Logical block size in little and big endian
times 156-($-iso_volume_record) db 0
; Root directory entry
.root_directory:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x00 ; File identifier
times 128 db ' ' ; Volume set identifier
times 128 db ' ' ; Publisher identifier
times 128 db ' ' ; Data preparer identifier
times 128 db ' ' ; Application identifier
times 37 db ' ' ; Copyright file ID
times 37 db ' ' ; Abstract file ID
times 37 db ' ' ; Bibliographic file ID
times 881-($-iso_volume_record) db 0
db 1 ; File structure version
; Fill until CD sector 0x11
times (0x11*2048)-($-$$) db 0
; Boot record
iso_boot_record:
db 0 ; Type boot record
db "CD001" ; Identifier
db 1 ; Version
db "EL TORITO SPECIFICATION" ; Boot system identifier
times 0x47-($ - iso_boot_record) db 0 ; Padding
dd 0x13 ; Sector of boot catalog
; Fill until CD sector 0x12
times (0x12*2048)-($-$$) db 0
; Terminator
iso_terminator:
db 0xFF ; Type terminator
db "CD001" ; Identifier
db 1 ; Version
; Fill until CD sector 0x13
times (0x13*2048)-($-$$) db 0
; Boot catalog
iso_boot_catalog:
; Validation entry
.validation:
db 1 ; Header ID
db 0 ; Platform ID (x86)
dw 0 ; Reserved
times 24 db 0 ; ID string
dw 0x55aa ; Checksum
dw 0xaa55 ; Key
; Default entry
.default:
db 0x88 ; Bootable
db 4 ; Hard drive emulation
dw 0 ; Load segment (0 is platform default)
db 0xEE ; Partition type (0xEE is protective MBR)
db 0 ; Unused
dw 1 ; Sector count
dd 0 ; Start address for virtual disk
times 20 db 0 ; Padding
; EFI section header entry
.efi_section_header:
db 0x91 ; Final header
db 0xEF ; Platform ID (EFI)
dw 1 ; Number of section header entries
times 28 db 0 ; ID string
; EFI section entry
.efi_section_entry:
db 0x88 ; Bootable
db 0 ; No emulation
dw 0 ; Load segment (0 is platform default)
db 0 ; Partition type (not used)
db 0 ; Unused
dw 512 ; Sector count (1 MiB = 512 CD sectors)
dd 512 ; Start address for virtual disk (1 MiB = 512 CD sectors)
times 20 db 0 ; Padding
; Fill until CD sector 0x14
times (0x14*2048)-($-$$) db 0
iso_root_directory:
.self:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x00 ; File identifier
.parent:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x01 ; File identifier
.boot_cat:
db 0x2C ; Length of entry
db 0x00 ; Length of extended attributes
db 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13 ; Location of extent (0x13)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x00 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x0A ; Length of file identifier
db "BOOT.CAT;1",0 ; File identifier
; Fill until CD sector 0x15
times (0x15*2048)-($-$$) db 0
-56
View File
@@ -1,56 +0,0 @@
SECTION .text
USE32
long_mode:
.func: dq 0
.page_table: dd 0
.entry:
; disable interrupts
cli
; disable paging
mov eax, cr0
and eax, 0x7FFFFFFF
mov cr0, eax
; enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension
mov eax, cr4
or eax, 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4
mov cr4, eax
; load long mode GDT
lgdt [gdtr]
; enable long mode
mov ecx, 0xC0000080 ; Read from the EFER MSR.
rdmsr
or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit.
wrmsr
; set page table
mov eax, [.page_table]
mov cr3, eax
; enabling paging and protection simultaneously
mov eax, cr0
or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
mov cr0, eax
; far jump to enable Long Mode and load CS with 64 bit segment
jmp gdt.lm64_code:.inner
USE64
.inner:
; load all the other segments with 64 bit data segments
mov rax, gdt.lm64_data
mov ds, rax
mov es, rax
mov fs, rax
mov gs, rax
mov ss, rax
; jump to specified function
mov rax, [.func]
jmp rax
-67
View File
@@ -1,67 +0,0 @@
SECTION .text
USE16
; provide function for printing in x86 real mode
; print a string and a newline
; CLOBBER
; ax
print_line:
mov al, 13
call print_char
mov al, 10
jmp print_char
; print a string
; IN
; si: points at zero-terminated String
; CLOBBER
; si, ax
print:
pushf
cld
.loop:
lodsb
test al, al
jz .done
call print_char
jmp .loop
.done:
popf
ret
; print a character
; IN
; al: character to print
print_char:
pusha
mov bx, 7
mov ah, 0x0e
int 0x10
popa
ret
; print a number in hex
; IN
; bx: the number
; CLOBBER
; al, cx
print_hex:
mov cx, 4
.lp:
mov al, bh
shr al, 4
cmp al, 0xA
jb .below_0xA
add al, 'A' - 0xA - '0'
.below_0xA:
add al, '0'
call print_char
shl bx, 4
loop .lp
ret
-36
View File
@@ -1,36 +0,0 @@
SECTION .text
USE16
protected_mode:
.func: dd 0
.entry:
; disable interrupts
cli
; load protected mode GDT
lgdt [gdtr]
; set protected mode bit of cr0
mov eax, cr0
or eax, 1
mov cr0, eax
; far jump to load CS with 32 bit segment
jmp gdt.pm32_code:.inner
USE32
.inner:
; load all the other segments with 32 bit data segments
mov eax, gdt.pm32_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; jump to specified function
mov eax, [.func]
jmp eax
-222
View File
@@ -1,222 +0,0 @@
ORG 0x7C00
SECTION .text
USE16
stage1: ; dl comes with disk
; initialize segment registers
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
; initialize stack
mov sp, 0x7C00
; initialize CS
push ax
push word .set_cs
retf
.set_cs:
; save disk number
mov [disk], dl
mov si, stage_msg
call print
mov al, '1'
call print_char
call print_line
; read CHS gemotry
; CL (bits 0-5) = maximum sector number
; CL (bits 6-7) = high bits of max cylinder number
; CH = low bits of maximum cylinder number
; DH = maximum head number
mov ah, 0x08
mov dl, [disk]
xor di, di
int 0x13
jc error ; carry flag set on error
mov bl, ch
mov bh, cl
shr bh, 6
mov [chs.c], bx
shr dx, 8
inc dx ; returns heads - 1
mov [chs.h], dx
and cl, 0x3f
mov [chs.s], cl
mov eax, (stage2 - stage1) / 512
mov bx, stage2
mov cx, (stage3.end - stage2) / 512
mov dx, 0
call load
mov si, stage_msg
call print
mov al, '2'
call print_char
call print_line
jmp stage2.entry
; load some sectors from disk to a buffer in memory
; buffer has to be below 1MiB
; IN
; ax: start sector
; bx: offset of buffer
; cx: number of sectors (512 Bytes each)
; dx: segment of buffer
; CLOBBER
; ax, bx, cx, dx, si
; TODO rewrite to (eventually) move larger parts at once
; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end)
load:
cmp cx, 127
jbe .good_size
pusha
mov cx, 127
call load
popa
add eax, 127
add dx, 127 * 512 / 16
sub cx, 127
jmp load
.good_size:
mov [DAPACK.addr], eax
mov [DAPACK.buf], bx
mov [DAPACK.count], cx
mov [DAPACK.seg], dx
call print_dapack
cmp byte [chs.s], 0
jne .chs
;INT 0x13 extended read does not work on CDROM!
mov dl, [disk]
mov si, DAPACK
mov ah, 0x42
int 0x13
jc error ; carry flag set on error
ret
.chs:
; calculate CHS
xor edx, edx
mov eax, [DAPACK.addr]
div dword [chs.s] ; divide by sectors
mov ecx, edx ; move sector remainder to ecx
xor edx, edx
div dword [chs.h] ; divide by heads
; eax has cylinders, edx has heads, ecx has sectors
; Sector cannot be greater than 63
inc ecx ; Sector is base 1
cmp ecx, 63
ja error_chs
; Head cannot be greater than 255
cmp edx, 255
ja error_chs
; Cylinder cannot be greater than 1023
cmp eax, 1023
ja error_chs
; Move CHS values to parameters
mov ch, al
shl ah, 6
and cl, 0x3f
or cl, ah
shl dx, 8
; read from disk using CHS
mov al, [DAPACK.count]
mov ah, 0x02 ; disk read (CHS)
mov bx, [DAPACK.buf]
mov dl, [disk]
push es ; save ES
mov es, [DAPACK.seg]
int 0x13
pop es ; restore EC
jc error ; carry flag set on error
ret
print_dapack:
mov bx, [DAPACK.addr + 2]
call print_hex
mov bx, [DAPACK.addr]
call print_hex
mov al, '#'
call print_char
mov bx, [DAPACK.count]
call print_hex
mov al, ' '
call print_char
mov bx, [DAPACK.seg]
call print_hex
mov al, ':'
call print_char
mov bx, [DAPACK.buf]
call print_hex
call print_line
ret
error_chs:
mov ah, 0
error:
call print_line
mov bh, 0
mov bl, ah
call print_hex
mov al, ' '
call print_char
mov si, error_msg
call print
call print_line
.halt:
cli
hlt
jmp .halt
%include "print.asm"
stage_msg: db "Stage ",0
error_msg: db "ERROR",0
disk: db 0
chs:
.c: dd 0
.h: dd 0
.s: dd 0
DAPACK:
db 0x10
db 0
.count: dw 0 ; int 13 resets this to # of blocks actually read/written
.buf: dw 0 ; memory buffer destination address (0:7c00)
.seg: dw 0 ; in memory page zero
.addr: dq 0 ; put the lba to read in this spot
times 446-($-$$) db 0
partitions: times 4 * 16 db 0
db 0x55
db 0xaa
-134
View File
@@ -1,134 +0,0 @@
SECTION .text
USE16
stage2.entry:
; check for required features
call cpuid_check
; enable A20-Line via IO-Port 92, might not work on all motherboards
in al, 0x92
or al, 2
out 0x92, al
mov dword [protected_mode.func], stage3.entry
jmp protected_mode.entry
%include "cpuid.asm"
%include "gdt.asm"
%include "long_mode.asm"
%include "protected_mode.asm"
%include "thunk.asm"
USE32
stage3.entry:
; stage3 stack at 448 KiB (512KiB minus 64KiB disk buffer)
mov esp, 0x70000
; push arguments
mov eax, thunk.int16
push eax
mov eax, thunk.int15
push eax
mov eax, thunk.int13
push eax
mov eax, thunk.int10
push eax
xor eax, eax
mov al, [disk]
push eax
mov eax, kernel.entry
push eax
mov eax, [stage3 + 0x18]
call eax
.halt:
cli
hlt
jmp .halt
kernel:
.stack: dq 0
.func: dq 0
.args: dq 0
.entry:
; page_table: usize
mov eax, [esp + 4]
mov [long_mode.page_table], eax
; stack: u64
mov eax, [esp + 8]
mov [.stack], eax
mov eax, [esp + 12]
mov [.stack + 4], eax
; func: u64
mov eax, [esp + 16]
mov [.func], eax
mov eax, [esp + 20]
mov [.func + 4], eax
; args: *const KernelArgs
mov eax, [esp + 24]
mov [.args], eax
; long_mode: usize
mov eax, [esp + 28]
test eax, eax
jz .inner32
mov eax, .inner64
mov [long_mode.func], eax
jmp long_mode.entry
.inner32:
; disable paging
mov eax, cr0
and eax, 0x7FFFFFFF
mov cr0, eax
;TODO: PAE (1 << 5)
; enable FXSAVE/FXRSTOR, Page Global, and Page Size Extension
mov eax, cr4
or eax, 1 << 9 | 1 << 7 | 1 << 4
mov cr4, eax
; set page table
mov eax, [long_mode.page_table]
mov cr3, eax
; enabling paging and protection simultaneously
mov eax, cr0
; Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
or eax, 1 << 31 | 1 << 16 | 1
mov cr0, eax
; enable FPU
;TODO: move to Rust
mov eax, cr0
and al, 11110011b ; Clear task switched (3) and emulation (2)
or al, 00100010b ; Set numeric error (5) monitor co-processor (1)
mov cr0, eax
fninit
mov esp, [.stack]
mov eax, [.args]
push eax
mov eax, [.func]
call eax
.halt32:
cli
hlt
jmp .halt32
USE64
.inner64:
mov rsp, [.stack]
mov rax, [.func]
mov rdi, [.args]
call rax
.halt64:
cli
hlt
jmp .halt64
-149
View File
@@ -1,149 +0,0 @@
SECTION .text
USE32
thunk:
.int10:
mov dword [.func], .int10_real
jmp .enter
.int13:
mov dword [.func], .int13_real
jmp .enter
.int15:
mov dword [.func], .int15_real
jmp .enter
.int16:
mov dword [.func], .int16_real
jmp .enter
.func: dd 0
.esp: dd 0
.cr0: dd 0
.enter:
; save flags
pushfd
; save registers
pushad
; save esp
mov [.esp], esp
; load gdt
lgdt [gdtr]
; far jump to protected mode 16-bit
jmp gdt.pm16_code:.pm16
.exit:
; set segment selectors to 32-bit protected mode
mov eax, gdt.pm32_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; restore esp
mov esp, [.esp]
; restore registers
popad
; restore flags
popfd
; return
ret
USE16
.int10_real:
int 0x10
ret
.int13_real:
int 0x13
ret
.int15_real:
int 0x15
ret
.int16_real:
int 0x16
ret
.pm16:
; set segment selectors to protected mode 16-bit
mov eax, gdt.pm16_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; save cr0
mov eax, cr0
mov [.cr0], eax
; disable paging and protected mode
and eax, 0x7FFFFFFE
mov cr0, eax
; far jump to real mode
jmp 0:.real
.real:
; set segment selectors to real mode
mov eax, 0
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; set stack
mov esp, 0x7C00 - 64
; load registers and ES
pop es
pop edi
pop esi
pop ebp
pop ebx
pop edx
pop ecx
pop eax
; enable interrupts
sti
; call real mode function
call [.func]
; disable interrupts
cli
; save registers and ES
push eax
push ecx
push edx
push ebx
push ebp
push esi
push edi
push es
; load gdt (BIOS sometimes overwrites this)
lgdt [gdtr]
; restore cr0, will enable protected mode
mov eax, [.cr0]
mov cr0, eax
; far jump to protected mode 32-bit
jmp gdt.pm32_code:.exit
+100
View File
@@ -0,0 +1,100 @@
#![allow(clippy::unwrap_used)] // the build script can panic
use std::{env, path::Path, process::Command};
use toml::Table;
fn parse_kconfig(arch: &str) -> Option<()> {
println!("cargo:rerun-if-changed=config.toml");
assert!(Path::new("config.toml.example").try_exists().unwrap());
if !Path::new("config.toml").try_exists().unwrap() {
std::fs::copy("config.toml.example", "config.toml").unwrap();
}
let config_str = std::fs::read_to_string("config.toml").unwrap();
let root: Table = toml::from_str(&config_str).unwrap();
let altfeatures = root
.get("arch")?
.as_table()
.unwrap()
.get(arch)?
.as_table()
.unwrap()
.get("features")?
.as_table()
.unwrap();
#[expect(clippy::format_collect)] // TODO: remove once version is bumped
let features_list = altfeatures
.keys()
.map(|feat| format!(", {feat:?}"))
.collect::<String>();
println!("cargo::rustc-check-cfg=cfg(cpu_feature_always, values(\"\"{features_list}))");
println!("cargo::rustc-check-cfg=cfg(cpu_feature_auto, values(\"\"{features_list}))");
println!("cargo::rustc-check-cfg=cfg(cpu_feature_never, values(\"\"{features_list}))");
let self_modifying = env::var("CARGO_FEATURE_SELF_MODIFYING").is_ok();
for (name, value) in altfeatures {
let mut choice = value.as_str().unwrap();
assert!(matches!(choice, "always" | "never" | "auto"));
if !self_modifying && choice == "auto" {
choice = "never";
}
println!("cargo:rustc-cfg=cpu_feature_{choice}=\"{name}\"");
}
Some(())
}
fn main() {
println!("cargo::rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo::rustc-check-cfg=cfg(dtb)");
let out_dir = env::var("OUT_DIR").unwrap();
let arch_str = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
match &*arch_str {
"aarch64" => {
println!("cargo::rustc-cfg=dtb");
}
"x86" => {
println!("cargo::rerun-if-changed=src/asm/x86/trampoline.asm");
let status = Command::new("nasm")
.arg("-f")
.arg("bin")
.arg("-o")
.arg(format!("{}/trampoline", out_dir))
.arg("src/asm/x86/trampoline.asm")
.status()
.expect("failed to run nasm");
if !status.success() {
panic!("nasm failed with exit status {}", status);
}
}
"x86_64" => {
println!("cargo::rerun-if-changed=src/asm/x86_64/trampoline.asm");
let status = Command::new("nasm")
.arg("-f")
.arg("bin")
.arg("-o")
.arg(format!("{}/trampoline", out_dir))
.arg("src/asm/x86_64/trampoline.asm")
.status()
.expect("failed to run nasm");
if !status.success() {
panic!("nasm failed with exit status {}", status);
}
}
"riscv64" => {
println!("cargo::rustc-cfg=dtb");
}
_ => (),
}
let _ = parse_kconfig(&arch_str);
}
Executable
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
export RUST_TARGET_PATH="${PWD}/targets"
export RUSTFLAGS="-C debuginfo=2"
cargo clippy --lib --release --target x86_64-unknown-none "$@"
+7
View File
@@ -0,0 +1,7 @@
[arch.x86_64.features]
smap = "auto"
fsgsbase = "auto"
xsave = "auto"
xsaveopt = "auto"
# vim: ft=toml
+55
View File
@@ -0,0 +1,55 @@
ENTRY(kstart)
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
KERNEL_OFFSET = 0xFFFFFF0000000000;
SECTIONS {
. = KERNEL_OFFSET;
. += SIZEOF_HEADERS;
/* Force the zero page to be part of a segment by creating a
* dummy section in the zero page.
* Limine will map the segment with the lowest vaddr value at
* 0xFFFFFFFF80000000 even if the segment has a higher vaddr.
* As such without the zero page being part of a segment, the
* kernel would be loaded at an offset from the expected
* location. As the redox kernel is not currently relocatable,
* this would result in a crash. A similar issue likely exists
* with multiboot/multiboot2 and the paddr of the segment.
*/
.dummy ALIGN(8) : AT(ADDR(.dummy) - KERNEL_OFFSET) {}
. = ALIGN(4096);
.text : AT(ADDR(.text) - KERNEL_OFFSET) {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : AT(ADDR(.rodata) - KERNEL_OFFSET) {
__rodata_start = .;
*(.rodata*)
. = ALIGN(4096);
__rodata_end = .;
}
.data : AT(ADDR(.data) - KERNEL_OFFSET) {
*(.data*)
. = ALIGN(4096);
*(.bss*)
. = ALIGN(4096);
}
__end = .;
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
+51
View File
@@ -0,0 +1,51 @@
ENTRY(kstart)
OUTPUT_FORMAT(elf32-i386)
KERNEL_OFFSET = 0xC0000000;
SECTIONS {
. = KERNEL_OFFSET;
. += SIZEOF_HEADERS;
/* Force the zero page to be part of a segment by creating a
* dummy section in the zero page.
* Limine will map the segment with the lowest vaddr value at
* 0xFFFFFFFF80000000 even if the segment has a higher vaddr.
* As such without the zero page being part of a segment, the
* kernel would be loaded at an offset from the expected
* location. As the redox kernel is not currently relocatable,
* this would result in a crash. A similar issue likely exists
* with multiboot/multiboot2 and the paddr of the segment.
*/
.dummy : AT(ADDR(.dummy) - KERNEL_OFFSET) {}
.text ALIGN(4K) : AT(ADDR(.text) - KERNEL_OFFSET) {
__text_start = .;
*(.text*)
}
.rodata ALIGN(4K) : AT(ADDR(.rodata) - KERNEL_OFFSET) {
__text_end = .;
__rodata_start = .;
*(.rodata*)
}
.data ALIGN(4K) : AT(ADDR(.data) - KERNEL_OFFSET) {
__rodata_end = .;
*(.data*)
. = ALIGN(4K);
*(.bss*)
. = ALIGN(4K);
}
__end = .;
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-78
View File
@@ -1,78 +0,0 @@
OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv")
OUTPUT_ARCH(riscv)
ENTRY(coff_start)
SECTIONS
{
PROVIDE(ImageBase = .);
. = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) *(.gnu.hash) }
. = ALIGN(4096);
.text :
{
PROVIDE(_text = .);
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(SORT(.text.sorted.*))
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf.em. */
*(.gnu.warning)
}
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
. = ALIGN(4096);
.rdata :
{
*(.rodata .rodata.* .gnu.linkonce.r.*)
*(.rodata1)
KEEP (*(.eh_frame))
*(.eh_frame.*)
*(.dynamic)
}
. = ALIGN(4096);
.data :
{
*(.got) *(.igot)
*(.got.plt) *(.igot.plt)
*(.data .data.* .gnu.linkonce.d.*)
*(.data1)
*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*)
*(.sdata2 .sdata2.* .gnu.linkonce.s2.*)
*(.sdata .sdata.* .gnu.linkonce.s.*)
PROVIDE (_edata = .); PROVIDE (edata = .);
. = ALIGN(4096);
PROVIDE (__bss_start = .);
*(.sbss2 .sbss2.* .gnu.linkonce.sb2.*)
*(.dynsbss)
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(4096);
}
.reloc :
{
KEEP(*(.reloc*))
}
.rela :
{
*(.rela.*)
}
.data.rel.ro :
{
*(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*)
*(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*)
}
. = ALIGN(4096);
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}
+61
View File
@@ -0,0 +1,61 @@
ENTRY(kstart)
OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv" )
KERNEL_OFFSET = 0xFFFFFFFF80000000;
SECTIONS {
. = KERNEL_OFFSET;
. += SIZEOF_HEADERS;
/* Force the zero page to be part of a segment by creating a
* dummy section in the zero page.
* Linker will map the segment with the lowest vaddr value at
* 0xFFFFFFFF80000000 even if the segment has a higher vaddr.
* As such without the zero page being part of a segment, the
* kernel would be loaded at an offset from the expected
* location. As the redox kernel is not currently relocatable,
* this would result in a crash. A similar issue likely exists
* with multiboot/multiboot2 and the paddr of the segment.
*/
.dummy ALIGN(8) : AT(ADDR(.dummy) - KERNEL_OFFSET) {}
. = ALIGN(4096);
.text : AT(ADDR(.text) - KERNEL_OFFSET) {
__text_start = .;
*(.early_init.text*)
. = ALIGN(4096);
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : AT(ADDR(.rodata) - KERNEL_OFFSET) {
__rodata_start = .;
*(.rodata*)
. = ALIGN(4096);
__rodata_end = .;
}
.data : AT(ADDR(.data) - KERNEL_OFFSET) {
*(.data*)
*(.sdata*)
. = ALIGN(4096);
*(.got*)
. = ALIGN(4096);
*(.bss*)
*(.sbss*)
. = ALIGN(4096);
}
__end = .;
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-57
View File
@@ -1,57 +0,0 @@
ENTRY(start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
/* The start address must match bootloader.asm */
. = 0x13000;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
.tdata : {
__tdata_start = .;
*(.tdata*)
. = ALIGN(4096);
__tdata_end = .;
__tbss_start = .;
*(.tbss*)
. += 8;
. = ALIGN(4096);
__tbss_end = .;
}
__end = .;
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
+60
View File
@@ -0,0 +1,60 @@
ENTRY(kstart)
OUTPUT_FORMAT(elf64-x86-64)
KERNEL_OFFSET = 0xFFFFFFFF80000000;
SECTIONS {
. = KERNEL_OFFSET;
. += SIZEOF_HEADERS;
/* Force the zero page to be part of a segment by creating a
* dummy section in the zero page.
* Limine will map the segment with the lowest vaddr value at
* 0xFFFFFFFF80000000 even if the segment has a higher vaddr.
* As such without the zero page being part of a segment, the
* kernel would be loaded at an offset from the expected
* location. As the redox kernel is not currently relocatable,
* this would result in a crash. A similar issue likely exists
* with multiboot/multiboot2 and the paddr of the segment.
*/
.dummy : AT(ADDR(.dummy) - KERNEL_OFFSET) {}
.text ALIGN(4K) : AT(ADDR(.text) - KERNEL_OFFSET) {
__text_start = .;
*(.text*)
}
.rodata ALIGN(4K) : AT(ADDR(.rodata) - KERNEL_OFFSET) {
__text_end = .;
__rodata_start = .;
*(.rodata*)
__altcode_start = .;
KEEP(*(.altcode*))
__altcode_end = .;
. = ALIGN(8);
__altrelocs_start = .;
KEEP(*(.altrelocs*))
__altrelocs_end = .;
__altfeatures_start = .;
KEEP(*(.altfeatures*))
__altfeatures_end = .;
}
.data ALIGN(4K) : AT(ADDR(.data) - KERNEL_OFFSET) {
__rodata_end = .;
*(.data*)
. = ALIGN(4K);
*(.bss*)
}
__end = .;
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-69
View File
@@ -1,69 +0,0 @@
export PARTED?=parted
export QEMU?=qemu-system-aarch64
all: $(BUILD)/bootloader.efi
$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target $(TARGET) \
--bin bootloader \
--release \
-- \
--emit link="$@"
$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target $(TARGET) \
--bin bootloader \
--release \
--features live \
-- \
--emit link="$@"
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
rm -f "$@.partial"
fallocate -l 64MiB "$@.partial"
mkfs.vfat -F 32 "$@.partial"
mmd -i "$@.partial" efi
mmd -i "$@.partial" efi/boot
mcopy -i "$@.partial" "$<" ::efi/boot/bootaa64.efi
mv "$@.partial" "$@"
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
rm -f "$@.partial"
fallocate -l 320MiB "$@.partial"
$(PARTED) -s -a minimal "$@.partial" mklabel gpt
$(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 65MiB
$(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 65MiB 100%
$(PARTED) -s -a minimal "$@.partial" toggle 1 boot
dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=65 conv=notrunc
mv "$@.partial" "$@"
$(BUILD)/firmware.rom: /usr/share/AAVMF/AAVMF_CODE.fd
cp "$<" "$@"
qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom
$(QEMU) \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-device virtio-gpu-pci \
-machine virt \
-net none \
-cpu max \
-bios "$(BUILD)/firmware.rom" \
-drive file="$<",format=raw
-108
View File
@@ -1,108 +0,0 @@
LD=riscv64-unknown-redox-ld
OBJCOPY=riscv64-unknown-redox-objcopy
SCRIPT=$(SOURCE)/linkers/riscv64-unknown-uefi.ld
PARTED?=parted
QEMU?=qemu-system-riscv64
all: $(BUILD)/bootloader.efi
$(BUILD)/%.efi: $(BUILD)/%.efi.elf $(BUILD)/%.efi.sym
$(OBJCOPY) -j .text -j .data -j .rdata -j .rela -j .reloc --target pei-riscv64-little \
--file-alignment 512 --section-alignment 4096 --subsystem 10 "$<" "$@"
.PRECIOUS: $(BUILD)/%.efi.sym
$(BUILD)/%.efi.sym: $(BUILD)/%.efi.elf
$(OBJCOPY) --only-keep-debug "$<" "$@"
$(BUILD)/%.efi.elf: $(BUILD)/%.a $(SCRIPT)
$(LD) --gc-sections -z max-page-size=0x1000 --warn-common --no-undefined -z nocombreloc -shared \
--fatal-warnings -Bsymbolic --entry coff_start -T "$(SCRIPT)" -o "$@" "$<"
$(BUILD)/bootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target $(TARGET) \
--lib \
--release \
-- \
--emit link=$@
$(BUILD)/bootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target $(TARGET) \
--lib \
--release \
--features live \
-- \
--emit link=$@
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
rm -f $@.partial
fallocate -l 64MiB $@.partial
mkfs.vfat -F 32 $@.partial
mmd -i $@.partial EFI
mmd -i $@.partial EFI/BOOT
mcopy -i $@.partial $< ::EFI/BOOT/BOOTRISCV64.EFI
mv $@.partial $@
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
rm -f $@.partial
fallocate -l 320MiB $@.partial
$(PARTED) -s -a minimal $@.partial mklabel gpt
$(PARTED) -s -a minimal $@.partial mkpart ESP FAT32 1MiB 65MiB
$(PARTED) -s -a minimal $@.partial mkpart REDOXFS 65MiB 100%
$(PARTED) -s -a minimal $@.partial toggle 1 boot
dd if=$(BUILD)/esp.bin of=$@.partial bs=1MiB seek=1 conv=notrunc
dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=65 conv=notrunc
mv $@.partial $@
$(BUILD)/fw_vars.img: /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd
cp "$<" "$@"
$(BUILD)/firmware.rom: /usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd
cp "$<" "$@"
qemu-acpi: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img
$(QEMU) \
-M virt \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-device virtio-gpu-pci \
-machine virt \
-net none \
-cpu max \
-drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \
-drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \
-drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio
qemu-dtb: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img
$(QEMU) \
-M virt,acpi=off \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-device virtio-gpu-pci \
-machine virt \
-net none \
-cpu max \
-drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \
-drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \
-drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio -s
-65
View File
@@ -1,65 +0,0 @@
export LD?=ld
export OBJCOPY?=objcopy
export PARTED?=parted
export QEMU?=qemu-system-x86_64
all: $(BUILD)/bootloader.bin
$(BUILD)/libbootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target "$(TARGET)" \
--lib \
--release \
-- \
--emit link="$@"
$(BUILD)/libbootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target "$(TARGET)" \
--lib \
--release \
--features live \
-- \
--emit link="$@"
$(BUILD)/%.elf: $(BUILD)/lib%.a $(SOURCE)/linkers/$(TARGET).ld
$(LD) -m elf_i386 --gc-sections -z max-page-size=0x1000 -T "$(SOURCE)/linkers/$(TARGET).ld" -o "$@" "$<"
$(OBJCOPY) --only-keep-debug "$@" "$@.sym"
$(OBJCOPY) --strip-debug "$@"
$(BUILD)/%.bin: $(BUILD)/%.elf $(shell find $(SOURCE)/asm/$(TARGET) -type f)
nasm -f bin -o "$@" -l "$@.lst" -D STAGE3="$<" -i"$(SOURCE)/asm/$(TARGET)/" "$(SOURCE)/asm/$(TARGET)/bootloader.asm"
$(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin
rm -f "$@.partial"
fallocate -l 256MiB "$@.partial"
$(PARTED) -s -a minimal "$@.partial" mklabel msdos
$(PARTED) -s -a minimal "$@.partial" mkpart primary 2MiB 100%
dd if="$<" of="$@.partial" bs=1 count=446 conv=notrunc
dd if="$<" of="$@.partial" bs=512 skip=1 seek=1 conv=notrunc
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc
mv "$@.partial" "$@"
qemu: $(BUILD)/harddrive.bin
$(QEMU) \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-machine q35 \
-net none \
-enable-kvm \
-cpu host \
-drive file="$<",format=raw
-70
View File
@@ -1,70 +0,0 @@
export PARTED?=parted
export QEMU?=qemu-system-x86_64
all: $(BUILD)/bootloader.efi
$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target $(TARGET) \
--bin bootloader \
--release \
-- \
--emit link="$@"
$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p $(BUILD)
cd "$(SOURCE)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target $(TARGET) \
--bin bootloader \
--release \
--features live \
-- \
--emit link="$@"
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
rm -f "$@.partial"
fallocate -l 1MiB $@.partial
mkfs.vfat "$@.partial"
mmd -i "$@.partial" efi
mmd -i "$@.partial" efi/boot
mcopy -i "$@.partial" "$<" ::efi/boot/bootx64.efi
mv "$@.partial" "$@"
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
rm -f "$@.partial"
fallocate -l 320MiB "$@.partial"
$(PARTED) -s -a minimal "$@.partial" mklabel gpt
$(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 2MiB
$(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 2MiB 100%
$(PARTED) -s -a minimal "$@.partial" toggle 1 boot
dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc
mv "$@.partial" "$@"
$(BUILD)/firmware.rom: /usr/share/OVMF/OVMF_CODE.fd
cp "$<" "$@"
qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom
$(QEMU) \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-machine q35 \
-net none \
-enable-kvm \
-cpu host \
-bios "$(BUILD)/firmware.rom" \
-drive file="$<",format=raw
BIN
View File
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "rmm"
version = "0.1.0"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024"
[dependencies]
bitflags = "2"
[features]
std = []
[[bin]]
name = "rmm"
path = "src/main.rs"
required-features = ["std"]
+4
View File
@@ -0,0 +1,4 @@
# Redox Memory Management
This is a Rust crate to provide abstractions for hardware memory management. It
also contains a mechanism for testing memory management with software emulation.
+296
View File
@@ -0,0 +1,296 @@
use core::{marker::PhantomData, mem};
use crate::{
Arch, BumpAllocator, FrameAllocator, FrameCount, FrameUsage, PhysicalAddress, VirtualAddress,
};
#[repr(transparent)]
struct BuddyUsage(u8);
#[repr(C, packed)]
struct BuddyEntry<A> {
base: PhysicalAddress,
size: usize,
// Number of first free page
skip: usize,
// Count of used pages
used: usize,
phantom: PhantomData<A>,
}
impl<A> Clone for BuddyEntry<A> {
fn clone(&self) -> Self {
*self
}
}
impl<A> Copy for BuddyEntry<A> {}
impl<A: Arch> BuddyEntry<A> {
fn empty() -> Self {
Self {
base: PhysicalAddress::new(0),
size: 0,
skip: 0,
used: 0,
phantom: PhantomData,
}
}
#[inline(always)]
fn pages(&self) -> usize {
self.size >> A::PAGE_SHIFT
}
fn usage_pages(&self) -> usize {
let bytes = self.pages() * mem::size_of::<BuddyUsage>();
// Round bytes used for usage to next page
(bytes + A::PAGE_OFFSET_MASK) >> A::PAGE_SHIFT
}
unsafe fn usage_addr(&self, page: usize) -> Option<VirtualAddress> {
if page < self.pages() {
let phys = self.base.add(page * mem::size_of::<BuddyUsage>());
Some(A::phys_to_virt(phys))
} else {
None
}
}
unsafe fn usage(&self, page: usize) -> Option<BuddyUsage> {
unsafe {
let addr = self.usage_addr(page)?;
Some(A::read(addr))
}
}
#[expect(clippy::unit_arg)]
unsafe fn set_usage(&self, page: usize, usage: BuddyUsage) -> Option<()> {
unsafe {
let addr = self.usage_addr(page)?;
Some(A::write(addr, usage))
}
}
}
pub struct BuddyAllocator<A> {
table_virt: VirtualAddress,
phantom: PhantomData<A>,
}
impl<A: Arch> BuddyAllocator<A> {
const BUDDY_ENTRIES: usize = A::PAGE_SIZE / mem::size_of::<BuddyEntry<A>>();
pub unsafe fn new(mut bump_allocator: BumpAllocator<A>) -> Option<Self> {
unsafe {
// Allocate buddy table
let table_phys = bump_allocator.allocate_one()?;
let table_virt = A::phys_to_virt(table_phys);
for i in 0..(A::PAGE_SIZE / mem::size_of::<BuddyEntry<A>>()) {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
A::write(virt, BuddyEntry::<A>::empty());
}
let allocator = Self {
table_virt,
phantom: PhantomData,
};
// Add areas to buddy table, combining areas when possible, and skipping frames used
// by the bump allocator
let mut offset = bump_allocator.offset();
for old_area in bump_allocator.areas().iter() {
let mut area = *old_area;
if offset >= area.size {
offset -= area.size;
continue;
} else if offset > 0 {
area.base = area.base.add(offset);
area.size -= offset;
offset = 0;
}
for i in 0..(A::PAGE_SIZE / mem::size_of::<BuddyEntry<A>>()) {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
let mut entry = A::read::<BuddyEntry<A>>(virt);
let inserted = if area.base.add(area.size) == { entry.base } {
// Combine entry at start
entry.base = area.base;
entry.size += area.size;
true
} else if area.base == entry.base.add(entry.size) {
// Combine entry at end
entry.size += area.size;
true
} else if entry.size == 0 {
// Create new entry
entry.base = area.base;
entry.size = area.size;
true
} else {
false
};
if inserted {
A::write(virt, entry);
break;
}
}
}
//TODO: sort areas?
// Allocate buddy maps
for i in 0..Self::BUDDY_ENTRIES {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
let mut entry = A::read::<BuddyEntry<A>>(virt);
// Only set up entries that have enough space for their own usage map
let usage_pages = entry.usage_pages();
if entry.pages() > usage_pages {
// Mark all usage bytes as unused
let usage_start = entry.usage_addr(0)?;
for page in 0..usage_pages {
A::write_bytes(usage_start.add(page << A::PAGE_SHIFT), 0, A::PAGE_SIZE);
}
// Mark bytes used for usage as used
for page in 0..usage_pages {
entry.set_usage(page, BuddyUsage(1))?;
}
}
// Skip the pages used for usage
entry.skip = usage_pages;
// Set used pages to pages used for usage
entry.used = usage_pages;
// Write updated entry
A::write(virt, entry);
}
Some(allocator)
}
}
}
unsafe impl<A: Arch> FrameAllocator for BuddyAllocator<A> {
fn allocate(&mut self, count: FrameCount) -> Option<PhysicalAddress> {
unsafe {
if self.table_virt.data() == 0 {
return None;
}
for entry_i in 0..Self::BUDDY_ENTRIES {
let virt = self
.table_virt
.add(entry_i * mem::size_of::<BuddyEntry<A>>());
let mut entry = A::read::<BuddyEntry<A>>(virt);
let mut free_page = entry.skip;
let mut free_count = 0;
for page in entry.skip..entry.pages() {
let usage = entry.usage(page)?;
if usage.0 == 0 {
free_count += 1;
if free_count == count.data() {
break;
}
} else {
free_page = page + 1;
free_count = 0;
}
}
if free_count == count.data() {
for page in free_page..free_page + free_count {
// Update usage
let mut usage = entry.usage(page)?;
usage.0 += 1;
entry.set_usage(page, usage);
// Zero page
let page_phys = entry.base.add(page << A::PAGE_SHIFT);
let page_virt = A::phys_to_virt(page_phys);
A::write_bytes(page_virt, 0, A::PAGE_SIZE);
}
// Update skip if necessary
if entry.skip == free_page {
entry.skip = free_page + free_count;
}
// Update used page count
entry.used += free_count;
// Write updated entry
A::write(virt, entry);
return Some(entry.base.add(free_page << A::PAGE_SHIFT));
}
}
None
}
}
unsafe fn free(&mut self, base: PhysicalAddress, count: FrameCount) {
unsafe {
if self.table_virt.data() == 0 {
return;
}
let size = count.data() * A::PAGE_SIZE;
for i in 0..Self::BUDDY_ENTRIES {
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
let mut entry = A::read::<BuddyEntry<A>>(virt);
if base >= { entry.base } && base.add(size) <= entry.base.add(entry.size) {
let start_page = (base.data() - { entry.base }.data()) >> A::PAGE_SHIFT;
for page in start_page..start_page + count.data() {
let mut usage = entry.usage(page).expect("failed to get usage during free");
if usage.0 > 0 {
usage.0 -= 1;
} else {
panic!("tried to free already free frame");
}
// If page was freed
if usage.0 == 0 {
// Update skip if necessary
if page < entry.skip {
entry.skip = page;
}
// Update used page count
entry.used -= 1;
}
entry
.set_usage(page, usage)
.expect("failed to set usage during free");
}
// Write updated entry
A::write(virt, entry);
return;
}
}
}
}
fn usage(&self) -> FrameUsage {
unsafe {
let mut total = 0;
let mut used = 0;
for i in 0..Self::BUDDY_ENTRIES {
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry<A>>());
let entry = A::read::<BuddyEntry<A>>(virt);
total += entry.size >> A::PAGE_SHIFT;
used += entry.used;
}
FrameUsage::new(FrameCount::new(used), FrameCount::new(total))
}
}
}
+79
View File
@@ -0,0 +1,79 @@
use core::marker::PhantomData;
use crate::{Arch, FrameAllocator, FrameCount, FrameUsage, MemoryArea, PhysicalAddress};
#[derive(Debug)]
pub struct BumpAllocator<A> {
orig_areas: (&'static [MemoryArea], usize),
cur_areas: (&'static [MemoryArea], usize),
_marker: PhantomData<fn() -> A>,
}
impl<A: Arch> BumpAllocator<A> {
pub fn new(mut areas: &'static [MemoryArea], mut offset: usize) -> Self {
while let Some(first) = areas.first()
&& first.size <= offset
{
offset -= first.size;
areas = &areas[1..];
}
Self {
orig_areas: (areas, offset),
cur_areas: (areas, offset),
_marker: PhantomData,
}
}
pub fn areas(&self) -> &'static [MemoryArea] {
self.orig_areas.0
}
/// Returns one semifree and the fully free areas. The offset is the number of bytes after
/// which the first area is free.
pub fn free_areas(&self) -> (&'static [MemoryArea], usize) {
self.cur_areas
}
pub fn abs_offset(&self) -> PhysicalAddress {
let (areas, off) = self.cur_areas;
areas
.first()
.map_or(PhysicalAddress::new(0), |a| a.base.add(off))
}
pub fn offset(&self) -> usize {
(self.usage().total().data() - self.usage().free().data()) * A::PAGE_SIZE
}
}
unsafe impl<A: Arch> FrameAllocator for BumpAllocator<A> {
fn allocate(&mut self, count: FrameCount) -> Option<PhysicalAddress> {
unsafe {
let req_size = count.data() * A::PAGE_SIZE;
let block = loop {
let area = self.cur_areas.0.first()?;
let off = self.cur_areas.1;
if area.size - off < req_size {
self.cur_areas = (&self.cur_areas.0[1..], 0);
continue;
}
self.cur_areas.1 += req_size;
break area.base.add(off);
};
A::write_bytes(A::phys_to_virt(block), 0, req_size);
Some(block)
}
}
unsafe fn free(&mut self, _address: PhysicalAddress, _count: FrameCount) {
unimplemented!("BumpAllocator::free not implemented");
}
fn usage(&self) -> FrameUsage {
let total = self.orig_areas.0.iter().map(|a| a.size).sum::<usize>() - self.orig_areas.1;
let free = self.cur_areas.0.iter().map(|a| a.size).sum::<usize>() - self.cur_areas.1;
FrameUsage::new(
FrameCount::new((total - free) / A::PAGE_SIZE),
FrameCount::new(total / A::PAGE_SIZE),
)
}
}
+83
View File
@@ -0,0 +1,83 @@
use crate::PhysicalAddress;
pub use self::{buddy::*, bump::*};
mod buddy;
mod bump;
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct FrameCount(usize);
impl FrameCount {
pub fn new(count: usize) -> Self {
Self(count)
}
pub fn data(&self) -> usize {
self.0
}
}
#[derive(Debug)]
pub struct FrameUsage {
used: FrameCount,
total: FrameCount,
}
impl FrameUsage {
pub fn new(used: FrameCount, total: FrameCount) -> Self {
Self { used, total }
}
pub fn used(&self) -> FrameCount {
self.used
}
pub fn free(&self) -> FrameCount {
FrameCount(self.total.0 - self.used.0)
}
pub fn total(&self) -> FrameCount {
self.total
}
}
pub unsafe trait FrameAllocator {
fn allocate(&mut self, count: FrameCount) -> Option<PhysicalAddress>;
unsafe fn free(&mut self, address: PhysicalAddress, count: FrameCount);
fn allocate_one(&mut self) -> Option<PhysicalAddress> {
self.allocate(FrameCount::new(1))
}
unsafe fn free_one(&mut self, address: PhysicalAddress) {
unsafe {
self.free(address, FrameCount::new(1));
}
}
fn usage(&self) -> FrameUsage;
}
unsafe impl<T> FrameAllocator for &mut T
where
T: FrameAllocator,
{
fn allocate(&mut self, count: FrameCount) -> Option<PhysicalAddress> {
T::allocate(self, count)
}
unsafe fn free(&mut self, address: PhysicalAddress, count: FrameCount) {
unsafe { T::free(self, address, count) }
}
fn allocate_one(&mut self) -> Option<PhysicalAddress> {
T::allocate_one(self)
}
unsafe fn free_one(&mut self, address: PhysicalAddress) {
unsafe { T::free_one(self, address) }
}
fn usage(&self) -> FrameUsage {
T::usage(self)
}
}
+3
View File
@@ -0,0 +1,3 @@
pub use self::frame::*;
mod frame;
+153
View File
@@ -0,0 +1,153 @@
use core::arch::asm;
use crate::{Arch, PhysicalAddress, TableKind, VirtualAddress};
#[derive(Clone, Copy)]
pub struct AArch64Arch;
impl Arch for AArch64Arch {
const KERNEL_SEPARATE_TABLE: bool = true;
const PAGE_SHIFT: usize = 12; // 4096 bytes
const PAGE_ENTRY_SHIFT: usize = 9; // 512 entries, 8 bytes each
const PAGE_LEVELS: usize = 4; // L0, L1, L2, L3
//TODO
const ENTRY_ADDRESS_WIDTH: usize = 40;
const ENTRY_FLAG_DEFAULT_PAGE: usize = Self::ENTRY_FLAG_PRESENT
| 1 << 1 // Page flag
| 1 << 10 // Access flag
| Self::ENTRY_FLAG_NO_GLOBAL;
const ENTRY_FLAG_DEFAULT_TABLE: usize
= Self::ENTRY_FLAG_PRESENT
| Self::ENTRY_FLAG_READWRITE
| 1 << 1 // Table flag
| 1 << 10 // Access flag
;
const ENTRY_FLAG_PRESENT: usize = 1 << 0;
const ENTRY_FLAG_READONLY: usize = 1 << 7;
const ENTRY_FLAG_READWRITE: usize = 0;
const ENTRY_FLAG_PAGE_USER: usize = 1 << 6;
// This sets both userspace and privileged execute never
//TODO: Separate the two?
const ENTRY_FLAG_NO_EXEC: usize = 0b11 << 53;
const ENTRY_FLAG_EXEC: usize = 0;
const ENTRY_FLAG_GLOBAL: usize = 0;
const ENTRY_FLAG_NO_GLOBAL: usize = 1 << 11;
const ENTRY_FLAG_DEVICE_MEMORY: usize = MEM_ATTR_DEVICE_nGnRnE << 2;
const ENTRY_FLAG_UNCACHEABLE: usize = MEM_ATTR_NC << 2;
const ENTRY_FLAG_WRITE_COMBINING: usize = MEM_ATTR_NC << 2;
const PHYS_OFFSET: usize = 0xFFFF_8000_0000_0000;
#[inline(always)]
fn invalidate(address: VirtualAddress) {
unsafe {
asm!("
dsb ishst
tlbi vaae1is, {}
dsb ish
isb
", in(reg) (address.data() >> Self::PAGE_SHIFT));
}
}
#[inline(always)]
fn invalidate_all() {
unsafe {
asm!(
"
dsb ishst
tlbi vmalle1is
dsb ish
isb
"
);
}
}
#[inline(always)]
fn table(table_kind: TableKind) -> PhysicalAddress {
let address: usize;
match table_kind {
TableKind::User => {
unsafe { asm!("mrs {0}, ttbr0_el1", out(reg) address) };
}
TableKind::Kernel => {
unsafe { asm!("mrs {0}, ttbr1_el1", out(reg) address) };
}
}
PhysicalAddress::new(address)
}
#[inline(always)]
unsafe fn set_table(table_kind: TableKind, address: PhysicalAddress) {
unsafe {
match table_kind {
TableKind::User => {
asm!("msr ttbr0_el1, {0}", in(reg) address.data());
}
TableKind::Kernel => {
asm!("msr ttbr1_el1, {0}", in(reg) address.data());
}
}
Self::invalidate_all();
}
}
fn virt_is_valid(_address: VirtualAddress) -> bool {
//TODO: what makes an address valid on aarch64?
true
}
}
#[cfg_attr(not(target_arch = "aarch64"), allow(unused))]
const MEM_ATTR_WB: usize = 0;
const MEM_ATTR_NC: usize = 1;
#[allow(non_upper_case_globals)]
const MEM_ATTR_DEVICE_nGnRnE: usize = 2;
/// Setup Memory Access Indirection Register
#[cfg(target_arch = "aarch64")]
#[inline(always)]
pub unsafe fn init_mair() {
// https://github.com/freebsd/freebsd-src/blob/d15733065c4221dcd5bb3622d225760f271f6fc9/sys/arm64/include/armreg.h#L1986-L1991
const fn mair_attr(attr: u64, idx: usize) -> u64 {
attr << (idx * 8)
}
#[allow(non_upper_case_globals)]
const MAIR_DEVICE_nGnRnE: u64 = 0x00;
#[allow(non_upper_case_globals)]
const _MAIR_DEVICE_nGnRE: u64 = 0x04;
const MAIR_NORMAL_NC: u64 = 0x44;
const _MAIR_NORMAL_WT: u64 = 0xbb;
const MAIR_NORMAL_WB: u64 = 0xff;
unsafe {
let val: u64 = const {
mair_attr(MAIR_DEVICE_nGnRnE, MEM_ATTR_DEVICE_nGnRnE)
| mair_attr(MAIR_NORMAL_NC, MEM_ATTR_NC)
| mair_attr(MAIR_NORMAL_WB, MEM_ATTR_WB)
};
asm!("msr mair_el1, {}", in(reg) val);
}
}
const _: () = {
assert!(AArch64Arch::PAGE_SIZE == 4096);
assert!(AArch64Arch::PAGE_OFFSET_MASK == 0xFFF);
assert!(AArch64Arch::PAGE_ADDRESS_SHIFT == 48);
assert!(AArch64Arch::PAGE_ADDRESS_SIZE == 0x0001_0000_0000_0000);
assert!(AArch64Arch::PAGE_ADDRESS_MASK == 0x0000_FFFF_FFFF_F000);
assert!(AArch64Arch::PAGE_ENTRY_SIZE == 8);
assert!(AArch64Arch::PAGE_ENTRIES == 512);
assert!(AArch64Arch::PAGE_ENTRY_MASK == 0x1FF);
assert!(AArch64Arch::PAGE_NEGATIVE_MASK == 0xFFFF_0000_0000_0000);
assert!(AArch64Arch::ENTRY_ADDRESS_SIZE == 0x0000_0100_0000_0000);
assert!(AArch64Arch::ENTRY_ADDRESS_MASK == 0x0000_00FF_FFFF_FFFF);
assert!(AArch64Arch::ENTRY_FLAGS_MASK == 0xFFF0_0000_0000_0FFF);
assert!(AArch64Arch::PHYS_OFFSET == 0xFFFF_8000_0000_0000);
};
+355
View File
@@ -0,0 +1,355 @@
extern crate std;
use std::{boxed::Box, collections::BTreeMap, marker::PhantomData, mem, ptr, sync::Mutex, vec};
use crate::{
arch::x86_64::X8664Arch, page::PageFlags, Arch, MemoryArea, PageEntry, PhysicalAddress,
TableKind, VirtualAddress, MEGABYTE,
};
#[derive(Clone, Copy)]
pub struct EmulateArch;
impl EmulateArch {
pub unsafe fn init() -> &'static [MemoryArea] {
unsafe {
// Create machine with PAGE_ENTRIES pages offset mapped (2 MiB on x86_64)
let mut machine = Machine::new(MEMORY_SIZE);
// PML4 index 256 (PHYS_OFFSET) link to PDP
let pml4 = 0;
let pdp = pml4 + Self::PAGE_SIZE;
let flags = Self::ENTRY_FLAG_READWRITE | Self::ENTRY_FLAG_PRESENT;
machine.write_phys::<usize>(
PhysicalAddress::new(pml4 + 256 * Self::PAGE_ENTRY_SIZE),
pdp | flags,
);
// PDP link to PD
let pd = pdp + Self::PAGE_SIZE;
machine.write_phys::<usize>(PhysicalAddress::new(pdp), pd | flags);
// PD link to PT
let pt = pd + Self::PAGE_SIZE;
machine.write_phys::<usize>(PhysicalAddress::new(pd), pt | flags);
// PT links to frames
for i in 0..Self::PAGE_ENTRIES {
let page = i * Self::PAGE_SIZE;
machine.write_phys::<usize>(
PhysicalAddress::new(pt + i * Self::PAGE_ENTRY_SIZE),
page | flags,
);
}
*MACHINE.lock().unwrap() = Some(machine);
// Set table to pml4
EmulateArch::set_table(TableKind::Kernel, PhysicalAddress::new(pml4));
&MEMORY_AREAS
}
}
}
impl Arch for EmulateArch {
const KERNEL_SEPARATE_TABLE: bool = false;
const PAGE_SHIFT: usize = X8664Arch::PAGE_SHIFT;
const PAGE_ENTRY_SHIFT: usize = X8664Arch::PAGE_ENTRY_SHIFT;
const PAGE_LEVELS: usize = X8664Arch::PAGE_LEVELS;
const ENTRY_ADDRESS_SHIFT: usize = X8664Arch::ENTRY_ADDRESS_SHIFT;
const ENTRY_FLAG_DEFAULT_PAGE: usize = X8664Arch::ENTRY_FLAG_DEFAULT_PAGE;
const ENTRY_FLAG_DEFAULT_TABLE: usize = X8664Arch::ENTRY_FLAG_DEFAULT_TABLE;
const ENTRY_FLAG_PRESENT: usize = X8664Arch::ENTRY_FLAG_PRESENT;
const ENTRY_FLAG_READONLY: usize = X8664Arch::ENTRY_FLAG_READONLY;
const ENTRY_FLAG_READWRITE: usize = X8664Arch::ENTRY_FLAG_READWRITE;
const ENTRY_FLAG_PAGE_USER: usize = X8664Arch::ENTRY_FLAG_PAGE_USER;
const ENTRY_FLAG_NO_EXEC: usize = X8664Arch::ENTRY_FLAG_NO_EXEC;
const ENTRY_FLAG_EXEC: usize = X8664Arch::ENTRY_FLAG_EXEC;
const PHYS_OFFSET: usize = X8664Arch::PHYS_OFFSET;
const ENTRY_FLAG_GLOBAL: usize = X8664Arch::ENTRY_FLAG_GLOBAL;
const ENTRY_FLAG_NO_GLOBAL: usize = X8664Arch::ENTRY_FLAG_NO_GLOBAL;
const ENTRY_ADDRESS_WIDTH: usize = X8664Arch::ENTRY_ADDRESS_WIDTH;
const ENTRY_FLAG_DEVICE_MEMORY: usize = X8664Arch::ENTRY_FLAG_DEVICE_MEMORY;
const ENTRY_FLAG_UNCACHEABLE: usize = X8664Arch::ENTRY_FLAG_UNCACHEABLE;
const ENTRY_FLAG_WRITE_COMBINING: usize = X8664Arch::ENTRY_FLAG_WRITE_COMBINING;
#[inline(always)]
unsafe fn read<T>(address: VirtualAddress) -> T {
MACHINE.lock().unwrap().as_ref().unwrap().read(address)
}
#[inline(always)]
unsafe fn write<T>(address: VirtualAddress, value: T) {
MACHINE
.lock()
.unwrap()
.as_mut()
.unwrap()
.write(address, value)
}
#[inline(always)]
unsafe fn write_bytes(address: VirtualAddress, value: u8, count: usize) {
MACHINE
.lock()
.unwrap()
.as_mut()
.unwrap()
.write_bytes(address, value, count)
}
#[inline(always)]
fn invalidate(address: VirtualAddress) {
MACHINE
.lock()
.unwrap()
.as_mut()
.unwrap()
.invalidate(address);
}
#[inline(always)]
fn invalidate_all() {
MACHINE.lock().unwrap().as_mut().unwrap().invalidate_all();
}
#[inline(always)]
fn table(_table_kind: TableKind) -> PhysicalAddress {
MACHINE.lock().unwrap().as_mut().unwrap().get_table()
}
#[inline(always)]
unsafe fn set_table(_table_kind: TableKind, address: PhysicalAddress) {
MACHINE.lock().unwrap().as_mut().unwrap().set_table(address);
}
fn virt_is_valid(_address: VirtualAddress) -> bool {
// TODO: Don't see why an emulated arch would have any problems with canonicalness...
true
}
}
const MEMORY_SIZE: usize = 64 * MEGABYTE;
static MEMORY_AREAS: [MemoryArea; 2] = [
MemoryArea {
base: PhysicalAddress::new(EmulateArch::PAGE_SIZE * 4), // Initial PML4, PDP, PD, and PT wasted
size: MEMORY_SIZE / 2 - EmulateArch::PAGE_SIZE * 4,
},
// Second area for debugging
MemoryArea {
base: PhysicalAddress::new(MEMORY_SIZE / 2),
size: MEMORY_SIZE / 2,
},
];
static MACHINE: Mutex<Option<Machine<EmulateArch>>> = Mutex::new(None);
struct Machine<A> {
memory: Box<[u8]>,
map: BTreeMap<VirtualAddress, PageEntry<A>>,
table_addr: PhysicalAddress,
phantom: PhantomData<A>,
}
impl<A: Arch> Machine<A> {
fn new(memory_size: usize) -> Self {
Self {
memory: vec![0; memory_size].into_boxed_slice(),
map: BTreeMap::new(),
table_addr: PhysicalAddress::new(0),
phantom: PhantomData,
}
}
fn read_phys<T>(&self, phys: PhysicalAddress) -> T {
let size = mem::size_of::<T>();
if phys.add(size).data() <= self.memory.len() {
unsafe { ptr::read(self.memory.as_ptr().add(phys.data()) as *const T) }
} else {
panic!(
"read_phys: 0x{:X} size 0x{:X} outside of memory",
phys.data(),
size
);
}
}
fn write_phys<T>(&mut self, phys: PhysicalAddress, value: T) {
let size = mem::size_of::<T>();
if phys.add(size).data() <= self.memory.len() {
unsafe {
ptr::write(self.memory.as_mut_ptr().add(phys.data()) as *mut T, value);
}
} else {
panic!(
"write_phys: 0x{:X} size 0x{:X} outside of memory",
phys.data(),
size
);
}
}
fn write_phys_bytes(&mut self, phys: PhysicalAddress, value: u8, count: usize) {
if phys.add(count).data() <= self.memory.len() {
unsafe {
ptr::write_bytes(self.memory.as_mut_ptr().add(phys.data()), value, count);
}
} else {
panic!(
"write_phys_bytes: 0x{:X} count 0x{:X} outside of memory",
phys.data(),
count
);
}
}
fn translate(&self, virt: VirtualAddress) -> Option<(PhysicalAddress, PageFlags<A>)> {
let virt_data = virt.data();
let page = virt_data & A::PAGE_ADDRESS_MASK;
let offset = virt_data & A::PAGE_OFFSET_MASK;
let entry = self.map.get(&VirtualAddress::new(page))?;
Some((entry.address().ok()?.add(offset), entry.flags()))
}
fn read<T>(&self, virt: VirtualAddress) -> T {
//TODO: allow reading past page boundaries
let virt_data = virt.data();
let size = mem::size_of::<T>();
if (virt_data & A::PAGE_ADDRESS_MASK) != ((virt_data + (size - 1)) & A::PAGE_ADDRESS_MASK) {
panic!(
"read: 0x{:X} size 0x{:X} passes page boundary",
virt_data, size
);
}
if let Some((phys, _flags)) = self.translate(virt) {
self.read_phys(phys)
} else {
panic!("read: 0x{:X} size 0x{:X} not present", virt_data, size);
}
}
fn write<T>(&mut self, virt: VirtualAddress, value: T) {
//TODO: allow writing past page boundaries
let virt_data = virt.data();
let size = mem::size_of::<T>();
if (virt_data & A::PAGE_ADDRESS_MASK) != ((virt_data + (size - 1)) & A::PAGE_ADDRESS_MASK) {
panic!(
"write: 0x{:X} size 0x{:X} passes page boundary",
virt_data, size
);
}
if let Some((phys, flags)) = self.translate(virt) {
if flags.has_write() {
self.write_phys(phys, value);
} else {
panic!("write: 0x{:X} size 0x{:X} not writable", virt_data, size);
}
} else {
panic!("write: 0x{:X} size 0x{:X} not present", virt_data, size);
}
}
fn write_bytes(&mut self, virt: VirtualAddress, value: u8, count: usize) {
//TODO: allow writing past page boundaries
let virt_data = virt.data();
if (virt_data & A::PAGE_ADDRESS_MASK) != ((virt_data + (count - 1)) & A::PAGE_ADDRESS_MASK)
{
panic!(
"write_bytes: 0x{:X} count 0x{:X} passes page boundary",
virt_data, count
);
}
if let Some((phys, flags)) = self.translate(virt) {
if flags.has_write() {
self.write_phys_bytes(phys, value, count);
} else {
panic!(
"write_bytes: 0x{:X} count 0x{:X} not writable",
virt_data, count
);
}
} else {
panic!(
"write_bytes: 0x{:X} count 0x{:X} not present",
virt_data, count
);
}
}
fn invalidate(&mut self, _address: VirtualAddress) {
unimplemented!("EmulateArch::invalidate not implemented");
}
//TODO: cleanup
fn invalidate_all(&mut self) {
self.map.clear();
// PML4
let a4 = self.table_addr.data();
for i4 in 0..A::PAGE_ENTRIES {
let e3 = self.read_phys::<usize>(PhysicalAddress::new(a4 + i4 * A::PAGE_ENTRY_SIZE));
let f3 = e3 & A::ENTRY_FLAGS_MASK;
if f3 & A::ENTRY_FLAG_PRESENT == 0 {
continue;
}
// Page directory pointer
let a3 = ((e3 >> A::ENTRY_ADDRESS_SHIFT) & A::ENTRY_ADDRESS_MASK) << A::PAGE_SHIFT;
for i3 in 0..A::PAGE_ENTRIES {
let e2 =
self.read_phys::<usize>(PhysicalAddress::new(a3 + i3 * A::PAGE_ENTRY_SIZE));
let f2 = e2 & A::ENTRY_FLAGS_MASK;
if f2 & A::ENTRY_FLAG_PRESENT == 0 {
continue;
}
// Page directory
let a2 = ((e2 >> A::ENTRY_ADDRESS_SHIFT) & A::ENTRY_ADDRESS_MASK) << A::PAGE_SHIFT;
for i2 in 0..A::PAGE_ENTRIES {
let e1 =
self.read_phys::<usize>(PhysicalAddress::new(a2 + i2 * A::PAGE_ENTRY_SIZE));
let f1 = e1 & A::ENTRY_FLAGS_MASK;
if f1 & A::ENTRY_FLAG_PRESENT == 0 {
continue;
}
// Page table
let a1 =
((e1 >> A::ENTRY_ADDRESS_SHIFT) & A::ENTRY_ADDRESS_MASK) << A::PAGE_SHIFT;
for i1 in 0..A::PAGE_ENTRIES {
let e = self
.read_phys::<usize>(PhysicalAddress::new(a1 + i1 * A::PAGE_ENTRY_SIZE));
let f = e & A::ENTRY_FLAGS_MASK;
if f & A::ENTRY_FLAG_PRESENT == 0 {
continue;
}
// Page
let page = (i4 << 39) | (i3 << 30) | (i2 << 21) | (i1 << 12);
//println!("map 0x{:X} to 0x{:X}, 0x{:X}", page, a, f);
self.map
.insert(VirtualAddress::new(page), PageEntry::from_data(e));
}
}
}
}
}
fn get_table(&self) -> PhysicalAddress {
self.table_addr
}
fn set_table(&mut self, address: PhysicalAddress) {
self.table_addr = address;
self.invalidate_all();
}
}
+93
View File
@@ -0,0 +1,93 @@
use core::ptr;
use crate::{PhysicalAddress, TableKind, VirtualAddress};
//TODO: Support having all page tables compile on all architectures
#[cfg(target_pointer_width = "64")]
pub mod aarch64;
#[cfg(all(feature = "std", target_pointer_width = "64"))]
pub mod emulate;
#[cfg(target_pointer_width = "64")]
pub mod riscv64;
#[cfg(target_pointer_width = "32")]
pub mod x86;
#[cfg(target_pointer_width = "64")]
pub mod x86_64;
mod x86_shared;
pub trait Arch: Clone + Copy {
/// Does the architecture use a separate page table for the kernel.
///
/// If false, the page table entries corresponding to the top half of the
/// address space will be copied into the top level of every page table
/// and will never be unmapped when unmapping pages.
const KERNEL_SEPARATE_TABLE: bool;
const PAGE_SHIFT: usize;
const PAGE_ENTRY_SHIFT: usize;
const PAGE_LEVELS: usize;
const ENTRY_ADDRESS_WIDTH: usize; // Number of bits of physical address in PTE
const ENTRY_ADDRESS_SHIFT: usize = Self::PAGE_SHIFT; // Offset of physical address in PTE
const ENTRY_FLAG_DEFAULT_PAGE: usize;
const ENTRY_FLAG_DEFAULT_TABLE: usize;
const ENTRY_FLAG_PRESENT: usize;
const ENTRY_FLAG_READONLY: usize;
const ENTRY_FLAG_READWRITE: usize;
const ENTRY_FLAG_PAGE_USER: usize; // Leaf table user page flag
const ENTRY_FLAG_TABLE_USER: usize = Self::ENTRY_FLAG_PAGE_USER; // Directory user page table flag
const ENTRY_FLAG_NO_EXEC: usize;
const ENTRY_FLAG_EXEC: usize;
const ENTRY_FLAG_GLOBAL: usize;
const ENTRY_FLAG_NO_GLOBAL: usize;
const ENTRY_FLAG_DEVICE_MEMORY: usize;
const ENTRY_FLAG_UNCACHEABLE: usize;
const ENTRY_FLAG_WRITE_COMBINING: usize;
const PHYS_OFFSET: usize;
const PAGE_SIZE: usize = 1 << Self::PAGE_SHIFT;
const PAGE_OFFSET_MASK: usize = Self::PAGE_SIZE - 1;
const PAGE_ADDRESS_SHIFT: usize = Self::PAGE_LEVELS * Self::PAGE_ENTRY_SHIFT + Self::PAGE_SHIFT;
const PAGE_ADDRESS_SIZE: u64 = 1 << (Self::PAGE_ADDRESS_SHIFT as u64);
const PAGE_ADDRESS_MASK: usize = (Self::PAGE_ADDRESS_SIZE - (Self::PAGE_SIZE as u64)) as usize;
const PAGE_ENTRY_SIZE: usize = 1 << (Self::PAGE_SHIFT - Self::PAGE_ENTRY_SHIFT);
const PAGE_ENTRIES: usize = 1 << Self::PAGE_ENTRY_SHIFT;
const PAGE_ENTRY_MASK: usize = Self::PAGE_ENTRIES - 1;
const PAGE_NEGATIVE_MASK: usize = !(Self::PAGE_ADDRESS_SIZE - 1) as usize;
const ENTRY_ADDRESS_SIZE: usize = 1 << Self::ENTRY_ADDRESS_WIDTH; // size of addressable physical memory, in pages
const ENTRY_ADDRESS_MASK: usize = Self::ENTRY_ADDRESS_SIZE - 1; // Mask of physical address, starting at 0th bit
const ENTRY_FLAGS_MASK: usize = !(Self::ENTRY_ADDRESS_MASK << Self::ENTRY_ADDRESS_SHIFT);
#[inline(always)]
unsafe fn read<T>(address: VirtualAddress) -> T {
unsafe { ptr::read(address.data() as *const T) }
}
#[inline(always)]
unsafe fn write<T>(address: VirtualAddress, value: T) {
unsafe { ptr::write(address.data() as *mut T, value) }
}
#[inline(always)]
unsafe fn write_bytes(address: VirtualAddress, value: u8, count: usize) {
unsafe { ptr::write_bytes(address.data() as *mut u8, value, count) }
}
fn invalidate(address: VirtualAddress);
fn invalidate_all();
fn table(table_kind: TableKind) -> PhysicalAddress;
unsafe fn set_table(table_kind: TableKind, address: PhysicalAddress);
#[inline(always)]
fn phys_to_virt(phys: PhysicalAddress) -> VirtualAddress {
match phys.data().checked_add(Self::PHYS_OFFSET) {
Some(some) => VirtualAddress::new(some),
None => panic!("phys_to_virt({:#x}) overflow", phys.data()),
}
}
fn virt_is_valid(address: VirtualAddress) -> bool;
}
+7
View File
@@ -0,0 +1,7 @@
pub use sv39::RiscV64Sv39Arch;
pub use sv48::RiscV64Sv48Arch;
pub use sv57::RiscV64Sv57Arch;
mod sv39;
mod sv48;
mod sv57;
+124
View File
@@ -0,0 +1,124 @@
use core::arch::asm;
use crate::{Arch, PhysicalAddress, TableKind, VirtualAddress};
#[derive(Clone, Copy)]
pub struct RiscV64Sv39Arch;
pub const ACCESSED: usize = 1 << 6;
pub const DIRTY: usize = 1 << 7;
impl Arch for RiscV64Sv39Arch {
const KERNEL_SEPARATE_TABLE: bool = false;
const PAGE_SHIFT: usize = 12; // 4096 bytes
const PAGE_ENTRY_SHIFT: usize = 9; // 512 entries, 8 bytes each
const PAGE_LEVELS: usize = 3; // L0, L1, L2
const ENTRY_ADDRESS_WIDTH: usize = 44;
const ENTRY_ADDRESS_SHIFT: usize = 10;
const ENTRY_FLAG_DEFAULT_PAGE: usize =
Self::ENTRY_FLAG_PRESENT | Self::ENTRY_FLAG_READONLY | ACCESSED | DIRTY;
const ENTRY_FLAG_DEFAULT_TABLE: usize = Self::ENTRY_FLAG_PRESENT;
const ENTRY_FLAG_PRESENT: usize = 1 << 0;
const ENTRY_FLAG_READONLY: usize = 1 << 1;
const ENTRY_FLAG_READWRITE: usize = 3 << 1;
const ENTRY_FLAG_PAGE_USER: usize = 1 << 4;
const ENTRY_FLAG_TABLE_USER: usize = 0;
const ENTRY_FLAG_NO_EXEC: usize = 0;
const ENTRY_FLAG_EXEC: usize = 1 << 3;
const ENTRY_FLAG_GLOBAL: usize = 1 << 5;
const ENTRY_FLAG_NO_GLOBAL: usize = 0;
const ENTRY_FLAG_DEVICE_MEMORY: usize = 0; // FIXME use Svpbmt
const ENTRY_FLAG_UNCACHEABLE: usize = 0; // FIXME use Svpbmt
const ENTRY_FLAG_WRITE_COMBINING: usize = 0; // FIXME use Svpbmt
const PHYS_OFFSET: usize = 0xFFFF_FFC0_0000_0000;
#[inline(always)]
fn invalidate(address: VirtualAddress) {
unsafe { asm!("sfence.vma {}", in(reg) address.data()) };
}
#[inline(always)]
fn invalidate_all() {
unsafe { asm!("sfence.vma") };
}
#[inline(always)]
fn table(_table_kind: TableKind) -> PhysicalAddress {
let satp: usize;
unsafe { asm!("csrr {0}, satp", out(reg) satp) };
PhysicalAddress::new(
(satp & Self::ENTRY_ADDRESS_MASK) << Self::PAGE_SHIFT, // Convert from PPN
)
}
#[inline(always)]
unsafe fn set_table(_table_kind: TableKind, address: PhysicalAddress) {
let satp = (8 << 60) | // Sv39 MODE
(address.data() >> Self::PAGE_SHIFT); // Convert to PPN (TODO: ensure alignment)
unsafe {
asm!("csrw satp, {0}", in(reg) satp);
Self::invalidate_all();
}
}
fn virt_is_valid(address: VirtualAddress) -> bool {
let mask = !((Self::PAGE_ADDRESS_SIZE as usize - 1) >> 1);
let masked = address.data() & mask;
masked == mask || masked == 0
}
}
const _: () = {
assert!(RiscV64Sv39Arch::PAGE_SIZE == 4096);
assert!(RiscV64Sv39Arch::PAGE_OFFSET_MASK == 0xFFF);
assert!(RiscV64Sv39Arch::PAGE_ADDRESS_SHIFT == 39);
assert!(RiscV64Sv39Arch::PAGE_ADDRESS_SIZE == 0x0000_0080_0000_0000);
assert!(RiscV64Sv39Arch::PAGE_ADDRESS_MASK == 0x0000_007F_FFFF_F000);
assert!(RiscV64Sv39Arch::PAGE_ENTRY_SIZE == 8);
assert!(RiscV64Sv39Arch::PAGE_ENTRIES == 512);
assert!(RiscV64Sv39Arch::PAGE_ENTRY_MASK == 0x1FF);
assert!(RiscV64Sv39Arch::PAGE_NEGATIVE_MASK == 0xFFFF_FF80_0000_0000);
assert!(RiscV64Sv39Arch::ENTRY_ADDRESS_SIZE == 0x0000_1000_0000_0000);
assert!(RiscV64Sv39Arch::ENTRY_ADDRESS_MASK == 0x0000_0FFF_FFFF_FFFF);
assert!(RiscV64Sv39Arch::ENTRY_FLAGS_MASK == 0xFFC0_0000_0000_03FF);
assert!(RiscV64Sv39Arch::PHYS_OFFSET == 0xFFFF_FFC0_0000_0000);
};
#[cfg(test)]
mod tests {
use super::RiscV64Sv39Arch;
use crate::Arch;
#[test]
fn is_canonical() {
use super::VirtualAddress;
#[track_caller]
fn yes(addr: usize) {
assert!(RiscV64Sv39Arch::virt_is_valid(VirtualAddress::new(addr)));
}
#[track_caller]
fn no(addr: usize) {
assert!(!RiscV64Sv39Arch::virt_is_valid(VirtualAddress::new(addr)));
}
yes(0xFFFF_FFFF_FFFF_FFFF);
yes(0xFFFF_FFF0_1337_1337);
no(0x0000_0F00_0000_0000);
no(0x1337_0000_0000_0000);
no(1 << 38);
yes(1 << 37);
// Check for off-by-one errors.
yes(0xFFFF_FFC0_0000_0000 | (1 << 37));
yes(0xFFFF_FFE0_0000_0000 | (1 << 37));
no(0xFFFF_FF80_0000_0000 | (1 << 37));
}
}
+118
View File
@@ -0,0 +1,118 @@
use core::arch::asm;
use crate::{Arch, PhysicalAddress, TableKind, VirtualAddress};
#[derive(Clone, Copy)]
pub struct RiscV64Sv48Arch;
impl Arch for RiscV64Sv48Arch {
const KERNEL_SEPARATE_TABLE: bool = false;
const PAGE_SHIFT: usize = 12; // 4096 bytes
const PAGE_ENTRY_SHIFT: usize = 9; // 512 entries, 8 bytes each
const PAGE_LEVELS: usize = 4; // L0, L1, L2, L3
const ENTRY_ADDRESS_WIDTH: usize = 44;
const ENTRY_ADDRESS_SHIFT: usize = 10;
const ENTRY_FLAG_DEFAULT_PAGE: usize = Self::ENTRY_FLAG_PRESENT | Self::ENTRY_FLAG_READONLY;
const ENTRY_FLAG_DEFAULT_TABLE: usize = Self::ENTRY_FLAG_PRESENT;
const ENTRY_FLAG_PRESENT: usize = 1 << 0;
const ENTRY_FLAG_READONLY: usize = 1 << 1;
const ENTRY_FLAG_READWRITE: usize = 3 << 1;
const ENTRY_FLAG_PAGE_USER: usize = 1 << 4;
const ENTRY_FLAG_TABLE_USER: usize = 0;
const ENTRY_FLAG_NO_EXEC: usize = 0;
const ENTRY_FLAG_EXEC: usize = 1 << 3;
const ENTRY_FLAG_GLOBAL: usize = 1 << 5;
const ENTRY_FLAG_NO_GLOBAL: usize = 0;
const ENTRY_FLAG_DEVICE_MEMORY: usize = 0; // FIXME use Svpbmt
const ENTRY_FLAG_UNCACHEABLE: usize = 0; // FIXME use Svpbmt
const ENTRY_FLAG_WRITE_COMBINING: usize = 0; // FIXME use Svpbmt
const PHYS_OFFSET: usize = 0xFFFF_8000_0000_0000;
#[inline(always)]
fn invalidate(address: VirtualAddress) {
unsafe { asm!("sfence.vma {}", in(reg) address.data()) };
}
#[inline(always)]
fn invalidate_all() {
unsafe { asm!("sfence.vma") };
}
#[inline(always)]
fn table(_table_kind: TableKind) -> PhysicalAddress {
let satp: usize;
unsafe { asm!("csrr {0}, satp", out(reg) satp) };
PhysicalAddress::new(
(satp & Self::ENTRY_ADDRESS_MASK) << Self::PAGE_SHIFT, // Convert from PPN
)
}
#[inline(always)]
unsafe fn set_table(_table_kind: TableKind, address: PhysicalAddress) {
let satp = (9 << 60) | // Sv48 MODE
(address.data() >> Self::PAGE_SHIFT); // Convert to PPN (TODO: ensure alignment)
unsafe {
asm!("csrw satp, {0}", in(reg) satp);
Self::invalidate_all();
}
}
fn virt_is_valid(address: VirtualAddress) -> bool {
// RISC-V SV48 uses 48-bit sign-extended addresses, identical to 4-level paging on x86_64.
let mask = !((Self::PAGE_ADDRESS_SIZE as usize - 1) >> 1);
let masked = address.data() & mask;
masked == mask || masked == 0
}
}
const _: () = {
assert!(RiscV64Sv48Arch::PAGE_SIZE == 4096);
assert!(RiscV64Sv48Arch::PAGE_OFFSET_MASK == 0xFFF);
assert!(RiscV64Sv48Arch::PAGE_ADDRESS_SHIFT == 48);
assert!(RiscV64Sv48Arch::PAGE_ADDRESS_SIZE == 0x0001_0000_0000_0000);
assert!(RiscV64Sv48Arch::PAGE_ADDRESS_MASK == 0x0000_FFFF_FFFF_F000);
assert!(RiscV64Sv48Arch::PAGE_ENTRY_SIZE == 8);
assert!(RiscV64Sv48Arch::PAGE_ENTRIES == 512);
assert!(RiscV64Sv48Arch::PAGE_ENTRY_MASK == 0x1FF);
assert!(RiscV64Sv48Arch::PAGE_NEGATIVE_MASK == 0xFFFF_0000_0000_0000);
assert!(RiscV64Sv48Arch::ENTRY_ADDRESS_SIZE == 0x0000_1000_0000_0000);
assert!(RiscV64Sv48Arch::ENTRY_ADDRESS_MASK == 0x0000_0FFF_FFFF_FFFF);
assert!(RiscV64Sv48Arch::ENTRY_FLAGS_MASK == 0xFFC0_0000_0000_03FF);
assert!(RiscV64Sv48Arch::PHYS_OFFSET == 0xFFFF_8000_0000_0000);
};
#[cfg(test)]
mod tests {
use super::RiscV64Sv48Arch;
use crate::Arch;
#[test]
fn is_canonical() {
use super::VirtualAddress;
// Close to identical when compared to x86_64 test.
fn yes(address: usize) {
assert!(RiscV64Sv48Arch::virt_is_valid(VirtualAddress::new(address)));
}
fn no(address: usize) {
assert!(!RiscV64Sv48Arch::virt_is_valid(VirtualAddress::new(
address
)));
}
yes(0xFFFF_8000_1337_1337);
yes(0xFFFF_FFFF_FFFF_FFFF);
yes(0x0000_0000_0000_0042);
yes(0x0000_7FFF_FFFF_FFFF);
no(0x1337_0000_0000_0000);
no(0x1337_8000_0000_0000);
no(0x0000_8000_0000_0000);
}
}
+116
View File
@@ -0,0 +1,116 @@
use core::arch::asm;
use crate::{Arch, PhysicalAddress, TableKind, VirtualAddress};
#[derive(Clone, Copy)]
pub struct RiscV64Sv57Arch;
impl Arch for RiscV64Sv57Arch {
const KERNEL_SEPARATE_TABLE: bool = false;
const PAGE_SHIFT: usize = 12; // 4096 bytes
const PAGE_ENTRY_SHIFT: usize = 9; // 512 entries, 8 bytes each
const PAGE_LEVELS: usize = 5; // L0, L1, L2, L3, L4
const ENTRY_ADDRESS_WIDTH: usize = 44;
const ENTRY_ADDRESS_SHIFT: usize = 10;
const ENTRY_FLAG_DEFAULT_PAGE: usize = Self::ENTRY_FLAG_PRESENT | Self::ENTRY_FLAG_READONLY;
const ENTRY_FLAG_DEFAULT_TABLE: usize = Self::ENTRY_FLAG_PRESENT;
const ENTRY_FLAG_PRESENT: usize = 1 << 0;
const ENTRY_FLAG_READONLY: usize = 1 << 1;
const ENTRY_FLAG_READWRITE: usize = 3 << 1;
const ENTRY_FLAG_PAGE_USER: usize = 1 << 4;
const ENTRY_FLAG_TABLE_USER: usize = 0;
const ENTRY_FLAG_NO_EXEC: usize = 0;
const ENTRY_FLAG_EXEC: usize = 1 << 3;
const ENTRY_FLAG_GLOBAL: usize = 1 << 5;
const ENTRY_FLAG_NO_GLOBAL: usize = 0;
const ENTRY_FLAG_DEVICE_MEMORY: usize = 0; // FIXME use Svpbmt
const ENTRY_FLAG_UNCACHEABLE: usize = 0; // FIXME use Svpbmt
const ENTRY_FLAG_WRITE_COMBINING: usize = 0; // FIXME use Svpbmt
const PHYS_OFFSET: usize = 0xFF00_0000_0000_0000;
#[inline(always)]
fn invalidate(address: VirtualAddress) {
unsafe { asm!("sfence.vma {}", in(reg) address.data()) };
}
#[inline(always)]
fn invalidate_all() {
unsafe { asm!("sfence.vma") };
}
#[inline(always)]
fn table(_table_kind: TableKind) -> PhysicalAddress {
let satp: usize;
unsafe { asm!("csrr {0}, satp", out(reg) satp) };
PhysicalAddress::new(
(satp & Self::ENTRY_ADDRESS_MASK) << Self::PAGE_SHIFT, // Convert from PPN
)
}
#[inline(always)]
unsafe fn set_table(_table_kind: TableKind, address: PhysicalAddress) {
let satp = (10 << 60) | // Sv57 MODE
(address.data() >> Self::PAGE_SHIFT); // Convert to PPN (TODO: ensure alignment)
unsafe {
asm!("csrw satp, {0}", in(reg) satp);
Self::invalidate_all();
}
}
fn virt_is_valid(address: VirtualAddress) -> bool {
let mask = !((Self::PAGE_ADDRESS_SIZE as usize - 1) >> 1);
let masked = address.data() & mask;
masked == mask || masked == 0
}
}
const _: () = {
assert!(RiscV64Sv57Arch::PAGE_SIZE == 4096);
assert!(RiscV64Sv57Arch::PAGE_OFFSET_MASK == 0xFFF);
assert!(RiscV64Sv57Arch::PAGE_ADDRESS_SHIFT == 57);
assert!(RiscV64Sv57Arch::PAGE_ADDRESS_SIZE == 0x0200_0000_0000_0000);
assert!(RiscV64Sv57Arch::PAGE_ADDRESS_MASK == 0x01FF_FFFF_FFFF_F000);
assert!(RiscV64Sv57Arch::PAGE_ENTRY_SIZE == 8);
assert!(RiscV64Sv57Arch::PAGE_ENTRIES == 512);
assert!(RiscV64Sv57Arch::PAGE_ENTRY_MASK == 0x1FF);
assert!(RiscV64Sv57Arch::PAGE_NEGATIVE_MASK == 0xFE00_0000_0000_0000);
assert!(RiscV64Sv57Arch::ENTRY_ADDRESS_SIZE == 0x0000_1000_0000_0000);
assert!(RiscV64Sv57Arch::ENTRY_ADDRESS_MASK == 0x0000_0FFF_FFFF_FFFF);
assert!(RiscV64Sv57Arch::ENTRY_FLAGS_MASK == 0xFFC0_0000_0000_03FF);
assert!(RiscV64Sv57Arch::PHYS_OFFSET == 0xFF00_0000_0000_0000);
};
#[cfg(test)]
mod tests {
use super::RiscV64Sv57Arch;
use crate::Arch;
#[test]
fn is_canonical() {
use super::VirtualAddress;
fn yes(address: usize) {
assert!(RiscV64Sv57Arch::virt_is_valid(VirtualAddress::new(address)));
}
fn no(address: usize) {
assert!(!RiscV64Sv57Arch::virt_is_valid(VirtualAddress::new(
address
)));
}
yes(0xFF00_0000_1337_1337);
yes(0xFFFF_FFFF_FFFF_FFFF);
yes(0x0000_0000_0000_0042);
yes(0x00FF_FFFF_FFFF_FFFF);
no(0x1337_0000_0000_0000);
no(0x1337_8000_0000_0000);
no(0x0F00_0000_0000_0000);
}
}
+80
View File
@@ -0,0 +1,80 @@
//TODO: USE PAE
use core::arch::asm;
use crate::{Arch, PhysicalAddress, TableKind, VirtualAddress};
#[derive(Clone, Copy)]
pub struct X86Arch;
impl Arch for X86Arch {
const KERNEL_SEPARATE_TABLE: bool = false;
const PAGE_SHIFT: usize = 12; // 4096 bytes
const PAGE_ENTRY_SHIFT: usize = 10; // 1024 entries, 4 bytes each
const PAGE_LEVELS: usize = 2; // PD, PT
const ENTRY_ADDRESS_WIDTH: usize = 20;
const ENTRY_FLAG_DEFAULT_PAGE: usize = Self::ENTRY_FLAG_PRESENT;
const ENTRY_FLAG_DEFAULT_TABLE: usize = Self::ENTRY_FLAG_PRESENT | Self::ENTRY_FLAG_READWRITE;
const ENTRY_FLAG_PRESENT: usize = 1 << 0;
const ENTRY_FLAG_READONLY: usize = 0;
const ENTRY_FLAG_READWRITE: usize = 1 << 1;
const ENTRY_FLAG_PAGE_USER: usize = 1 << 2;
// Not used: const ENTRY_FLAG_HUGE: usize = 1 << 7;
const ENTRY_FLAG_GLOBAL: usize = 1 << 8;
const ENTRY_FLAG_NO_GLOBAL: usize = 0;
const ENTRY_FLAG_NO_EXEC: usize = 0; // NOT AVAILABLE UNLESS PAE IS USED!
const ENTRY_FLAG_EXEC: usize = 0;
const ENTRY_FLAG_DEVICE_MEMORY: usize = PAT_UC_;
const ENTRY_FLAG_UNCACHEABLE: usize = PAT_UC_;
const ENTRY_FLAG_WRITE_COMBINING: usize = PAT_WC;
const PHYS_OFFSET: usize = 0x8000_0000;
#[inline(always)]
fn invalidate(address: VirtualAddress) {
unsafe { asm!("invlpg [{0}]", in(reg) address.data()) };
}
#[inline(always)]
fn invalidate_all() {
unsafe { Self::set_table(TableKind::User, Self::table(TableKind::User)) };
}
#[inline(always)]
fn table(_table_kind: TableKind) -> PhysicalAddress {
let address: usize;
unsafe { asm!("mov {0}, cr3", out(reg) address) };
PhysicalAddress::new(address)
}
#[inline(always)]
unsafe fn set_table(_table_kind: TableKind, address: PhysicalAddress) {
unsafe { asm!("mov cr3, {0}", in(reg) address.data()) };
}
fn virt_is_valid(_address: VirtualAddress) -> bool {
// On 32-bit x86, every virtual address is valid
true
}
}
pub use super::x86_shared::*;
const _: () = {
assert!(X86Arch::PAGE_SIZE == 4096);
assert!(X86Arch::PAGE_OFFSET_MASK == 0xFFF);
assert!(X86Arch::PAGE_ADDRESS_SHIFT == 32);
assert!(X86Arch::PAGE_ADDRESS_SIZE == 0x0000_0001_0000_0000);
assert!(X86Arch::PAGE_ADDRESS_MASK == 0xFFFF_F000);
assert!(X86Arch::PAGE_ENTRY_SIZE == 4);
assert!(X86Arch::PAGE_ENTRIES == 1024);
assert!(X86Arch::PAGE_ENTRY_MASK == 0x3FF);
assert!(X86Arch::PAGE_NEGATIVE_MASK == 0x0000_0000_0000);
assert!(X86Arch::ENTRY_ADDRESS_SIZE == 0x0000_0000_0010_0000);
assert!(X86Arch::ENTRY_ADDRESS_MASK == 0x000F_FFFF);
assert!(X86Arch::ENTRY_FLAGS_MASK == 0x0000_0FFF);
assert!(X86Arch::PHYS_OFFSET == 0x8000_0000);
};
+107
View File
@@ -0,0 +1,107 @@
use core::arch::asm;
use crate::{Arch, PhysicalAddress, TableKind, VirtualAddress};
#[derive(Clone, Copy, Debug)]
pub struct X8664Arch;
impl Arch for X8664Arch {
const KERNEL_SEPARATE_TABLE: bool = false;
const PAGE_SHIFT: usize = 12; // 4096 bytes
const PAGE_ENTRY_SHIFT: usize = 9; // 512 entries, 8 bytes each
const PAGE_LEVELS: usize = 4; // PML4, PDP, PD, PT
const ENTRY_ADDRESS_WIDTH: usize = 40;
const ENTRY_FLAG_DEFAULT_PAGE: usize = Self::ENTRY_FLAG_PRESENT;
const ENTRY_FLAG_DEFAULT_TABLE: usize = Self::ENTRY_FLAG_PRESENT | Self::ENTRY_FLAG_READWRITE;
const ENTRY_FLAG_PRESENT: usize = 1 << 0;
const ENTRY_FLAG_READONLY: usize = 0;
const ENTRY_FLAG_READWRITE: usize = 1 << 1;
const ENTRY_FLAG_PAGE_USER: usize = 1 << 2;
// Not used: const ENTRY_FLAG_HUGE: usize = 1 << 7;
const ENTRY_FLAG_GLOBAL: usize = 1 << 8;
const ENTRY_FLAG_NO_GLOBAL: usize = 0;
const ENTRY_FLAG_NO_EXEC: usize = 1 << 63;
const ENTRY_FLAG_EXEC: usize = 0;
const ENTRY_FLAG_DEVICE_MEMORY: usize = PAT_UC_;
const ENTRY_FLAG_UNCACHEABLE: usize = PAT_UC_;
const ENTRY_FLAG_WRITE_COMBINING: usize = PAT_WC;
const PHYS_OFFSET: usize = Self::PAGE_NEGATIVE_MASK + (Self::PAGE_ADDRESS_SIZE >> 1) as usize; // PML4 slot 256 and onwards
#[inline(always)]
fn invalidate(address: VirtualAddress) {
unsafe { asm!("invlpg [{0}]", in(reg) address.data()) };
}
#[inline(always)]
fn invalidate_all() {
unsafe { Self::set_table(TableKind::User, Self::table(TableKind::User)) };
}
#[inline(always)]
fn table(_table_kind: TableKind) -> PhysicalAddress {
let address: usize;
unsafe { asm!("mov {0}, cr3", out(reg) address) };
PhysicalAddress::new(address)
}
#[inline(always)]
unsafe fn set_table(_table_kind: TableKind, address: PhysicalAddress) {
unsafe { asm!("mov cr3, {0}", in(reg) address.data()) };
}
fn virt_is_valid(address: VirtualAddress) -> bool {
// On x86_64, an address is valid if and only if it is canonical. It may still point to
// unmapped memory, but will always be valid once translated via the page table has
// suceeded.
let masked = address.data() & 0xFFFF_8000_0000_0000;
// TODO: 5-level paging
masked == 0xFFFF_8000_0000_0000 || masked == 0
}
}
pub use super::x86_shared::*;
const _: () = {
assert!(X8664Arch::PAGE_SIZE == 4096);
assert!(X8664Arch::PAGE_OFFSET_MASK == 0xFFF);
assert!(X8664Arch::PAGE_ADDRESS_SHIFT == 48);
assert!(X8664Arch::PAGE_ADDRESS_SIZE == 0x0001_0000_0000_0000);
assert!(X8664Arch::PAGE_ADDRESS_MASK == 0x0000_FFFF_FFFF_F000);
assert!(X8664Arch::PAGE_ENTRY_SIZE == 8);
assert!(X8664Arch::PAGE_ENTRIES == 512);
assert!(X8664Arch::PAGE_ENTRY_MASK == 0x1FF);
assert!(X8664Arch::PAGE_NEGATIVE_MASK == 0xFFFF_0000_0000_0000);
assert!(X8664Arch::ENTRY_ADDRESS_SIZE == 0x0000_0100_0000_0000);
assert!(X8664Arch::ENTRY_ADDRESS_MASK == 0x0000_00FF_FFFF_FFFF);
assert!(X8664Arch::ENTRY_FLAGS_MASK == 0xFFF0_0000_0000_0FFF);
assert!(X8664Arch::PHYS_OFFSET == 0xFFFF_8000_0000_0000);
};
#[cfg(test)]
mod tests {
use super::{VirtualAddress, X8664Arch};
use crate::Arch;
#[test]
fn is_canonical() {
fn yes(address: usize) {
assert!(X8664Arch::virt_is_valid(VirtualAddress::new(address)));
}
fn no(address: usize) {
assert!(!X8664Arch::virt_is_valid(VirtualAddress::new(address)));
}
yes(0xFFFF_8000_1337_1337);
yes(0xFFFF_FFFF_FFFF_FFFF);
yes(0x0000_0000_0000_0042);
yes(0x0000_7FFF_FFFF_FFFF);
no(0x1337_0000_0000_0000);
no(0x1337_8000_0000_0000);
no(0x0000_8000_0000_0000);
}
}
+37
View File
@@ -0,0 +1,37 @@
#![expect(clippy::identity_op)]
// Page attribute table is indexed by PAT(7) PCD(4) PWT(3)
pub(crate) const _PAT_WB: usize = (0b0 << 7) + (0b00 << 3);
pub(crate) const _PAT_WT: usize = (0b0 << 7) + (0b01 << 3);
pub(crate) const PAT_UC_: usize = (0b0 << 7) + (0b10 << 3); // UC-
pub(crate) const _PAT_UC: usize = (0b0 << 7) + (0b11 << 3); // UC
pub(crate) const PAT_WC: usize = (0b1 << 7) + (0b00 << 3);
/// Setup page attribute table
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline(always)]
pub unsafe fn init_pat() {
unsafe {
let uncacheable = 0; // UC
let write_combining = 1; // WC
let write_through = 4; // WT
let _write_protected = 5; // WP
let write_back = 6; // WB
let uncached = 7; // UC- (overridable by WC MTRR)
let pat0 = write_back;
let pat1 = write_through;
let pat2 = uncached;
let pat3 = uncacheable;
let pat4 = write_combining;
let pat5 = pat1;
let pat6 = pat2;
let pat7 = pat3;
let msr = 631; // IA32_PAT
let low = u32::from_be_bytes([pat3, pat2, pat1, pat0]);
let high = u32::from_be_bytes([pat7, pat6, pat5, pat4]);
core::arch::asm!("wrmsr", in("ecx") msr, in("eax") low, in("edx") high);
}
}
+97
View File
@@ -0,0 +1,97 @@
#![no_std]
#![allow(clippy::new_without_default)]
pub use crate::{allocator::*, arch::*, page::*};
mod allocator;
mod arch;
mod page;
pub const KILOBYTE: usize = 1024;
pub const MEGABYTE: usize = KILOBYTE * 1024;
pub const GIGABYTE: usize = MEGABYTE * 1024;
#[cfg(target_pointer_width = "64")]
pub const TERABYTE: usize = GIGABYTE * 1024;
/// Specific table to be used, needed on some architectures
//TODO: Use this throughout the code
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum TableKind {
/// Userspace page table
User,
/// Kernel page table
Kernel,
}
/// Physical memory address
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct PhysicalAddress(usize);
impl PhysicalAddress {
#[inline(always)]
pub const fn new(address: usize) -> Self {
Self(address)
}
#[inline(always)]
pub fn data(&self) -> usize {
self.0
}
#[expect(clippy::should_implement_trait)]
#[inline(always)]
pub fn add(self, offset: usize) -> Self {
Self(self.0 + offset)
}
}
impl core::fmt::Debug for PhysicalAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "[phys {:#0x}]", self.data())
}
}
/// Virtual memory address
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct VirtualAddress(usize);
impl VirtualAddress {
#[inline(always)]
pub const fn new(address: usize) -> Self {
Self(address)
}
#[inline(always)]
pub fn data(&self) -> usize {
self.0
}
#[expect(clippy::should_implement_trait)]
#[inline(always)]
pub fn add(self, offset: usize) -> Self {
Self(self.0 + offset)
}
#[inline(always)]
pub fn kind(&self) -> TableKind {
if (self.0 as isize) < 0 {
TableKind::Kernel
} else {
TableKind::User
}
}
}
impl core::fmt::Debug for VirtualAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "[virt {:#0x}]", self.data())
}
}
#[derive(Clone, Copy, Debug)]
pub struct MemoryArea {
pub base: PhysicalAddress,
pub size: usize,
}
+309
View File
@@ -0,0 +1,309 @@
#![cfg(target_pointer_width = "64")]
use rmm::{
emulate::EmulateArch, Arch, BuddyAllocator, BumpAllocator, Flusher, FrameAllocator, FrameCount,
MemoryArea, PageFlags, PageFlushAll, PageMapper, PageTable, PhysicalAddress, TableKind,
VirtualAddress, GIGABYTE, KILOBYTE, MEGABYTE, TERABYTE,
};
use std::marker::PhantomData;
pub fn format_size(size: usize) -> String {
if size >= 2 * TERABYTE {
format!("{} TB", size / TERABYTE)
} else if size >= 2 * GIGABYTE {
format!("{} GB", size / GIGABYTE)
} else if size >= 2 * MEGABYTE {
format!("{} MB", size / MEGABYTE)
} else if size >= 2 * KILOBYTE {
format!("{} KB", size / KILOBYTE)
} else {
format!("{} B", size)
}
}
#[allow(dead_code)]
unsafe fn dump_tables<A: Arch>(table: PageTable<A>) {
unsafe {
let level = table.level();
for i in 0..A::PAGE_ENTRIES {
if level == 0 {
if let Some(entry) = table.entry(i) {
if entry.present() {
let base = table.entry_base(i).unwrap();
println!(
"0x{:X}: 0x{:X}",
base.data(),
entry.address().unwrap().data()
);
}
}
} else {
if let Some(next) = table.next(i) {
dump_tables(next);
}
}
}
}
}
pub struct SlabNode<A> {
next: PhysicalAddress,
count: usize,
phantom: PhantomData<A>,
}
impl<A: Arch> SlabNode<A> {
pub fn new(next: PhysicalAddress, count: usize) -> Self {
Self {
next,
count,
phantom: PhantomData,
}
}
pub fn empty() -> Self {
Self::new(PhysicalAddress::new(0), 0)
}
pub unsafe fn insert(&mut self, phys: PhysicalAddress) {
unsafe {
let virt = A::phys_to_virt(phys);
A::write(virt, self.next);
self.next = phys;
self.count += 1;
}
}
pub unsafe fn remove(&mut self) -> Option<PhysicalAddress> {
unsafe {
if self.count > 0 {
let phys = self.next;
let virt = A::phys_to_virt(phys);
self.next = A::read(virt);
self.count -= 1;
Some(phys)
} else {
None
}
}
}
}
pub struct SlabAllocator<A> {
//TODO: Allow allocations up to maximum pageable size
nodes: [SlabNode<A>; 4],
phantom: PhantomData<A>,
}
impl<A: Arch> SlabAllocator<A> {
pub unsafe fn new(areas: &'static [MemoryArea], offset: usize) -> Self {
unsafe {
let mut allocator = Self {
nodes: [
SlabNode::empty(),
SlabNode::empty(),
SlabNode::empty(),
SlabNode::empty(),
],
phantom: PhantomData,
};
// Add unused areas to free lists
let mut area_offset = offset;
for area in areas.iter() {
if area_offset < area.size {
let area_base = area.base.add(area_offset);
let area_size = area.size - area_offset;
allocator.free(area_base, area_size);
area_offset = 0;
} else {
area_offset -= area.size;
}
}
allocator
}
}
pub unsafe fn allocate(&mut self, size: usize) -> Option<PhysicalAddress> {
unsafe {
for level in 0..A::PAGE_LEVELS - 1 {
let level_shift = level * A::PAGE_ENTRY_SHIFT + A::PAGE_SHIFT;
let level_size = 1 << level_shift;
if size <= level_size {
if let Some(base) = self.nodes[level].remove() {
self.free(base.add(size), level_size - size);
return Some(base);
}
}
}
None
}
}
//TODO: This causes fragmentation, since neighbors are not identified
//TODO: remainders less than PAGE_SIZE will be lost
pub unsafe fn free(&mut self, mut base: PhysicalAddress, mut size: usize) {
unsafe {
for level in (0..A::PAGE_LEVELS - 1).rev() {
let level_shift = level * A::PAGE_ENTRY_SHIFT + A::PAGE_SHIFT;
let level_size = 1 << level_shift;
while size >= level_size {
println!("Add {:X} {}", base.data(), format_size(level_size));
self.nodes[level].insert(base);
base = base.add(level_size);
size -= level_size;
}
}
}
}
pub unsafe fn remaining(&mut self) -> usize {
let mut remaining = 0;
for level in (0..A::PAGE_LEVELS - 1).rev() {
let level_shift = level * A::PAGE_ENTRY_SHIFT + A::PAGE_SHIFT;
let level_size = 1 << level_shift;
remaining += self.nodes[level].count * level_size;
}
remaining
}
}
unsafe fn new_tables<A: Arch>(areas: &'static [MemoryArea]) {
unsafe {
// First, calculate how much memory we have
let mut size = 0;
for area in areas.iter() {
size += area.size;
}
println!("Memory: {}", format_size(size));
// Create a basic allocator for the first pages
let mut bump_allocator = BumpAllocator::<A>::new(areas, 0);
{
// Map all physical areas at PHYS_OFFSET
let mut mapper = PageMapper::<A, _>::create(TableKind::Kernel, &mut bump_allocator)
.expect("failed to create Mapper");
for area in areas.iter() {
for i in 0..area.size / A::PAGE_SIZE {
let phys = area.base.add(i * A::PAGE_SIZE);
let (_, flush) = mapper
.map_linearly(phys, PageFlags::<A>::new().write(true))
.expect("failed to map page to frame");
flush.ignore(); // Not the active table
}
}
// Use the new table
mapper.make_current();
}
// Create the physical memory map
let offset = bump_allocator.offset();
println!("Permanently used: {}", format_size(offset));
let mut allocator = BuddyAllocator::<A>::new(bump_allocator).unwrap();
for i in 0..16 {
{
let phys_opt = allocator.allocate_one();
println!("page {}: {:X?}", i, phys_opt);
if i % 3 == 0 {
if let Some(phys) = phys_opt {
println!("free {}: {:X?}", i, phys_opt);
allocator.free_one(phys);
}
}
}
{
let phys_opt = allocator.allocate(FrameCount::new(16));
println!("page*16 {}: {:X?}", i, phys_opt);
if i % 2 == 0 {
if let Some(phys) = phys_opt {
println!("free*16 {}: {:X?}", i, phys_opt);
allocator.free(phys, FrameCount::new(16));
}
}
}
}
let mut mapper = PageMapper::<A, _>::current(TableKind::Kernel, &mut allocator);
let mut flush_all = PageFlushAll::new();
for i in 0..16 {
let virt = VirtualAddress::new(MEGABYTE + i * A::PAGE_SIZE);
let phys = mapper
.allocator_mut()
.allocate_one()
.expect("failed to map page");
let flush = mapper
.map_phys(virt, phys, PageFlags::<A>::new().user(true).write(true))
.expect("failed to map page");
flush_all.consume(flush);
}
flush_all.flush();
let mut flush_all = PageFlushAll::new();
for i in 0..16 {
let virt = VirtualAddress::new(MEGABYTE + i * A::PAGE_SIZE);
let (old, _, flush) = mapper.unmap_phys(virt).expect("failed to unmap page");
mapper.allocator_mut().free_one(old);
flush_all.consume(flush);
}
flush_all.flush();
let usage = allocator.usage();
println!("Allocator usage:");
println!(
" Used: {}",
format_size(usage.used().data() * A::PAGE_SIZE)
);
println!(
" Free: {}",
format_size(usage.free().data() * A::PAGE_SIZE)
);
println!(
" Total: {}",
format_size(usage.total().data() * A::PAGE_SIZE)
);
}
}
fn main() {
unsafe {
let areas = EmulateArch::init();
// Debug table
//dump_tables(PageTable::<A>::top());
new_tables::<EmulateArch>(areas);
//dump_tables(PageTable::<A>::top());
for i in &[1, 2, 4, 8, 16, 32] {
let phys = PhysicalAddress::new(i * MEGABYTE);
let virt = EmulateArch::phys_to_virt(phys);
// Test read
println!(
"0x{:X} (0x{:X}) = 0x{:X}",
virt.data(),
phys.data(),
EmulateArch::read::<u8>(virt)
);
// Test write
EmulateArch::write::<u8>(virt, 0x5A);
// Test read
println!(
"0x{:X} (0x{:X}) = 0x{:X}",
virt.data(),
phys.data(),
EmulateArch::read::<u8>(virt)
);
}
}
}
+59
View File
@@ -0,0 +1,59 @@
use core::marker::PhantomData;
use crate::{Arch, PageFlags, PhysicalAddress};
#[derive(Clone, Copy, Debug)]
pub struct PageEntry<A> {
data: usize,
phantom: PhantomData<A>,
}
impl<A: Arch> PageEntry<A> {
#[inline(always)]
pub fn new(address: usize, flags: usize) -> Self {
let data = (((address >> A::PAGE_SHIFT) & A::ENTRY_ADDRESS_MASK) << A::ENTRY_ADDRESS_SHIFT)
| flags;
Self::from_data(data)
}
#[inline(always)]
pub fn from_data(data: usize) -> Self {
Self {
data,
phantom: PhantomData,
}
}
#[inline(always)]
pub fn data(&self) -> usize {
self.data
}
#[inline(always)]
pub fn address(&self) -> Result<PhysicalAddress, PhysicalAddress> {
let addr = PhysicalAddress(
((self.data >> A::ENTRY_ADDRESS_SHIFT) & A::ENTRY_ADDRESS_MASK) << A::PAGE_SHIFT,
);
if self.present() {
Ok(addr)
} else {
Err(addr)
}
}
#[inline(always)]
pub fn flags(&self) -> PageFlags<A> {
unsafe { PageFlags::from_data(self.data & A::ENTRY_FLAGS_MASK) }
}
#[inline(always)]
pub fn set_flags(&mut self, flags: PageFlags<A>) {
self.data &= !A::ENTRY_FLAGS_MASK;
self.data |= flags.data();
}
#[inline(always)]
pub fn present(&self) -> bool {
self.data & A::ENTRY_FLAG_PRESENT != 0
}
}
+157
View File
@@ -0,0 +1,157 @@
use core::{fmt, marker::PhantomData};
use crate::Arch;
#[derive(Clone, Copy)]
pub struct PageFlags<A> {
data: usize,
arch: PhantomData<A>,
}
impl<A: Arch> PageFlags<A> {
#[inline(always)]
pub fn new() -> Self {
unsafe {
Self::from_data(
// Flags set to present, kernel space, read-only, no-execute by default
A::ENTRY_FLAG_DEFAULT_PAGE
| A::ENTRY_FLAG_READONLY
| A::ENTRY_FLAG_NO_EXEC
| A::ENTRY_FLAG_NO_GLOBAL,
)
}
}
#[inline(always)]
pub fn new_table() -> Self {
unsafe {
Self::from_data(
// Flags set to present, kernel space, read-only, no-execute by default
A::ENTRY_FLAG_DEFAULT_TABLE | A::ENTRY_FLAG_NO_EXEC | A::ENTRY_FLAG_NO_GLOBAL,
)
}
}
#[inline(always)]
pub unsafe fn from_data(data: usize) -> Self {
Self {
data,
arch: PhantomData,
}
}
#[inline(always)]
pub fn data(&self) -> usize {
self.data
}
#[must_use]
#[inline(always)]
pub fn custom_flag(mut self, flag: usize, value: bool) -> Self {
if value {
self.data |= flag;
} else {
self.data &= !flag;
}
self
}
#[must_use]
#[inline(always)]
pub fn device_memory(self, value: bool) -> Self {
self.custom_flag(A::ENTRY_FLAG_DEVICE_MEMORY, value)
}
#[must_use]
#[inline(always)]
pub fn uncacheable(self, value: bool) -> Self {
self.custom_flag(A::ENTRY_FLAG_UNCACHEABLE, value)
}
#[must_use]
#[inline(always)]
pub fn write_combining(self, value: bool) -> Self {
self.custom_flag(A::ENTRY_FLAG_WRITE_COMBINING, value)
}
#[inline(always)]
pub fn has_flag(&self, flag: usize) -> bool {
self.data & flag == flag
}
#[inline(always)]
pub fn has_present(&self) -> bool {
self.has_flag(A::ENTRY_FLAG_PRESENT)
}
#[must_use]
#[inline(always)]
pub fn user(self, value: bool) -> Self {
self.custom_flag(A::ENTRY_FLAG_PAGE_USER, value)
}
#[inline(always)]
pub fn has_user(&self) -> bool {
self.has_flag(A::ENTRY_FLAG_PAGE_USER)
}
#[must_use]
#[inline(always)]
pub fn write(self, value: bool) -> Self {
// Architecture may use readonly or readwrite, or both, support either
if value {
self.custom_flag(A::ENTRY_FLAG_READONLY | A::ENTRY_FLAG_READWRITE, false)
.custom_flag(A::ENTRY_FLAG_READWRITE, true)
} else {
self.custom_flag(A::ENTRY_FLAG_READONLY | A::ENTRY_FLAG_READWRITE, false)
.custom_flag(A::ENTRY_FLAG_READONLY, true)
}
}
#[inline(always)]
pub fn has_write(&self) -> bool {
// Architecture may use readonly or readwrite, or both, support either
self.data & (A::ENTRY_FLAG_READONLY | A::ENTRY_FLAG_READWRITE) == A::ENTRY_FLAG_READWRITE
}
#[must_use]
#[inline(always)]
pub fn execute(self, value: bool) -> Self {
//TODO: write xor execute?
// Architecture may use no exec or exec, support either
self.custom_flag(A::ENTRY_FLAG_NO_EXEC, !value)
.custom_flag(A::ENTRY_FLAG_EXEC, value)
}
#[inline(always)]
pub fn has_execute(&self) -> bool {
// Architecture may use no exec or exec, support either
self.data & (A::ENTRY_FLAG_NO_EXEC | A::ENTRY_FLAG_EXEC) == A::ENTRY_FLAG_EXEC
}
#[must_use]
#[inline(always)]
pub fn global(self, value: bool) -> Self {
// Architecture may use global or non global, support either
self.custom_flag(A::ENTRY_FLAG_NO_GLOBAL, !value)
.custom_flag(A::ENTRY_FLAG_GLOBAL, value)
}
#[inline(always)]
pub fn is_global(&self) -> bool {
// Architecture may use global or non global, support either
self.data & (A::ENTRY_FLAG_GLOBAL | A::ENTRY_FLAG_NO_GLOBAL) == A::ENTRY_FLAG_GLOBAL
}
}
impl<A: Arch> fmt::Debug for PageFlags<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PageFlags")
.field("present", &self.has_present())
.field("write", &self.has_write())
.field("executable", &self.has_execute())
.field("user", &self.has_user())
.field("bits", &format_args!("{:#0x}", self.data))
.finish()
}
}
+71
View File
@@ -0,0 +1,71 @@
use core::{marker::PhantomData, mem};
use crate::{Arch, VirtualAddress};
pub trait Flusher<A> {
fn consume(&mut self, flush: PageFlush<A>);
}
#[must_use = "The page table must be flushed, or the changes unsafely ignored"]
pub struct PageFlush<A> {
virt: VirtualAddress,
phantom: PhantomData<A>,
}
impl<A: Arch> PageFlush<A> {
pub fn new(virt: VirtualAddress) -> Self {
Self {
virt,
phantom: PhantomData,
}
}
pub fn flush(self) {
A::invalidate(self.virt);
}
#[expect(clippy::forget_non_drop)]
pub unsafe fn ignore(self) {
mem::forget(self);
}
}
// TODO: Might remove Drop and add #[must_use] again, but ergonomically I prefer being able to pass
// a flusher, and have it dropped by the end of the function it is passed to, in order to flush.
pub struct PageFlushAll<A: Arch> {
phantom: PhantomData<fn() -> A>,
}
impl<A: Arch> PageFlushAll<A> {
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
pub fn flush(self) {}
pub unsafe fn ignore(self) {
mem::forget(self);
}
}
impl<A: Arch> Drop for PageFlushAll<A> {
fn drop(&mut self) {
A::invalidate_all();
}
}
impl<A: Arch> Flusher<A> for PageFlushAll<A> {
fn consume(&mut self, flush: PageFlush<A>) {
unsafe {
flush.ignore();
}
}
}
impl<A: Arch, T: Flusher<A> + ?Sized> Flusher<A> for &mut T {
fn consume(&mut self, flush: PageFlush<A>) {
<T as Flusher<A>>::consume(self, flush)
}
}
impl<A: Arch> Flusher<A> for () {
fn consume(&mut self, _: PageFlush<A>) {}
}
+269
View File
@@ -0,0 +1,269 @@
use core::marker::PhantomData;
use crate::{
Arch, FrameAllocator, PageEntry, PageFlags, PageFlush, PageTable, PhysicalAddress, TableKind,
VirtualAddress,
};
pub struct PageMapper<A, F> {
table_kind: TableKind,
table_addr: PhysicalAddress,
allocator: F,
_phantom: PhantomData<fn() -> A>,
}
impl<A: Arch, F> PageMapper<A, F> {
unsafe fn new(table_kind: TableKind, table_addr: PhysicalAddress, allocator: F) -> Self {
Self {
table_kind,
table_addr,
allocator,
_phantom: PhantomData,
}
}
pub unsafe fn current(table_kind: TableKind, allocator: F) -> Self {
unsafe {
let table_addr = A::table(table_kind);
Self::new(table_kind, table_addr, allocator)
}
}
pub fn is_current(&self) -> bool {
self.table().phys() == A::table(self.table_kind)
}
pub unsafe fn make_current(&self) {
unsafe {
A::set_table(self.table_kind, self.table_addr);
}
}
pub fn table(&self) -> PageTable<A> {
// SAFETY: The only way to initialize a PageMapper is via new(), and we assume it upholds
// all necessary invariants for this to be safe.
unsafe { PageTable::new(VirtualAddress::new(0), self.table_addr, A::PAGE_LEVELS - 1) }
}
pub fn allocator(&self) -> &F {
&self.allocator
}
pub fn allocator_mut(&mut self) -> &mut F {
&mut self.allocator
}
fn visit<T>(
&self,
virt: VirtualAddress,
f: impl FnOnce(&mut PageTable<A>, usize) -> T,
) -> Option<T> {
let mut table = self.table();
loop {
let i = table.index_of(virt)?;
if table.level() == 0 {
return Some(f(&mut table, i));
} else {
table = unsafe { table.next(i)? };
}
}
}
pub fn translate(&self, virt: VirtualAddress) -> Option<(PhysicalAddress, PageFlags<A>)> {
let entry = self.visit(virt, |p1, i| unsafe { p1.entry(i) })??;
Some((entry.address().ok()?, entry.flags()))
}
pub unsafe fn remap_with_full(
&mut self,
virt: VirtualAddress,
f: impl FnOnce(PhysicalAddress, PageFlags<A>) -> Option<(PhysicalAddress, PageFlags<A>)>,
) -> Option<(PageFlags<A>, PhysicalAddress, PageFlush<A>)> {
unsafe {
self.visit(virt, |p1, i| {
let old_entry = p1.entry(i)?;
let old_phys = old_entry.address().ok()?;
let old_flags = old_entry.flags();
let (new_phys, new_flags) = f(old_phys, old_flags)?;
// TODO: Higher-level PageEntry::new interface?
let new_entry = PageEntry::new(new_phys.data(), new_flags.data());
p1.set_entry(i, new_entry);
Some((old_flags, old_phys, PageFlush::new(virt)))
})
.flatten()
}
}
pub unsafe fn remap_with(
&mut self,
virt: VirtualAddress,
map_flags: impl FnOnce(PageFlags<A>) -> PageFlags<A>,
) -> Option<(PageFlags<A>, PhysicalAddress, PageFlush<A>)> {
unsafe {
self.remap_with_full(virt, |same_phys, old_flags| {
Some((same_phys, map_flags(old_flags)))
})
}
}
pub unsafe fn remap(
&mut self,
virt: VirtualAddress,
flags: PageFlags<A>,
) -> Option<PageFlush<A>> {
unsafe { self.remap_with(virt, |_| flags).map(|(_, _, flush)| flush) }
}
}
impl<A: Arch, F: FrameAllocator> PageMapper<A, F> {
pub unsafe fn create(table_kind: TableKind, mut allocator: F) -> Option<Self> {
unsafe {
let table_addr = allocator.allocate_one()?;
let mut table = Self::new(table_kind, table_addr, allocator);
match (table_kind, A::KERNEL_SEPARATE_TABLE) {
(TableKind::Kernel, false) => {
// Pre-allocate all kernel top-level page table entries so that when
// the page table is copied, these entries are synced between processes.
for i in A::PAGE_ENTRIES / 2..A::PAGE_ENTRIES {
let phys = table
.allocator
.allocate_one()
.expect("failed to map page table");
let flags = A::ENTRY_FLAG_DEFAULT_TABLE;
table
.table()
.set_entry(i, PageEntry::new(phys.data(), flags));
}
}
(TableKind::User, false) => {
// Copy higher half (kernel) mappings
let active_ktable = PageMapper::current(TableKind::Kernel, ());
for i in A::PAGE_ENTRIES / 2..A::PAGE_ENTRIES {
if let Some(entry) = active_ktable.table().entry(i) {
table.table().set_entry(i, entry);
}
}
}
(_, true) => {
// There is a separate page table for the kernel. No need to copy the kernel
// mappings to the user page table.
}
}
Some(table)
}
}
pub unsafe fn map_phys(
&mut self,
virt: VirtualAddress,
phys: PhysicalAddress,
flags: PageFlags<A>,
) -> Option<PageFlush<A>> {
unsafe {
//TODO: verify virt and phys are aligned
//TODO: verify flags have correct bits
let entry = PageEntry::new(phys.data(), flags.data());
let mut table = self.table();
loop {
let i = table.index_of(virt)?;
if table.level() == 0 {
//TODO: check for overwriting entry
table.set_entry(i, entry);
return Some(PageFlush::new(virt));
}
let next = match table.next(i) {
Some(some) => some,
None => {
let next_phys = self.allocator.allocate_one()?;
//TODO: correct flags?
let flags = A::ENTRY_FLAG_DEFAULT_TABLE
| if virt.kind() == TableKind::User {
A::ENTRY_FLAG_TABLE_USER
} else {
0
};
table.set_entry(i, PageEntry::new(next_phys.data(), flags));
table.next(i)?
}
};
table = next;
}
}
}
pub unsafe fn map_linearly(
&mut self,
phys: PhysicalAddress,
flags: PageFlags<A>,
) -> Option<(VirtualAddress, PageFlush<A>)> {
unsafe {
let virt = A::phys_to_virt(phys);
self.map_phys(virt, phys, flags).map(|flush| (virt, flush))
}
}
pub unsafe fn unmap_phys(
&mut self,
virt: VirtualAddress,
) -> Option<(PhysicalAddress, PageFlags<A>, PageFlush<A>)> {
//TODO: verify virt is aligned
let mut table = self.table();
let unmap_parents = A::KERNEL_SEPARATE_TABLE || table.index_of(virt)? < A::PAGE_ENTRIES / 2; // Is a userspace mapping
unsafe {
unmap_phys_inner(virt, &mut table, unmap_parents, &mut self.allocator)
.map(|(pa, pf)| (pa, pf, PageFlush::new(virt)))
}
}
}
unsafe fn unmap_phys_inner<A: Arch>(
virt: VirtualAddress,
table: &mut PageTable<A>,
unmap_parents: bool,
allocator: &mut impl FrameAllocator,
) -> Option<(PhysicalAddress, PageFlags<A>)> {
unsafe {
let i = table.index_of(virt)?;
if table.level() == 0 {
let entry_opt = table.entry(i);
table.set_entry(i, PageEntry::new(0, 0));
let entry = entry_opt?;
return Some((entry.address().ok()?, entry.flags()));
}
let mut subtable = table.next(i)?;
let res = unmap_phys_inner(virt, &mut subtable, unmap_parents, allocator)?;
if unmap_parents {
// TODO: Use a counter? This would reduce the remaining number of available bits, but could be
// faster (benchmark is needed).
let is_still_populated = (0..A::PAGE_ENTRIES)
.map(|j| subtable.entry(j).expect("must be within bounds"))
.any(|e| e.present());
if !is_still_populated {
allocator.free_one(subtable.phys());
table.set_entry(i, PageEntry::new(0, 0));
}
}
Some(res)
}
}
impl<A, F: core::fmt::Debug> core::fmt::Debug for PageMapper<A, F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PageMapper")
.field("frame", &self.table_addr)
.field("allocator", &self.allocator)
.finish()
}
}
+7
View File
@@ -0,0 +1,7 @@
pub use self::{entry::*, flags::*, flush::*, mapper::*, table::*};
mod entry;
mod flags;
mod flush;
mod mapper;
mod table;
+105
View File
@@ -0,0 +1,105 @@
use core::{fmt, marker::PhantomData};
use crate::{page::PageEntry, Arch, PhysicalAddress, VirtualAddress};
pub struct PageTable<A> {
base: VirtualAddress,
phys: PhysicalAddress,
level: usize,
phantom: PhantomData<A>,
}
impl<A: Arch> PageTable<A> {
pub(super) unsafe fn new(base: VirtualAddress, phys: PhysicalAddress, level: usize) -> Self {
Self {
base,
phys,
level,
phantom: PhantomData,
}
}
pub fn base(&self) -> VirtualAddress {
self.base
}
pub fn phys(&self) -> PhysicalAddress {
self.phys
}
pub fn level(&self) -> usize {
self.level
}
pub fn entry_base(&self, i: usize) -> Option<VirtualAddress> {
if i < A::PAGE_ENTRIES {
let level_shift = self.level * A::PAGE_ENTRY_SHIFT + A::PAGE_SHIFT;
Some(self.base.add(i << level_shift))
} else {
None
}
}
unsafe fn entry_virt(&self, i: usize) -> Option<VirtualAddress> {
if i < A::PAGE_ENTRIES {
Some(A::phys_to_virt(self.phys).add(i * A::PAGE_ENTRY_SIZE))
} else {
None
}
}
pub unsafe fn entry(&self, i: usize) -> Option<PageEntry<A>> {
unsafe {
let addr = self.entry_virt(i)?;
Some(PageEntry::from_data(A::read::<usize>(addr)))
}
}
pub(super) unsafe fn set_entry(&mut self, i: usize, entry: PageEntry<A>) -> Option<()> {
unsafe {
let addr = self.entry_virt(i)?;
A::write::<usize>(addr, entry.data());
Some(())
}
}
pub(super) fn index_of(&self, address: VirtualAddress) -> Option<usize> {
// Canonicalize address first
let address = VirtualAddress::new(address.data() & A::PAGE_ADDRESS_MASK);
let level_shift = self.level * A::PAGE_ENTRY_SHIFT + A::PAGE_SHIFT;
// Intentionally wraps around at last-level table to get all-ones mask on architectures
// where addressable physical address space covers entire usized space (e.g. x86)
let level_mask = A::PAGE_ENTRIES
.wrapping_shl(level_shift as u32)
.wrapping_sub(1);
if address >= self.base && address <= self.base.add(level_mask) {
Some((address.data() >> level_shift) & A::PAGE_ENTRY_MASK)
} else {
None
}
}
pub unsafe fn next(&self, i: usize) -> Option<Self> {
if self.level == 0 {
return None;
}
unsafe {
Some(PageTable::new(
self.entry_base(i)?,
self.entry(i)?.address().ok()?,
self.level - 1,
))
}
}
pub fn debug_entries(&self, f: impl Fn(fmt::Arguments<'_>)) {
for i in 0..A::PAGE_ENTRIES {
if let Some(entry) = unsafe { self.entry(i) }
&& entry.present()
{
f(format_args!("{}: {:X}", i, entry.data()));
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
blank_lines_lower_bound = 0 # default
blank_lines_upper_bound = 1 # default
brace_style = "SameLineWhere" # default
disable_all_formatting = false # default
edition = "2024"
style_edition = "2015"
empty_item_single_line = true # default
fn_single_line = false # default
force_explicit_abi = true # default
format_strings = false # default
hard_tabs = false # default
show_parse_errors = true # default
imports_granularity = "Crate" # default = Preserve
imports_indent = "Block" # default
imports_layout = "Mixed" # default
indent_style = "Block" # default
max_width = 100 # default
newline_style = "Unix" # default = Auto
skip_children = false # default
tab_spaces = 4 # default
trailing_comma = "Vertical" # default
where_single_line = false # default
+291
View File
@@ -0,0 +1,291 @@
//! ACPI Firmware ACPI Control Structure (FACS) parser.
//!
//! Per ACPI 6.5 §5.2.10. The FACS contains the firmware waking
//! vector that the platform firmware jumps to on S3 wake.
//! This is the memory location the S3 resume trampoline in
//! `arch/x86_shared/s3_resume.rs` must be written to.
//!
//! This is a comprehensive parser matching Linux 7.1's
//! `struct acpi_table_facs` in `include/acpi/actbl.h`:
//!
//! ```c
//! struct acpi_table_facs {
//! char signature[4]; // ASCII table signature
//! u32 length; // Length of structure, in bytes
//! u32 hardware_signature; // Hardware configuration signature
//! u32 firmware_waking_vector; // 32-bit FW waking vector
//! u32 global_lock; // Global Lock for shared hardware
//! u32 flags; // Flags
//! u64 xfirmware_waking_vector; // 64-bit FW waking vector (ACPI 2.0+)
//! u8 version; // Version of this table (ACPI 2.0+)
//! u8 reserved[3]; // Reserved
//! u32 ospm_flags; // Flags set by OSPM (ACPI 4.0+)
//! u8 reserved1[24]; // Reserved (ACPI 4.0+)
//! };
//! ```
//!
//! We also model the corresponding flag constants:
//! - `ACPI_GLOCK_PENDING` (1 << 0)
//! - `ACPI_GLOCK_OWNED` (1 << 1)
//! - `ACPI_FACS_S4_BIOS_PRESENT` (1 << 0)
//! - `ACPI_FACS_64BIT_WAKE` (1 << 1)
//! - `ACPI_FACS_64BIT_ENVIRONMENT` (1 << 0)
//!
//! Hardware-agnostic: the FACS layout is standardized by the ACPI
//! spec. Only the field values (specifically the waking vector)
//! vary per platform.
use core::sync::atomic::AtomicPtr;
use crate::acpi::sdt::Sdt;
/// Linux 7.1 compatibility: matching `struct acpi_table_facs`.
///
/// The struct is `repr(C, packed)` to match the wire layout
/// exactly. The kernel reads the bytes via direct pointer
/// access.
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct FacsStruct {
/// SDT header (signature, length, revision, checksum,
/// oem_id, oem_table_id, oem_revision, creator_id,
/// creator_revision). Same as other ACPI tables.
pub header: super::sdt::Sdt,
/// Hardware configuration signature. Used by firmware to
/// detect a cold boot vs a resume (the value differs
/// across boots because of system-specific information).
/// Red Bear OS doesn't currently use this — the kernel's
/// S3 magic value is in `s3_resume::S3State.saved_magic`.
pub hardware_signature: u32,
/// 32-bit firmware waking vector. Legacy field used by
/// firmware that runs in 32-bit mode after S3 wake. The
/// platform firmware jumps to this address on a wake
/// event.
pub firmware_waking_vector: u32,
/// Global Lock for shared hardware resources. Acquired by
/// the OS before reading/writing the FACS (or any other
/// ACPI table that requires synchronization with firmware).
/// The kernel currently doesn't use this — we read/write
/// the FACS without taking the global lock. Future work:
/// take the global lock in `read()` and `write()` if
/// multi-core S3 support is added.
pub global_lock: u32,
/// Flags. Bit 0 = S4BIOS support is present. Bit 1 = 64-bit
/// wake vector is supported (ACPI 4.0+).
pub flags: u32,
/// 64-bit firmware waking vector. Used by firmware that
/// runs in 64-bit mode after S3 wake. This is what the
/// kernel's S3 trampoline is written to. (ACPI 2.0+.)
pub xfirmware_waking_vector: u64,
/// FACS version. The FACS was introduced in ACPI 1.0; the
/// 64-bit wake vector was added in ACPI 2.0. (ACPI 2.0+.)
pub version: u8,
/// Reserved. Must be zero. Three bytes.
pub reserved: [u8; 3],
/// OSPM-set flags. Bit 0 = 64-bit wake environment is
/// required (ACPI 4.0+).
pub ospm_flags: u32,
/// Reserved. Must be zero. 24 bytes. (ACPI 4.0+.)
pub reserved1: [u8; 24],
}
/// FACS flag constants (mirrors Linux 7.1's `actbl.h`).
///
/// Used in the `flags` field. Bit 0 = S4BIOS support is
/// present. Bit 1 = 64-bit wake vector is supported.
pub mod facs_flags {
/// `ACPI_FACS_S4_BIOS_PRESENT` (bit 0). The S4BIOS_REQ
/// signal is supported.
pub const S4_BIOS_PRESENT: u32 = 1 << 0;
/// `ACPI_FACS_64BIT_WAKE` (bit 1). The 64-bit wake vector
/// is supported (i.e., `xfirmware_waking_vector` is valid).
pub const WAKE_64BIT: u32 = 1 << 1;
}
/// FACS OSPM flag constants (mirrors Linux 7.1's `actbl.h`).
///
/// Used in the `ospm_flags` field. Bit 0 = 64-bit wake
/// environment is required.
pub mod facs_ospm_flags {
/// `ACPI_FACS_64BIT_ENVIRONMENT` (bit 0). The firmware
/// uses the 64-bit waking vector on S3 wake.
pub const WAKE_64BIT_ENVIRONMENT: u32 = 1 << 0;
}
/// FACS Global Lock flag constants (mirrors Linux 7.1's
/// `actbl.h`). Used in the `global_lock` field.
pub mod facs_glock_flags {
/// `ACPI_GLOCK_PENDING` (bit 0). The global lock is
/// pending (firmware requested it).
pub const PENDING: u32 = 1 << 0;
/// `ACPI_GLOCK_OWNED` (bit 1). The global lock is
/// currently owned.
pub const OWNED: u32 = 1 << 1;
}
/// FACS instance pointer. Set by `init` when the FACS is
/// discovered during ACPI table parsing. Used by the
/// `SetS3WakingVector` AcPiVerb and the `firmware_waking_vector_*`
/// functions below.
static FACS_PTR: AtomicPtr<FacsStruct> = AtomicPtr::new(core::ptr::null_mut());
/// Phase II.X.W: Initialize the FACS parser. Called from the
/// kernel's acpi init after all SDTs are loaded. Reads the
/// FACS from the SDT table and stores the pointer for later
/// use by the `SetS3WakingVector` AcPiVerb.
///
/// # Safety
/// `sdt` must be a valid pointer to a validated SDT whose
/// signature is "FACS" and whose length is at least 64
/// bytes (the minimum size of the FACS structure for ACPI
/// 4.0+ with `ospm_flags` and `reserved1`).
pub fn init(sdt: &Sdt) {
if &sdt.signature != b"FACS" {
return;
}
// The minimum FACS size depends on the version:
// - ACPI 1.0: 32 bytes (just the header + hardware_signature
// + firmware_waking_vector + global_lock)
// - ACPI 2.0: 40 bytes (adds flags + xfirmware_waking_vector)
// - ACPI 4.0: 64 bytes (adds version + reserved + ospm_flags
// + reserved1)
// We require 64 bytes to support all fields.
if sdt.length() < 64 {
return;
}
FACS_PTR.store(
sdt.data_address() as *mut FacsStruct,
core::sync::atomic::Ordering::Release,
);
}
/// Phase II.X.W: Get the FACS structure. Returns `None` if
/// the FACS is not available.
pub fn get() -> Option<&'static FacsStruct> {
let ptr = FACS_PTR.load(core::sync::atomic::Ordering::Acquire);
if ptr.is_null() {
None
} else {
// SAFETY: FACS_PTR was set by `init` after verifying
// sdt.length() >= 64. The pointer is to firmware memory
// which doesn't change.
Some(unsafe { &*ptr })
}
}
/// Phase II.X.W: Read the 32-bit firmware waking vector.
/// Returns 0 if the FACS is not available.
pub fn firmware_waking_vector() -> u32 {
self::get()
.map(|f| f.firmware_waking_vector)
.unwrap_or(0)
}
/// Phase II.X.W: Read the 64-bit firmware waking vector.
/// Returns 0 if the FACS is not available.
pub fn x_firmware_waking_vector() -> u64 {
self::get()
.map(|f| f.xfirmware_waking_vector)
.unwrap_or(0)
}
/// Phase II.X.W: Write the 32-bit firmware waking vector.
/// The kernel's S3 trampoline is written here so the legacy
/// 32-bit firmware wake path works.
///
/// Returns true on success, false if the FACS is not
/// available.
pub fn set_firmware_waking_vector_32(addr: u32) -> bool {
let ptr = FACS_PTR.load(core::sync::atomic::Ordering::Acquire);
if ptr.is_null() {
return false;
}
// SAFETY: FACS_PTR was set by `init` after verifying
// sdt.length() >= 64. The `firmware_waking_vector` field
// is at offset 36 (after the 36-byte SDT header). The
// memory is page-aligned (firmware memory) and writable.
unsafe {
let waking_vector_ptr = (ptr as *mut u8).add(36) as *mut u32;
core::ptr::write_unaligned(waking_vector_ptr, addr);
}
true
}
/// Phase II.X.W: Write the 64-bit firmware waking vector.
/// The kernel's S3 trampoline is written here so the modern
/// 64-bit firmware wake path works.
///
/// Returns true on success, false if the FACS is not
/// available.
pub fn set_x_firmware_waking_vector_64(addr: u64) -> bool {
let ptr = FACS_PTR.load(core::sync::atomic::Ordering::Acquire);
if ptr.is_null() {
return false;
}
// SAFETY: FACS_PTR was set by `init` after verifying
// sdt.length() >= 64. The `xfirmware_waking_vector` field
// is at offset 40. The memory is page-aligned and
// writable.
unsafe {
let x_waking_vector_ptr = (ptr as *mut u8).add(40) as *mut u64;
core::ptr::write_unaligned(x_waking_vector_ptr, addr);
}
true
}
/// Phase II.X.W: Write the 64-bit firmware waking vector
/// (preferred over the 32-bit version on 64-bit systems).
/// Equivalent to `set_x_firmware_waking_vector_64`.
pub fn set_waking_vector(addr: u64) -> bool {
set_x_firmware_waking_vector_64(addr)
}
/// Phase II.X.W: Read the FACS version. Returns 0 if the
/// FACS is not available.
pub fn version() -> u8 {
self::get().map(|f| f.version).unwrap_or(0)
}
/// Phase II.X.W: Read the FACS hardware signature. Returns 0
/// if the FACS is not available.
pub fn hardware_signature() -> u32 {
self::get()
.map(|f| f.hardware_signature)
.unwrap_or(0)
}
/// Phase II.X.W: Read the FACS flags. Returns 0 if the FACS
/// is not available.
///
/// See `facs_flags::*` for the bit definitions.
pub fn flags() -> u32 {
self::get().map(|f| f.flags).unwrap_or(0)
}
/// Phase II.X.W: Read the FACS OSPM flags. Returns 0 if the
/// FACS is not available.
///
/// See `facs_ospm_flags::*` for the bit definitions.
pub fn ospm_flags() -> u32 {
self::get().map(|f| f.ospm_flags).unwrap_or(0)
}
/// Phase II.X.W: Read the FACS global lock. Returns 0 if the
/// FACS is not available.
///
/// See `facs_glock_flags::*` for the bit definitions.
pub fn global_lock() -> u32 {
self::get().map(|f| f.global_lock).unwrap_or(0)
}
/// Phase II.X.W: Read the reserved bytes. Returns `None` if
/// the FACS is not available.
pub fn reserved() -> Option<[u8; 3]> {
self::get().map(|f| f.reserved)
}
/// Phase II.X.W: Read the ACPI 4.0+ reserved bytes. Returns
/// `None` if the FACS is not available.
pub fn reserved1() -> Option<[u8; 24]> {
self::get().map(|f| f.reserved1)
}
+134
View File
@@ -0,0 +1,134 @@
//! ACPI Fixed ACPI Description Table (FADT) parser.
//!
//! Per ACPI 6.5 §5.2.9. The FADT contains the hardware register
//! addresses used by the kernel for ACPI sleep state entry (S3/S5)
//! and the SCI interrupt. This module parses the fields the
//! kernel needs (PM1a_CNT, PM1a_STS for the sleep entry path,
//! and x_firmware_ctrl / firmware_ctrl for the FACS address).
//!
//! Hardware-agnostic: the FADT layout is standardized by the ACPI
//! spec; only the field values vary per platform.
use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU64};
use crate::acpi::sdt::Sdt;
/// Phase II: PM1a_CNT port. Read from the FADT at boot, written
/// by `enter_s3()` to enter S3 (SLP_TYP|SLP_EN bits). Also used
/// by S5 entry (set_global_s_state in acpid).
pub static PM1A_CONTROL_PORT: AtomicU16 = AtomicU16::new(0);
/// Phase II: PM1a_STS port. Used by `enter_s3()` to clear
/// WAK_STS (bit 15) before writing SLP_TYP|SLP_EN.
pub static PM1A_STATUS_PORT: AtomicU16 = AtomicU16::new(0);
/// Phase II.X.W: 32-bit FACS address (FADT offset 36,
/// `firmware_ctrl` field). Used as a fallback when
/// `x_firmware_ctrl` (offset 140, ACPI 2.0+) is not present
/// (i.e., for ACPI 1.0 systems).
pub static FIRMWARE_CTRL: AtomicU32 = AtomicU32::new(0);
/// Phase II.X.W: 64-bit FACS address (FADT offset 140,
/// `x_firmware_ctrl` field, ACPI 2.0+). The kernel's FACS
/// parser uses this to find the FACS for writing the
/// `xfirmware_waking_vector` on S3 entry.
pub static X_FIRMWARE_CTRL: AtomicU64 = AtomicU64::new(0);
/// FADT signature bytes ("FACP").
const FADT_SIGNATURE: [u8; 4] = *b"FACP";
/// FADT fixed offsets for the fields we read. These match
/// the ACPI 6.5 §5.2.9 Table 5.6 layout.
mod offsets {
/// PM1a_STS (PM1 Status Register) Block 0 Address.
/// 32-bit General-Purpose Event Register Block 0 Address.
pub const PM1A_STS: usize = 48;
/// PM1a_CNT (PM1 Control Register) Block 0 Address.
/// 32-bit General-Purpose Event Register Block 0 Address.
pub const PM1A_CNT: usize = 56;
/// `firmware_ctrl`: 32-bit Firmware ACPI Control
/// Structure address. ACPI 1.0+.
pub const FIRMWARE_CTRL_32: usize = 36;
/// `x_firmware_ctrl`: 64-bit Firmware ACPI Control
/// Structure address. ACPI 2.0+.
pub const X_FIRMWARE_CTRL_64: usize = 140;
/// FADT minimum size for ACPI 2.0+ (i.e., enough to
/// include `x_firmware_ctrl` at offset 140). Stored as
/// `u32` to match `Sdt::length()` (which is `u32` per
/// ACPI spec) and avoid a usize/u32 comparison on x86_64.
pub const FADT_MIN_SIZE_ACPI_2_0: u32 = 148;
/// FADT minimum size for ACPI 1.0. Same rationale.
pub const FADT_MIN_SIZE_ACPI_1_0: u32 = 76;
}
/// Parse the FADT from the given SDT bytes and extract the
/// PM1a_CNT, PM1a_STS, and FACS-address fields. Called once at
/// boot after the ACPI table discovery finds the FADT.
///
/// The FADT layout is variable (different sizes for ACPI 1.0 vs
/// 6.5+). We only need the first ~148 bytes which contain the
/// fixed-register addresses. Reference: ACPI 6.5 §5.2.9.
pub fn init(sdt: &Sdt) {
if &sdt.signature != &FADT_SIGNATURE {
return;
}
// SAFETY: We trust the ACPI table discovery code to have
// verified the FADT checksum. The FADT fields are at fixed
// offsets (per the ACPI spec); reading them as u32/u64 is
// safe because all of them are at 4-byte or 8-byte aligned
// offsets on x86_64.
let data = sdt.data_address() as *const u8;
unsafe {
// PM1a_CNT is at offset 56 in the FADT (ACPI 6.5 §5.2.9
// Table 5.6). 32-bit General-Purpose Event Register Block 0
// Address.
let pm1a_cnt = core::ptr::read_unaligned(data.add(offsets::PM1A_CNT) as *const u32);
// PM1a_STS is at offset 48 in the FADT.
let pm1a_sts = core::ptr::read_unaligned(data.add(offsets::PM1A_STS) as *const u32);
// Convert u32 to u16 (port numbers are 16-bit). The low
// 16 bits are the IO port; the high 16 bits are the
// address-space ID which we ignore (always IO on x86).
PM1A_CONTROL_PORT.store(
(pm1a_cnt & 0xFFFF) as u16,
core::sync::atomic::Ordering::Release,
);
PM1A_STATUS_PORT.store(
(pm1a_sts & 0xFFFF) as u16,
core::sync::atomic::Ordering::Release,
);
// Phase II.X.W: 32-bit FACS address (FADT offset 36,
// `firmware_ctrl` field). ACPI 1.0+.
let firmware_ctrl = core::ptr::read_unaligned(
data.add(offsets::FIRMWARE_CTRL_32) as *const u32,
);
FIRMWARE_CTRL.store(firmware_ctrl, core::sync::atomic::Ordering::Release);
// Phase II.X.W: 64-bit FACS address (FADT offset 140,
// `x_firmware_ctrl` field). ACPI 2.0+. We require the
// FADT to be at least 148 bytes to have this field
// (the field is at offset 140, which is 8 bytes for the
// u64, so the minimum FADT size is 148 bytes).
if sdt.length() >= offsets::FADT_MIN_SIZE_ACPI_2_0 {
let x_firmware_ctrl = core::ptr::read_unaligned(
data.add(offsets::X_FIRMWARE_CTRL_64) as *const u64,
);
X_FIRMWARE_CTRL.store(x_firmware_ctrl, core::sync::atomic::Ordering::Release);
}
}
}
/// Phase II.X.W: 32-bit FACS address (FADT offset 36,
/// `firmware_ctrl` field). Returns 0 if the FADT has not
/// been initialized.
pub fn firmware_ctrl() -> u32 {
FIRMWARE_CTRL.load(core::sync::atomic::Ordering::Acquire)
}
/// Phase II.X.W: 64-bit FACS address (FADT offset 140,
/// `x_firmware_ctrl` field). Returns 0 if the FADT has not
/// been initialized or the FADT is too short to have the
/// field.
pub fn x_firmware_ctrl() -> u64 {
X_FIRMWARE_CTRL.load(core::sync::atomic::Ordering::Acquire)
}
+64
View File
@@ -0,0 +1,64 @@
use alloc::boxed::Box;
use super::{find_sdt, sdt::Sdt};
use crate::{
arch::device::generic_timer::GenericTimer,
dtb::irqchip::{register_irq, IRQ_CHIP},
};
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct Gtdt {
pub header: Sdt,
pub cnt_control_base: u64,
_reserved: u32,
pub secure_el1_timer_gsiv: u32,
pub secure_el1_timer_flags: u32,
pub non_secure_el1_timer_gsiv: u32,
pub non_secure_el1_timer_flags: u32,
pub virtual_el1_timer_gsiv: u32,
pub virtual_el1_timer_flags: u32,
pub el2_timer_gsiv: u32,
pub el2_timer_flags: u32,
pub cnt_read_base: u64,
pub platform_timer_count: u32,
pub platform_timer_offset: u32,
/*TODO: we don't need these yet, and they cause short tables to fail parsing
pub virtual_el2_timer_gsiv: u32,
pub virtual_el2_timer_flags: u32,
*/
//TODO: platform timer structure (at platform timer offset, with platform timer count)
}
impl Gtdt {
pub fn init() {
let gtdt_sdt = find_sdt("GTDT");
let gtdt = if gtdt_sdt.len() == 1 {
match Gtdt::new(gtdt_sdt[0]) {
Some(gtdt) => gtdt,
None => {
warn!("Failed to parse GTDT");
return;
}
}
} else {
warn!("Unable to find GTDT");
return;
};
let gsiv = gtdt.non_secure_el1_timer_gsiv;
info!("generic_timer gsiv = {}", gsiv);
let mut timer = GenericTimer::new();
timer.init();
register_irq(gsiv, Box::new(timer));
unsafe { IRQ_CHIP.irq_enable(gsiv as u32) };
}
pub fn new(sdt: &'static Sdt) -> Option<&'static Gtdt> {
if &sdt.signature == b"GTDT" && sdt.length as usize >= size_of::<Gtdt>() {
Some(unsafe { &*((sdt as *const Sdt) as *const Gtdt) })
} else {
None
}
}
}
+121
View File
@@ -0,0 +1,121 @@
use core::ptr::{self, read_volatile, write_volatile};
#[cfg(not(target_arch = "x86"))]
use crate::memory::{RmmA, RmmArch};
use crate::{find_one_sdt, memory::PhysicalAddress};
use super::{sdt::Sdt, GenericAddressStructure, ACPI_TABLE};
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct Hpet {
pub header: Sdt,
pub hw_rev_id: u8,
pub comparator_descriptor: u8,
pub pci_vendor_id: u16,
pub base_address: GenericAddressStructure,
pub hpet_number: u8,
pub min_periodic_clk_tick: u16,
pub oem_attribute: u8,
}
impl Hpet {
pub fn init() {
let hpet = Hpet::new(find_one_sdt!("HPET"));
if let Some(hpet) = hpet {
debug!(" HPET: {:X}", hpet.hpet_number);
let mut hpet_t = ACPI_TABLE.hpet.write();
*hpet_t = Some(hpet);
}
}
pub fn new(sdt: &'static Sdt) -> Option<Hpet> {
if &sdt.signature == b"HPET" && sdt.length as usize >= size_of::<Hpet>() {
let s = unsafe { ptr::read((sdt as *const Sdt) as *const Hpet) };
if s.base_address.address_space == 0 {
unsafe { s.map() };
Some(s)
} else {
warn!(
"HPET has unsupported address space {}",
s.base_address.address_space
);
None
}
} else {
None
}
}
}
//TODO: x86 use assumes only one HPET and only one GenericAddressStructure
#[cfg(target_arch = "x86")]
impl Hpet {
pub unsafe fn map(&self) {
unsafe {
use crate::memory::{Frame, KernelMapper, Page, PageFlags, VirtualAddress};
let frame = Frame::containing(PhysicalAddress::new(self.base_address.address as usize));
let page = Page::containing_address(VirtualAddress::new(crate::HPET_OFFSET));
KernelMapper::lock_rw()
.map_phys(
page.start_address(),
frame.base(),
PageFlags::new().write(true).device_memory(true),
)
.expect("failed to map memory for GenericAddressStructure")
.flush();
}
}
pub unsafe fn read_u64(&self, offset: usize) -> u64 {
unsafe { read_volatile((crate::HPET_OFFSET + offset) as *const u64) }
}
pub unsafe fn write_u64(&mut self, offset: usize, value: u64) {
unsafe {
write_volatile((crate::HPET_OFFSET + offset) as *mut u64, value);
}
}
}
#[cfg(not(target_arch = "x86"))]
impl Hpet {
pub unsafe fn map(&self) {
unsafe {
crate::memory::map_device_memory(
PhysicalAddress::new(self.base_address.address as usize),
crate::memory::PAGE_SIZE,
);
}
}
pub unsafe fn read_u64(&self, offset: usize) -> u64 {
unsafe {
read_volatile(
RmmA::phys_to_virt(PhysicalAddress::new(
self.base_address.address as usize + offset,
))
.data() as *const u64,
)
}
}
pub unsafe fn write_u64(&mut self, offset: usize, value: u64) {
unsafe {
write_volatile(
RmmA::phys_to_virt(PhysicalAddress::new(
self.base_address.address as usize + offset,
))
.data() as *mut u64,
value,
);
}
}
}
+97
View File
@@ -0,0 +1,97 @@
use alloc::{boxed::Box, vec::Vec};
use super::{Madt, MadtEntry};
use crate::{
arch::device::irqchip::{
gic::{GenericInterruptController, GicCpuIf, GicDistIf},
gicv3::{GicV3, GicV3CpuIf},
},
dtb::irqchip::{IrqChipItem, IRQ_CHIP},
memory::{map_device_memory, PhysicalAddress, PAGE_SIZE},
};
pub(super) fn init(madt: Madt) {
let mut gicd_opt = None;
let mut giccs = Vec::new();
for madt_entry in madt.iter() {
debug!(" {:#x?}", madt_entry);
match madt_entry {
MadtEntry::Gicc(gicc) => {
giccs.push(gicc);
}
MadtEntry::Gicd(gicd) => {
if gicd_opt.is_some() {
warn!("Only one GICD should be present on a system, ignoring this one");
} else {
gicd_opt = Some(gicd);
}
}
_ => {}
}
}
let Some(gicd) = gicd_opt else {
warn!("No GICD found");
return;
};
let mut gic_dist_if = GicDistIf::default();
unsafe {
let phys = PhysicalAddress::new(gicd.physical_base_address as usize);
let virt = map_device_memory(phys, PAGE_SIZE);
gic_dist_if.init(virt.data());
};
info!("{:#x?}", gic_dist_if);
match gicd.gic_version {
1 | 2 => {
for gicc in giccs {
let mut gic_cpu_if = GicCpuIf::default();
unsafe {
let phys = PhysicalAddress::new(gicc.physical_base_address as usize);
let virt = map_device_memory(phys, PAGE_SIZE);
gic_cpu_if.init(virt.data())
};
info!("{:#x?}", gic_cpu_if);
let gic = GenericInterruptController {
gic_dist_if,
gic_cpu_if,
irq_range: (0, 0),
};
let chip = IrqChipItem {
phandle: 0,
parents: Vec::new(),
children: Vec::new(),
ic: Box::new(gic),
};
unsafe { IRQ_CHIP.irq_chip_list.chips.push(chip) };
//TODO: support more GICCs
break;
}
}
3 => {
for gicc in giccs {
let mut gic_cpu_if = GicV3CpuIf;
unsafe { gic_cpu_if.init() };
info!("{:#x?}", gic_cpu_if);
let gic = GicV3 {
gic_dist_if,
gic_cpu_if,
//TODO: get GICRs
gicrs: Vec::new(),
irq_range: (0, 0),
};
let chip = IrqChipItem {
phandle: 0,
parents: Vec::new(),
children: Vec::new(),
ic: Box::new(gic),
};
unsafe { IRQ_CHIP.irq_chip_list.chips.push(chip) };
//TODO: support more GICCs
break;
}
}
_ => {
warn!("unsupported GIC version {}", gicd.gic_version);
}
}
unsafe { IRQ_CHIP.init(None) };
}
+9
View File
@@ -0,0 +1,9 @@
use super::Madt;
pub(super) fn init(madt: Madt) {
for madt_entry in madt.iter() {
debug!(" {:#x?}", madt_entry);
}
warn!("MADT not yet handled on this platform");
}
+160
View File
@@ -0,0 +1,160 @@
use core::{
hint,
sync::atomic::{AtomicU8, Ordering},
};
use crate::{
arch::{
device::local_apic::the_local_apic,
start::{kstart_ap, KernelArgsAp},
},
cpu_set::LogicalCpuId,
memory::{
allocate_p2frame, Frame, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch,
VirtualAddress, PAGE_SIZE,
},
startup::AP_READY,
};
use super::{Madt, MadtEntry};
const TRAMPOLINE: usize = 0x8000;
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
pub(super) fn init(madt: Madt) {
let local_apic = unsafe { the_local_apic() };
let me = local_apic.id();
if local_apic.x2 {
debug!(" X2APIC {}", me.get());
} else {
debug!(" XAPIC {}: {:>08X}", me.get(), local_apic.address);
}
if cfg!(not(feature = "multi_core")) {
return;
}
// Map trampoline
let trampoline_frame = Frame::containing(PhysicalAddress::new(TRAMPOLINE));
let trampoline_page = Page::containing_address(VirtualAddress::new(TRAMPOLINE));
let (result, page_table_physaddr) = unsafe {
//TODO: do not have writable and executable!
let mut mapper = KernelMapper::lock_rw();
let result = mapper
.map_phys(
trampoline_page.start_address(),
trampoline_frame.base(),
PageFlags::new().execute(true).write(true),
)
.expect("failed to map trampoline");
(result, mapper.table().phys().data())
};
result.flush();
// Write trampoline, make sure TRAMPOLINE page is free for use
for (i, val) in TRAMPOLINE_DATA.iter().enumerate() {
unsafe {
(*((TRAMPOLINE as *mut u8).add(i) as *const AtomicU8)).store(*val, Ordering::SeqCst);
}
}
unsafe {
let preliminary_cpu_count = madt.iter().filter(|e| matches!(e, MadtEntry::LocalApic(entry) if u32::from(entry.id) == me.get() || entry.flags & 1 == 1)).count();
crate::profiling::allocate(preliminary_cpu_count as u32);
}
for madt_entry in madt.iter() {
debug!(" {:x?}", madt_entry);
if let MadtEntry::LocalApic(ap_local_apic) = madt_entry {
if u32::from(ap_local_apic.id) == me.get() {
debug!(" This is my local APIC");
} else if ap_local_apic.flags & 1 == 1 {
let cpu_id = LogicalCpuId::next();
// Allocate a stack
let stack_start = RmmA::phys_to_virt(
allocate_p2frame(4)
.expect("no more frames in acpi stack_start")
.base(),
)
.data();
let stack_end = stack_start + (PAGE_SIZE << 4);
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
let args = KernelArgsAp {
stack_end: stack_end as *mut u8,
cpu_id,
pcr_ptr,
idt_ptr,
};
let ap_ready = (TRAMPOLINE + 8) as *mut u64;
let ap_args_ptr = unsafe { ap_ready.add(1) };
let ap_page_table = unsafe { ap_ready.add(2) };
let ap_code = unsafe { ap_ready.add(3) };
// Set the ap_ready to 0, volatile
unsafe {
ap_ready.write(0);
ap_args_ptr.write(&args as *const _ as u64);
ap_page_table.write(page_table_physaddr as u64);
#[expect(clippy::fn_to_numeric_cast)]
ap_code.write(kstart_ap as u64);
// TODO: Is this necessary (this fence)?
core::arch::asm!("");
};
AP_READY.store(false, Ordering::SeqCst);
// Send INIT IPI
{
let mut icr = 0x4500;
if local_apic.x2 {
icr |= u64::from(ap_local_apic.id) << 32;
} else {
icr |= u64::from(ap_local_apic.id) << 56;
}
local_apic.set_icr(icr);
}
// Send START IPI
{
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
let mut icr = 0x4600 | ap_segment as u64;
if local_apic.x2 {
icr |= u64::from(ap_local_apic.id) << 32;
} else {
icr |= u64::from(ap_local_apic.id) << 56;
}
local_apic.set_icr(icr);
}
// Wait for trampoline ready
while unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } == 0 {
hint::spin_loop();
}
while !AP_READY.load(Ordering::SeqCst) {
hint::spin_loop();
}
RmmA::invalidate_all();
}
}
}
// Unmap trampoline
let (_frame, _, flush) = unsafe {
KernelMapper::lock_rw()
.unmap_phys(trampoline_page.start_address())
.expect("failed to unmap trampoline page")
};
flush.flush();
}
+240
View File
@@ -0,0 +1,240 @@
use core::cell::SyncUnsafeCell;
use super::sdt::Sdt;
use crate::find_one_sdt;
/// The Multiple APIC Descriptor Table
#[derive(Clone, Copy, Debug)]
pub struct Madt {
sdt: &'static Sdt,
pub local_address: u32,
pub flags: u32,
}
#[cfg(target_arch = "aarch64")]
#[path = "arch/aarch64.rs"]
mod arch;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[path = "arch/x86.rs"]
mod arch;
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))]
#[path = "arch/other.rs"]
mod arch;
static MADT: SyncUnsafeCell<Option<Madt>> = SyncUnsafeCell::new(None);
pub fn madt() -> Option<&'static Madt> {
unsafe { &*MADT.get() }.as_ref()
}
pub const FLAG_PCAT: u32 = 1;
impl Madt {
pub fn init() {
let madt = Madt::new(find_one_sdt!("APIC"));
if let Some(madt) = madt {
// safe because no APs have been started yet.
unsafe { MADT.get().write(Some(madt)) };
debug!(" APIC: {:>08X}: {}", madt.local_address, madt.flags);
arch::init(madt);
}
}
pub fn new(sdt: &'static Sdt) -> Option<Madt> {
if &sdt.signature == b"APIC" && sdt.data_len() >= 8 {
//Not valid if no local address and flags
let local_address = unsafe { (sdt.data_address() as *const u32).read_unaligned() };
let flags = unsafe {
(sdt.data_address() as *const u32)
.offset(1)
.read_unaligned()
};
Some(Madt {
sdt,
local_address,
flags,
})
} else {
None
}
}
pub fn iter(&self) -> MadtIter {
MadtIter {
sdt: self.sdt,
i: 8, // Skip local controller address and flags
}
}
}
/// MADT Local APIC
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtLocalApic {
/// Processor ID
pub processor: u8,
/// Local APIC ID
pub id: u8,
/// Flags. 1 means that the processor is enabled
pub flags: u32,
}
/// MADT I/O APIC
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtIoApic {
/// I/O APIC ID
pub id: u8,
/// reserved
_reserved: u8,
/// I/O APIC address
pub address: u32,
/// Global system interrupt base
pub gsi_base: u32,
}
/// MADT Interrupt Source Override
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtIntSrcOverride {
/// Bus Source
pub bus_source: u8,
/// IRQ Source
pub irq_source: u8,
/// Global system interrupt base
pub gsi_base: u32,
/// Flags
pub flags: u16,
}
/// MADT GICC
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtGicc {
_reserved: u16,
pub cpu_interface_number: u32,
pub acpi_processor_uid: u32,
pub flags: u32,
pub parking_protocol_version: u32,
pub performance_interrupt_gsiv: u32,
pub parked_address: u64,
pub physical_base_address: u64,
pub gicv: u64,
pub gich: u64,
pub vgic_maintenance_interrupt: u32,
pub gicr_base_address: u64,
pub mpidr: u64,
pub processor_power_efficiency_class: u8,
_reserved2: u8,
pub spe_overflow_interrupt: u16,
//TODO: optional field introduced in ACPI 6.5: pub trbe_interrupt: u16,
}
/// MADT GICD
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtGicd {
_reserved: u16,
pub gic_id: u32,
pub physical_base_address: u64,
pub system_vector_base: u32,
pub gic_version: u8,
_reserved2: [u8; 3],
}
/// MADT Entries
#[derive(Debug)]
#[allow(dead_code)]
pub enum MadtEntry {
LocalApic(&'static MadtLocalApic),
InvalidLocalApic(usize),
IoApic(&'static MadtIoApic),
InvalidIoApic(usize),
IntSrcOverride(&'static MadtIntSrcOverride),
InvalidIntSrcOverride(usize),
Gicc(&'static MadtGicc),
InvalidGicc(usize),
Gicd(&'static MadtGicd),
InvalidGicd(usize),
Unknown(u8),
}
pub struct MadtIter {
sdt: &'static Sdt,
i: usize,
}
impl Iterator for MadtIter {
type Item = MadtEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.i + 1 < self.sdt.data_len() {
let entry_type = unsafe { *(self.sdt.data_address() as *const u8).add(self.i) };
let entry_len =
unsafe { *(self.sdt.data_address() as *const u8).add(self.i + 1) } as usize;
if self.i + entry_len <= self.sdt.data_len() {
let item = match entry_type {
0x0 => {
if entry_len == size_of::<MadtLocalApic>() + 2 {
MadtEntry::LocalApic(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtLocalApic)
})
} else {
MadtEntry::InvalidLocalApic(entry_len)
}
}
0x1 => {
if entry_len == size_of::<MadtIoApic>() + 2 {
MadtEntry::IoApic(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtIoApic)
})
} else {
MadtEntry::InvalidIoApic(entry_len)
}
}
0x2 => {
if entry_len == size_of::<MadtIntSrcOverride>() + 2 {
MadtEntry::IntSrcOverride(unsafe {
&*((self.sdt.data_address() + self.i + 2)
as *const MadtIntSrcOverride)
})
} else {
MadtEntry::InvalidIntSrcOverride(entry_len)
}
}
0xB => {
if entry_len >= size_of::<MadtGicc>() + 2 {
MadtEntry::Gicc(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtGicc)
})
} else {
MadtEntry::InvalidGicc(entry_len)
}
}
0xC => {
if entry_len >= size_of::<MadtGicd>() + 2 {
MadtEntry::Gicd(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtGicd)
})
} else {
MadtEntry::InvalidGicd(entry_len)
}
}
_ => MadtEntry::Unknown(entry_type),
};
self.i += entry_len;
Some(item)
} else {
None
}
} else {
None
}
}
}
+255
View File
@@ -0,0 +1,255 @@
//! # ACPI
//! Code to parse the ACPI tables
use alloc::{boxed::Box, string::String, vec::Vec};
use core::ptr::NonNull;
use hashbrown::HashMap;
use spin::{Once, RwLock};
use crate::memory::{KernelMapper, PageFlags, PhysicalAddress, RmmA, RmmArch};
use self::{hpet::Hpet, madt::Madt, rsdp::Rsdp, rsdt::Rsdt, rxsdt::Rxsdt, sdt::Sdt, xsdt::Xsdt};
#[cfg(target_arch = "aarch64")]
mod gtdt;
pub mod fadt;
pub mod facs;
pub mod hpet;
pub mod madt;
mod rsdp;
mod rsdt;
mod rxsdt;
pub mod sdt;
#[cfg(target_arch = "aarch64")]
mod spcr;
mod xsdt;
unsafe fn map_linearly(addr: PhysicalAddress, len: usize, mapper: &mut crate::memory::PageMapper) {
unsafe {
let base = PhysicalAddress::new(crate::memory::round_down_pages(addr.data()));
let aligned_len = crate::memory::round_up_pages(len + (addr.data() - base.data()));
for page_idx in 0..aligned_len / crate::memory::PAGE_SIZE {
let (_, flush) = mapper
.map_linearly(
base.add(page_idx * crate::memory::PAGE_SIZE),
PageFlags::new(),
)
.expect("failed to linearly map SDT");
flush.flush();
}
}
}
pub fn get_sdt(sdt_address: PhysicalAddress, mapper: &mut KernelMapper<true>) -> &'static Sdt {
let sdt;
unsafe {
const SDT_SIZE: usize = size_of::<Sdt>();
map_linearly(sdt_address, SDT_SIZE, mapper);
sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt);
map_linearly(
sdt_address.add(SDT_SIZE),
sdt.length as usize - SDT_SIZE,
mapper,
);
}
sdt
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
pub struct GenericAddressStructure {
pub address_space: u8,
pub bit_width: u8,
pub bit_offset: u8,
pub access_size: u8,
pub address: u64,
}
pub enum RxsdtEnum {
Rsdt(Rsdt),
Xsdt(Xsdt),
}
impl Rxsdt for RxsdtEnum {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>> {
match self {
Self::Rsdt(rsdt) => <Rsdt as Rxsdt>::iter(rsdt),
Self::Xsdt(xsdt) => <Xsdt as Rxsdt>::iter(xsdt),
}
}
}
pub static RXSDT_ENUM: Once<RxsdtEnum> = Once::new();
/// Parse the ACPI tables to gather CPU, interrupt, and timer information
pub unsafe fn init(already_supplied_rsdp: Option<NonNull<u8>>) {
unsafe {
{
let mut sdt_ptrs = SDT_POINTERS.write();
*sdt_ptrs = Some(HashMap::new());
}
// Search for RSDP
let rsdp_opt = Rsdp::get_rsdp(already_supplied_rsdp);
if let Some(rsdp) = rsdp_opt {
debug!("SDT address: {:#x}", rsdp.sdt_address().data());
let rxsdt = get_sdt(rsdp.sdt_address(), &mut KernelMapper::lock_rw());
let rxsdt = if let Some(rsdt) = Rsdt::new(rxsdt) {
let mut initialized = false;
let rsdt = RXSDT_ENUM.call_once(|| {
initialized = true;
RxsdtEnum::Rsdt(rsdt)
});
if !initialized {
error!("RXSDT_ENUM already initialized");
}
rsdt
} else if let Some(xsdt) = Xsdt::new(rxsdt) {
let mut initialized = false;
let xsdt = RXSDT_ENUM.call_once(|| {
initialized = true;
RxsdtEnum::Xsdt(xsdt)
});
if !initialized {
error!("RXSDT_ENUM already initialized");
}
xsdt
} else {
warn!("UNKNOWN RSDT OR XSDT SIGNATURE");
return;
};
// TODO: Don't touch ACPI tables in kernel?
for sdt in rxsdt.iter() {
get_sdt(sdt, &mut KernelMapper::lock_rw());
}
for sdt_address in rxsdt.iter() {
let sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt);
let signature = get_sdt_signature(sdt);
if let Some(ref mut ptrs) = *(SDT_POINTERS.write()) {
ptrs.insert(signature, sdt);
}
}
// TODO: Enumerate processors in userspace, and then provide an ACPI-independent interface
// to initialize enumerated processors to userspace?
Madt::init();
//TODO: support this on any arch
// SPCR must be initialized after MADT for interrupt controllers
#[cfg(target_arch = "aarch64")]
spcr::Spcr::init();
// TODO: Let userspace setup HPET, and then provide an interface to specify which timer to
// use?
Hpet::init();
#[cfg(target_arch = "aarch64")]
gtdt::Gtdt::init();
// Phase II: parse the FADT to extract the PM1a_CNT
// and PM1a_STS port addresses used by the S3 entry
// path. Hardware-agnostic — works on any platform
// with a working FADT.
if let Some(fadt_sdts) = find_sdt("FACP").first() {
fadt::init(fadt_sdts);
} else {
warn!("ACPI: no FADT (FACP) found, S3 entry path disabled");
}
// Phase II.X.W: parse the FACS to extract the
// xfirmware_waking_vector. This is the address the
// platform firmware jumps to on S3 wake. The kernel's
// S3 resume trampoline in arch/x86_shared/s3_resume.rs
// is written to this address by acpid via the
// SetS3WakingVector AcPiVerb.
//
// The FACS is found via the FADT's x_firmware_ctrl
// field (64-bit) or firmware_ctrl field (32-bit).
// The FADT parser caches the FACS address. We use
// the FADT's x_firmware_ctrl to find the FACS SDT.
let facs_addr = fadt::x_firmware_ctrl();
if facs_addr != 0 {
// SAFETY: The FACS address is a physical
// address stored in the FADT. The boot-time page
// table maps the FACS into the kernel's address
// space (firmware tables are below 4GB on x86_64).
let facs_sdt = unsafe { &*(facs_addr as *const Sdt) };
facs::init(facs_sdt);
} else {
let facs_addr = fadt::firmware_ctrl() as u64;
if facs_addr != 0 {
// SAFETY: same as above.
let facs_sdt =
unsafe { &*(facs_addr as *const Sdt) };
facs::init(facs_sdt);
} else {
warn!("ACPI: no FACS found (neither x_firmware_ctrl nor firmware_ctrl), S3 resume path disabled");
}
}
} else {
error!("NO RSDP FOUND");
}
}
}
pub type SdtSignature = (String, [u8; 6], [u8; 8]);
pub static SDT_POINTERS: RwLock<Option<HashMap<SdtSignature, &'static Sdt>>> = RwLock::new(None);
pub fn find_sdt(name: &str) -> Vec<&'static Sdt> {
let mut sdts: Vec<&'static Sdt> = vec![];
if let Some(ref ptrs) = *(SDT_POINTERS.read()) {
for (signature, sdt) in ptrs {
if signature.0 == name {
sdts.push(sdt);
}
}
}
sdts
}
#[macro_export]
macro_rules! find_one_sdt {
($name:expr) => {{
use $crate::acpi::find_sdt;
match find_sdt($name).as_slice() {
[] => {
println!("Unable to find {}", $name);
return;
}
[x] => *x,
x => {
println!("{} {} found, expected 1", x.len(), $name);
return;
}
}
}};
}
pub fn get_sdt_signature(sdt: &'static Sdt) -> SdtSignature {
let signature =
String::from_utf8(sdt.signature.to_vec()).expect("Error converting signature to string");
(signature, sdt.oem_id, sdt.oem_table_id)
}
pub struct Acpi {
pub hpet: RwLock<Option<Hpet>>,
}
pub static ACPI_TABLE: Acpi = Acpi {
hpet: RwLock::new(None),
};
+62
View File
@@ -0,0 +1,62 @@
use core::ptr::NonNull;
use rmm::PhysicalAddress;
/// RSDP
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Rsdp {
signature: [u8; 8],
_checksum: u8,
_oemid: [u8; 6],
revision: u8,
rsdt_address: u32,
length: u32,
xsdt_address: u64,
_extended_checksum: u8,
_reserved: [u8; 3],
}
impl Rsdp {
pub unsafe fn get_rsdp(already_supplied_rsdp: Option<NonNull<u8>>) -> Option<Rsdp> {
already_supplied_rsdp.and_then(|rsdp_ptr: NonNull<u8>| {
let rsdp: Rsdp = unsafe { rsdp_ptr.cast().read() };
if rsdp.signature != *b"RSD PTR " {
error!("RSDP signature check failed");
return None;
}
let mut sum: u8 = 0;
for i in 0..20 {
sum = sum.wrapping_add(unsafe { rsdp_ptr.add(i).read() });
}
if sum != 0 {
error!("RSDP checksum failed");
return None;
}
if rsdp.revision >= 2 {
let mut sum: u8 = 0;
for i in 0..rsdp.length as usize {
sum = sum.wrapping_add(unsafe { rsdp_ptr.add(i).read() });
}
if sum != 0 {
error!("XSDP checksum failed");
return None;
}
}
Some(rsdp)
})
}
/// Get the RSDT or XSDT address
pub fn sdt_address(&self) -> PhysicalAddress {
PhysicalAddress::new(if self.revision >= 2 {
self.xsdt_address as usize
} else {
self.rsdt_address as usize
})
}
}
+52
View File
@@ -0,0 +1,52 @@
use alloc::boxed::Box;
use core::convert::TryFrom;
use rmm::PhysicalAddress;
use super::{rxsdt::Rxsdt, sdt::Sdt};
#[derive(Debug)]
pub struct Rsdt(&'static Sdt);
impl Rsdt {
pub fn new(sdt: &'static Sdt) -> Option<Rsdt> {
if &sdt.signature == b"RSDT" {
Some(Rsdt(sdt))
} else {
None
}
}
pub fn as_slice(&self) -> &[u8] {
let length =
usize::try_from(self.0.length).expect("expected 32-bit length to fit within usize");
unsafe { core::slice::from_raw_parts(self.0 as *const _ as *const u8, length) }
}
}
impl Rxsdt for Rsdt {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>> {
Box::new(RsdtIter { sdt: self.0, i: 0 })
}
}
pub struct RsdtIter {
sdt: &'static Sdt,
i: usize,
}
impl Iterator for RsdtIter {
type Item = PhysicalAddress;
fn next(&mut self) -> Option<Self::Item> {
if self.i < self.sdt.data_len() / size_of::<u32>() {
let item = unsafe {
(self.sdt.data_address() as *const u32)
.add(self.i)
.read_unaligned()
};
self.i += 1;
Some(PhysicalAddress::new(item as usize))
} else {
None
}
}
}
+6
View File
@@ -0,0 +1,6 @@
use alloc::boxed::Box;
use rmm::PhysicalAddress;
pub trait Rxsdt {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>>;
}
+42
View File
@@ -0,0 +1,42 @@
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Sdt {
pub signature: [u8; 4],
pub length: u32,
pub revision: u8,
pub checksum: u8,
pub oem_id: [u8; 6],
pub oem_table_id: [u8; 8],
pub oem_revision: u32,
pub creator_id: u32,
pub creator_revision: u32,
}
impl Sdt {
/// Get the address of this tables data
pub fn data_address(&self) -> usize {
self as *const _ as usize + size_of::<Sdt>()
}
/// Get the total length of the table (including the SDT
/// header), in bytes. The SDT is `#[repr(C, packed)]` so
/// direct field access requires an unaligned read.
pub fn length(&self) -> u32 {
// SAFETY: The Sdt is `#[repr(C, packed)]` and the
// `length` field is at offset 4 (after the 4-byte
// signature), aligned to a 4-byte boundary. The address
// is a valid pointer to the SDT; reading 4 bytes from
// offset 4 is safe.
unsafe {
let p = self as *const Self as *const u8;
core::ptr::read_unaligned(p.add(4) as *const u32)
}
}
/// Get the length of this tables data
pub fn data_len(&self) -> usize {
let total_size = self.length as usize;
let header_size = size_of::<Sdt>();
total_size.saturating_sub(header_size)
}
}
+140
View File
@@ -0,0 +1,140 @@
use super::{find_sdt, sdt::Sdt, GenericAddressStructure};
use crate::{
arch::device::serial::COM1,
devices::{serial::SerialKind, uart_pl011},
log::LOG,
memory::{map_device_memory, PhysicalAddress, PAGE_SIZE},
};
const INTERRUPT_TYPE_8259: u8 = 1 << 0;
const INTERRUPT_TYPE_APIC: u8 = 1 << 1;
const INTERRUPT_TYPE_SAPIC: u8 = 1 << 2;
const INTERRUPT_TYPE_GIC: u8 = 1 << 3;
const INTERRUPT_TYPE_PLIC: u8 = 1 << 4;
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct Spcr {
pub header: Sdt,
pub interface_type: u8,
_reserved: [u8; 3],
pub base_address: GenericAddressStructure,
pub interrupt_type: u8,
pub irq: u8,
pub gsiv: u32,
pub configured_baud_rate: u8,
pub parity: u8,
pub stop_bits: u8,
pub flow_control: u8,
pub terminal_type: u8,
pub language: u8,
pub pci_device_id: u16,
pub pci_vendor_id: u16,
pub pci_bus: u8,
pub pci_device: u8,
pub pci_function: u8,
pub pci_flags: u32,
pub pci_segment: u8,
/*TODO: these fields are optional based on the table revision
pub uart_clock_frequency: u32,
pub precise_baud_rate: u32,
pub namespace_string_length: u16,
pub namespace_string_offset: u16,
*/
// namespace_string
}
impl Spcr {
pub fn init() {
let spcr_sdt = find_sdt("SPCR");
let spcr = if spcr_sdt.len() == 1 {
match Spcr::new(spcr_sdt[0]) {
Some(spcr) => spcr,
None => {
warn!("Failed to parse SPCR");
return;
}
}
} else {
warn!("Unable to find SPCR");
return;
};
if spcr.base_address.address == 0 {
// Serial disabled
return;
}
let serial_was_empty = !matches!(*COM1.lock(), SerialKind::NotPresent);
if spcr.header.revision >= 2 {
match spcr.interface_type {
3 => {
// PL011
if spcr.base_address.address_space == 0
&& spcr.base_address.bit_width == 32
&& spcr.base_address.bit_offset == 0
&& spcr.base_address.access_size == 3
{
let virt = unsafe {
map_device_memory(
PhysicalAddress::new(spcr.base_address.address as usize),
PAGE_SIZE,
)
};
let serial_port = uart_pl011::SerialPort::new(virt.data(), false);
*COM1.lock() = SerialKind::Pl011(serial_port);
//TODO: enable IRQ on more platforms and interrupt types
if (spcr.interrupt_type & INTERRUPT_TYPE_GIC) == INTERRUPT_TYPE_GIC {
#[cfg(target_arch = "aarch64")]
unsafe {
crate::arch::device::serial::init_acpi(spcr.gsiv);
}
}
} else {
warn!(
"SPCR unsuppoted address for PL011 {:#x?}",
spcr.base_address
);
}
}
//TODO: support more types!
unsupported => {
warn!(
"SPCR revision {} unsupported interface type {}",
spcr.header.revision, unsupported
);
}
}
} else if spcr.header.revision == 1 {
match spcr.interface_type {
//TODO: support more types!
unsupported => {
warn!("SPCR revision 1 unsupported interface type {}", unsupported);
}
}
} else {
warn!("SPCR unsupported revision {}", spcr.header.revision);
}
let mut serial_port = COM1.lock();
if serial_was_empty && !matches!(*serial_port, SerialKind::NotPresent) {
// backfill logs since the heap is loaded
if let Some(ref mut early_log) = *LOG.lock() {
let (s1, s2) = early_log.read();
if !s1.is_empty() {
serial_port.write(s1);
}
if !s2.is_empty() {
serial_port.write(s2);
}
}
}
}
pub fn new(sdt: &'static Sdt) -> Option<&'static Spcr> {
if &sdt.signature == b"SPCR" && sdt.length as usize >= size_of::<Spcr>() {
Some(unsafe { &*((sdt as *const Sdt) as *const Spcr) })
} else {
None
}
}
}
+50
View File
@@ -0,0 +1,50 @@
use alloc::boxed::Box;
use core::convert::TryFrom;
use rmm::PhysicalAddress;
use super::{rxsdt::Rxsdt, sdt::Sdt};
#[derive(Debug)]
pub struct Xsdt(&'static Sdt);
impl Xsdt {
pub fn new(sdt: &'static Sdt) -> Option<Xsdt> {
if &sdt.signature == b"XSDT" {
Some(Xsdt(sdt))
} else {
None
}
}
pub fn as_slice(&self) -> &[u8] {
let length =
usize::try_from(self.0.length).expect("expected 32-bit length to fit within usize");
unsafe { core::slice::from_raw_parts(self.0 as *const _ as *const u8, length) }
}
}
impl Rxsdt for Xsdt {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>> {
Box::new(XsdtIter { sdt: self.0, i: 0 })
}
}
pub struct XsdtIter {
sdt: &'static Sdt,
i: usize,
}
impl Iterator for XsdtIter {
type Item = PhysicalAddress;
fn next(&mut self) -> Option<Self::Item> {
if self.i < self.sdt.data_len() / size_of::<u64>() {
let item = unsafe {
core::ptr::read_unaligned((self.sdt.data_address() as *const u64).add(self.i))
};
self.i += 1;
Some(PhysicalAddress::new(item as usize))
} else {
None
}
}
}
+50
View File
@@ -0,0 +1,50 @@
use crate::memory::KernelMapper;
use core::{
alloc::{GlobalAlloc, Layout},
ptr::NonNull,
};
use linked_list_allocator::Heap;
use spin::Mutex;
static HEAP: Mutex<Option<Heap>> = Mutex::new(None);
pub struct Allocator;
impl Allocator {
pub unsafe fn init(offset: usize, size: usize) {
unsafe {
*HEAP.lock() = Some(Heap::new(offset, size));
}
}
}
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe {
while let Some(ref mut heap) = *HEAP.lock() {
match heap.allocate_first_fit(layout) {
Ok(ptr) => return ptr.as_ptr(),
Err(()) => {
let size = heap.size();
super::map_heap(
&mut KernelMapper::lock_rw(),
crate::kernel_heap_offset() + size,
super::KERNEL_HEAP_SIZE,
);
heap.extend(super::KERNEL_HEAP_SIZE);
}
}
}
panic!("__rust_allocate: heap not initialized");
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe {
HEAP.lock()
.as_mut()
.expect("heap not initialized")
.deallocate(NonNull::new_unchecked(ptr), layout)
}
}
}
+48
View File
@@ -0,0 +1,48 @@
use crate::memory::{KernelMapper, Page, PageFlags, VirtualAddress};
use rmm::{Flusher, FrameAllocator, PageFlushAll};
pub use self::linked_list::Allocator;
mod linked_list;
/// Size of kernel heap
const KERNEL_HEAP_SIZE: usize = ::rmm::MEGABYTE;
unsafe fn map_heap(mapper: &mut KernelMapper<true>, offset: usize, size: usize) {
let mut flush_all = PageFlushAll::new();
let heap_start_page = Page::containing_address(VirtualAddress::new(offset));
let heap_end_page = Page::containing_address(VirtualAddress::new(offset + size - 1));
for page in Page::range_inclusive(heap_start_page, heap_end_page) {
let phys = mapper
.allocator_mut()
.allocate_one()
.expect("failed to allocate kernel heap");
let flush = unsafe {
mapper
.map_phys(
page.start_address(),
phys,
PageFlags::new()
.write(true)
.global(cfg!(not(feature = "pti"))),
)
.expect("failed to map kernel heap")
};
flush_all.consume(flush);
}
flush_all.flush();
}
pub unsafe fn init() {
unsafe {
let offset = crate::kernel_heap_offset();
let size = KERNEL_HEAP_SIZE;
// Map heap pages
map_heap(&mut KernelMapper::lock_rw(), offset, size);
// Initialize global heap
Allocator::init(offset, size);
}
}
-154
View File
@@ -1,154 +0,0 @@
use crate::area_add;
use crate::os::{Os, OsMemoryEntry, OsMemoryKind, dtb::is_in_dev_mem_region};
use core::slice;
pub(crate) const PF_PRESENT: u64 = 1 << 0;
pub(crate) const PF_TABLE: u64 = 1 << 1;
pub(crate) const PF_OUTER_SHAREABLE: u64 = 0b01 << 8;
pub(crate) const PF_INNER_SHAREABLE: u64 = 0b11 << 8;
pub(crate) const PF_ACCESS: u64 = 1 << 10;
pub(crate) const PF_DEV: u64 = PF_OUTER_SHAREABLE | 2 << 2;
pub(crate) const PF_RAM: u64 = PF_INNER_SHAREABLE;
pub(crate) const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
pub(crate) const PAGE_ENTRIES: usize = 512;
const PAGE_SIZE: usize = 4096;
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> {
unsafe {
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
if !ptr.is_null() {
area_add(OsMemoryEntry {
base: ptr as u64,
size: PAGE_SIZE as u64,
kind: OsMemoryKind::Reclaim,
});
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
} else {
None
}
}
}
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
// Create L0
let l0 = paging_allocate(os)?;
{
// Create L1 for identity mapping
let l1 = paging_allocate(os)?;
// Link first user and first kernel L0 entry to L1
l0[0] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l0[256] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
// Identity map 8 GiB using 1 GiB pages
for l1_i in 0..8 {
let addr = l1_i as u64 * 0x4000_0000;
//TODO: is PF_RAM okay?
l1[l1_i] = addr | PF_ACCESS | PF_DEV | PF_PRESENT;
}
}
{
// Create L1 for kernel mapping
let l1 = paging_allocate(os)?;
// Link second to last L0 entry to L1
l0[510] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
// Map kernel_size at kernel offset
let mut kernel_mapped = 0;
let mut l1_i = 0;
while kernel_mapped < kernel_size && l1_i < l1.len() {
let l2 = paging_allocate(os)?;
l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l1_i += 1;
let mut l2_i = 0;
while kernel_mapped < kernel_size && l2_i < l2.len() {
let l3 = paging_allocate(os)?;
l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l2_i += 1;
let mut l3_i = 0;
while kernel_mapped < kernel_size && l3_i < l3.len() {
let addr = kernel_phys + kernel_mapped;
l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT;
l3_i += 1;
kernel_mapped += PAGE_SIZE as u64;
}
}
}
assert!(kernel_mapped >= kernel_size);
}
Some(l0.as_ptr() as usize)
}
}
pub unsafe fn paging_framebuffer(
os: &impl Os,
page_phys: usize,
framebuffer_phys: u64,
framebuffer_size: u64,
) -> Option<u64> {
unsafe {
//TODO: smarter test for framebuffer already mapped
if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 {
return Some(framebuffer_phys + PHYS_OFFSET);
}
let l0_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize;
let mut l1_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize;
let mut l2_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize;
let mut l3_i = ((framebuffer_phys % 0x20_0000) / (PAGE_SIZE as u64)) as usize;
assert_eq!(framebuffer_phys % (PAGE_SIZE as u64), 0);
let l0 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
// Create l1 for framebuffer mapping
let l1 = if l0[l0_i] == 0 {
let l1 = paging_allocate(os)?;
l0[l0_i] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l1
} else {
slice::from_raw_parts_mut((l0[l0_i] & ENTRY_ADDRESS_MASK) as *mut u64, PAGE_ENTRIES)
};
// Map framebuffer_size at framebuffer offset
let mut framebuffer_mapped = 0;
while framebuffer_mapped < framebuffer_size && l1_i < l1.len() {
let l2 = paging_allocate(os)?;
assert_eq!(l1[l1_i], 0);
l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
while framebuffer_mapped < framebuffer_size && l2_i < l2.len() {
let l3 = paging_allocate(os)?;
assert_eq!(l2[l2_i], 0);
l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
while framebuffer_mapped < framebuffer_size && l3_i < l3.len() {
let addr = framebuffer_phys + framebuffer_mapped;
assert_eq!(l3[l3_i], 0);
//TODO: is PF_RAM okay?
l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT;
framebuffer_mapped += PAGE_SIZE as u64;
l3_i += 1;
}
l2_i += 1;
l3_i = 0;
}
l1_i += 1;
l2_i = 0;
}
assert!(framebuffer_mapped >= framebuffer_size);
Some(framebuffer_phys + PHYS_OFFSET)
}
}
+15
View File
@@ -0,0 +1,15 @@
// Because the memory map is so important to not be aliased, it is defined here, in one place
// The lower 256 PML4 entries are reserved for userspace
// Each PML4 entry references up to 512 GB of memory
// The second from the top (510) PML4 is reserved for the kernel
/// The size of a single PML4
pub const PML4_SIZE: usize = 0x0000_0080_0000_0000;
/// Offset to kernel heap
#[inline(always)]
pub fn kernel_heap_offset() -> usize {
crate::kernel_executable_offsets::KERNEL_OFFSET() - PML4_SIZE
}
/// End offset of the user image, i.e. kernel start
pub const USER_END_OFFSET: usize = 256 * PML4_SIZE;
+19
View File
@@ -0,0 +1,19 @@
use spin::MutexGuard;
use crate::{arch::device::serial::COM1, devices::serial::SerialKind};
pub struct Writer<'a> {
serial: MutexGuard<'a, SerialKind>,
}
impl<'a> Writer<'a> {
pub fn new() -> Writer<'a> {
Writer {
serial: COM1.lock(),
}
}
pub fn write(&mut self, buf: &[u8]) {
self.serial.write(buf);
}
}
+277
View File
@@ -0,0 +1,277 @@
use core::fmt::{Result, Write};
use crate::arch::device::cpu::registers::{control_regs, id_regs};
pub mod registers;
bitfield::bitfield! {
pub struct MachineId(u32);
get_implementer, _: 31, 24;
get_variant, _: 23, 20;
get_architecture, _: 19, 16;
get_part_number, _: 15, 4;
get_revision, _: 3, 0;
}
enum ImplementerID {
Unknown,
Arm,
Broadcom,
Cavium,
Digital,
Fujitsu,
Infineon,
Motorola,
Nvidia,
AMCC,
Qualcomm,
Marvell,
Intel,
Ampere,
}
const IMPLEMENTERS: [&'static str; 14] = [
"Unknown", "Arm", "Broadcom", "Cavium", "Digital", "Fujitsu", "Infineon", "Motorola", "Nvidia",
"AMCC", "Qualcomm", "Marvell", "Intel", "Ampere",
];
enum VariantID {
Unknown,
}
const VARIANTS: [&'static str; 1] = ["Unknown"];
enum ArchitectureID {
Unknown,
V4,
V4T,
V5,
V5T,
V5TE,
V5TEJ,
V6,
}
const ARCHITECTURES: [&'static str; 8] =
["Unknown", "v4", "v4T", "v5", "v5T", "v5TE", "v5TEJ", "v6"];
enum PartNumberID {
Unknown,
Thunder,
Foundation,
CortexA35,
CortexA53,
CortexA55,
CortexA57,
CortexA72,
CortexA73,
CortexA75,
}
const PART_NUMBERS: [&'static str; 10] = [
"Unknown",
"Thunder",
"Foundation",
"Cortex-A35",
"Cortex-A53",
"Cortex-A55",
"Cortex-A57",
"Cortex-A72",
"Cortex-A73",
"Cortex-A75",
];
enum RevisionID {
Unknown,
Thunder1_0,
Thunder1_1,
}
const REVISIONS: [&'static str; 3] = ["Unknown", "Thunder-1.0", "Thunder-1.1"];
struct CpuInfo {
implementer: &'static str,
variant: &'static str,
architecture: &'static str,
part_number: &'static str,
revision: &'static str,
aa64isar0: id_regs::AA64Isar0,
aa64isar1: id_regs::AA64Isar1,
}
impl CpuInfo {
fn new() -> CpuInfo {
let midr = unsafe { control_regs::midr() };
let midr = MachineId(midr);
let implementer = match midr.get_implementer() {
0x41 => IMPLEMENTERS[ImplementerID::Arm as usize],
0x42 => IMPLEMENTERS[ImplementerID::Broadcom as usize],
0x43 => IMPLEMENTERS[ImplementerID::Cavium as usize],
0x44 => IMPLEMENTERS[ImplementerID::Digital as usize],
0x46 => IMPLEMENTERS[ImplementerID::Fujitsu as usize],
0x49 => IMPLEMENTERS[ImplementerID::Infineon as usize],
0x4d => IMPLEMENTERS[ImplementerID::Motorola as usize],
0x4e => IMPLEMENTERS[ImplementerID::Nvidia as usize],
0x50 => IMPLEMENTERS[ImplementerID::AMCC as usize],
0x51 => IMPLEMENTERS[ImplementerID::Qualcomm as usize],
0x56 => IMPLEMENTERS[ImplementerID::Marvell as usize],
0x69 => IMPLEMENTERS[ImplementerID::Intel as usize],
0xc0 => IMPLEMENTERS[ImplementerID::Ampere as usize],
_ => IMPLEMENTERS[ImplementerID::Unknown as usize],
};
let variant = match midr.get_variant() {
_ => VARIANTS[VariantID::Unknown as usize],
};
let architecture = match midr.get_architecture() {
0b0001 => ARCHITECTURES[ArchitectureID::V4 as usize],
0b0010 => ARCHITECTURES[ArchitectureID::V4T as usize],
0b0011 => ARCHITECTURES[ArchitectureID::V5 as usize],
0b0100 => ARCHITECTURES[ArchitectureID::V5T as usize],
0b0101 => ARCHITECTURES[ArchitectureID::V5TE as usize],
0b0110 => ARCHITECTURES[ArchitectureID::V5TEJ as usize],
0b0111 => ARCHITECTURES[ArchitectureID::V6 as usize],
_ => ARCHITECTURES[ArchitectureID::Unknown as usize],
};
let part_number = match midr.get_part_number() {
0x0a1 => PART_NUMBERS[PartNumberID::Thunder as usize],
0xd00 => PART_NUMBERS[PartNumberID::Foundation as usize],
0xd04 => PART_NUMBERS[PartNumberID::CortexA35 as usize],
0xd03 => PART_NUMBERS[PartNumberID::CortexA53 as usize],
0xd05 => PART_NUMBERS[PartNumberID::CortexA55 as usize],
0xd07 => PART_NUMBERS[PartNumberID::CortexA57 as usize],
0xd08 => PART_NUMBERS[PartNumberID::CortexA72 as usize],
0xd09 => PART_NUMBERS[PartNumberID::CortexA73 as usize],
0xd0a => PART_NUMBERS[PartNumberID::CortexA75 as usize],
_ => PART_NUMBERS[PartNumberID::Unknown as usize],
};
let revision = match part_number {
"Thunder" => {
let val = match midr.get_revision() {
0x00 => REVISIONS[RevisionID::Thunder1_0 as usize],
0x01 => REVISIONS[RevisionID::Thunder1_1 as usize],
_ => REVISIONS[RevisionID::Unknown as usize],
};
val
}
_ => REVISIONS[RevisionID::Unknown as usize],
};
let aa64isar0 = id_regs::aa64isar0();
let aa64isar1 = id_regs::aa64isar1();
CpuInfo {
implementer,
variant,
architecture,
part_number,
revision,
aa64isar0,
aa64isar1,
}
}
}
pub fn cpu_info<W: Write>(w: &mut W) -> Result {
let cpuinfo = CpuInfo::new();
writeln!(w, "Implementer: {}", cpuinfo.implementer)?;
writeln!(w, "Variant: {}", cpuinfo.variant)?;
writeln!(w, "Architecture version: {}", cpuinfo.architecture)?;
writeln!(w, "Part Number: {}", cpuinfo.part_number)?;
writeln!(w, "Revision: {}", cpuinfo.revision)?;
// Print detected CPU features.
// Follow the naming convention estabilished by `std::arch::is_aarch64_feature_detected`.
write!(w, "Features:")?;
// ID_AA64ISAR0_EL1
if cpuinfo.aa64isar0.has_feat_rng() {
write!(w, " rand")?;
}
if cpuinfo.aa64isar0.has_feat_flagm() {
write!(w, " flagm")?;
}
if cpuinfo.aa64isar0.has_feat_flagm2() {
write!(w, " flagm2")?;
}
if cpuinfo.aa64isar0.has_feat_fhm() {
write!(w, " fhm")?;
}
if cpuinfo.aa64isar0.has_feat_dotprod() {
write!(w, " dotprod")?;
}
if cpuinfo.aa64isar0.has_feat_sm3() && cpuinfo.aa64isar0.has_feat_sm4() {
write!(w, " sm4")?;
}
if cpuinfo.aa64isar0.has_feat_sha512() && cpuinfo.aa64isar0.has_feat_sha3() {
write!(w, " sha3")?;
}
if cpuinfo.aa64isar0.has_feat_rdm() {
write!(w, " rdm")?;
}
if cpuinfo.aa64isar0.has_feat_lse() {
write!(w, " lse")?;
}
if cpuinfo.aa64isar0.has_feat_lse128() {
write!(w, " lse128")?;
}
if cpuinfo.aa64isar0.has_feat_crc() {
write!(w, " crc")?;
}
if cpuinfo.aa64isar0.has_feat_sha1() && cpuinfo.aa64isar0.has_feat_sha256() {
write!(w, " sha2")?;
}
if cpuinfo.aa64isar0.has_feat_aes() && cpuinfo.aa64isar0.has_feat_pmull() {
write!(w, " aes")?;
}
// ID_AA64ISAR1_EL1
if cpuinfo.aa64isar1.has_feat_i8mm() {
write!(w, " i8mm")?;
}
if cpuinfo.aa64isar1.has_feat_bf16() {
write!(w, " bf16")?;
}
if cpuinfo.aa64isar1.has_feat_sb() {
write!(w, " sb")?;
}
if cpuinfo.aa64isar1.has_feat_frintts() {
write!(w, " frintts")?;
}
if cpuinfo.aa64isar1.gpi() != 0 || cpuinfo.aa64isar1.gpa() != 0 {
write!(w, " pacg")?;
}
if cpuinfo.aa64isar1.has_feat_lrcpc() {
write!(w, " rcpc")?;
}
if cpuinfo.aa64isar1.has_feat_lrcpc2() {
write!(w, " rcpc2")?;
}
if cpuinfo.aa64isar1.has_feat_lrcpc3() {
write!(w, " rcpc3")?;
}
if cpuinfo.aa64isar1.has_feat_fcma() {
write!(w, " fcma")?;
}
if cpuinfo.aa64isar1.has_feat_jscvt() {
write!(w, " jsconv")?;
}
if cpuinfo.aa64isar1.api() != 0 || cpuinfo.aa64isar1.apa() != 0 {
write!(w, " paca")?;
}
if cpuinfo.aa64isar1.has_feat_dpb() {
write!(w, " dpb")?;
}
if cpuinfo.aa64isar1.has_feat_dpb2() {
write!(w, " dpb2")?;
}
writeln!(w)?;
Ok(())
}
@@ -0,0 +1,167 @@
#![allow(unused)]
//! Functions to read and write control registers.
use core::arch::asm;
pub unsafe fn ttbr0_el1() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, ttbr0_el1", out(reg) ret);
ret
}
}
pub unsafe fn ttbr0_el1_write(val: u64) {
unsafe {
asm!("msr ttbr0_el1, {}", in(reg) val);
}
}
pub unsafe fn ttbr1_el1() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, ttbr1_el1", out(reg) ret);
ret
}
}
pub unsafe fn ttbr1_el1_write(val: u64) {
unsafe {
asm!("msr ttbr1_el1, {}", in(reg) val);
}
}
pub unsafe fn tpidr_el0() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, tpidr_el0", out(reg) ret);
ret
}
}
pub unsafe fn tpidr_el0_write(val: u64) {
unsafe {
asm!("msr tpidr_el0, {}", in(reg) val);
}
}
pub unsafe fn tpidr_el1() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, tpidr_el1", out(reg) ret);
ret
}
}
pub unsafe fn tpidr_el1_write(val: u64) {
unsafe {
asm!("msr tpidr_el1, {}", in(reg) val);
}
}
pub unsafe fn tpidrro_el0() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, tpidrro_el0", out(reg) ret);
ret
}
}
pub unsafe fn tpidrro_el0_write(val: u64) {
unsafe {
asm!("msr tpidrro_el0, {}", in(reg) val);
}
}
pub unsafe fn esr_el1() -> u32 {
unsafe {
let ret: u32;
asm!("mrs {0:w}, esr_el1", out(reg) ret);
ret
}
}
pub unsafe fn vhe_present() -> bool {
unsafe {
let mut mmfr1: u64;
asm!("mrs {}, id_aa64mmfr1_el1", out(reg) mmfr1);
// The VHE (Virtualization Host Extensions) field is in bits [7:4].
let vhe_field = (mmfr1 >> 4) & 0b1111;
vhe_field != 0
}
}
pub unsafe fn cntfrq_el0() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, cntfrq_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn ptmr_ctrl() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, cntp_ctl_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn ptmr_ctrl_write(val: u32) {
unsafe {
asm!("msr cntp_ctl_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn ptmr_tval() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {0}, cntp_tval_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn ptmr_tval_write(val: u32) {
unsafe {
asm!("msr cntp_tval_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn vtmr_ctrl() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, cntv_ctl_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn vtmr_ctrl_write(val: u32) {
unsafe {
asm!("msr cntv_ctl_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn vtmr_tval() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {0}, cntv_tval_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn vtmr_tval_write(val: u32) {
unsafe {
asm!("msr cntv_tval_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn midr() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, midr_el1", out(reg) ret);
ret as u32
}
}
@@ -0,0 +1,151 @@
//! Functions and bitfield definitions for `ID_AA64*` system registers. (e.g. `ID_AA64ISAR0_EL1`)
use core::arch::asm;
bitfield::bitfield! {
pub struct AA64Isar0(u64);
impl Debug;
pub rndr, _: 63, 60;
pub tlb, _: 59, 56;
pub ts, _: 55, 52;
pub fhm, _: 51, 48;
pub dp, _: 47, 44;
pub sm4, _: 43, 40;
pub sm3, _: 39, 36;
pub sha3, _: 35, 32;
pub rdm, _: 31, 28;
pub atomic, _: 23, 20;
pub crc32, _: 19, 16;
pub sha2, _: 15, 12;
pub sha1, _: 11, 8;
pub aes, _: 7, 4;
}
bitfield::bitfield! {
pub struct AA64Isar1(u64);
impl Debug;
pub ls64, _: 63, 60;
pub xs, _: 59, 56;
pub i8mm, _: 55, 52;
pub dgh, _: 51, 48;
pub bf16, _: 47, 44;
pub specres, _: 43, 40;
pub sb, _: 39, 36;
pub frintts, _: 35, 32;
pub gpi, _: 31, 28;
pub gpa, _: 27, 24;
pub lrcpc, _: 23, 20;
pub fcma, _: 19, 16;
pub jscvt, _: 15, 12;
pub api, _: 11, 8;
pub apa, _: 7, 4;
pub dpb, _: 3, 0;
}
impl AA64Isar0 {
pub fn has_feat_rng(&self) -> bool {
self.rndr() == 0b0001
}
pub fn has_feat_flagm(&self) -> bool {
self.ts() == 0b0001
}
pub fn has_feat_flagm2(&self) -> bool {
self.ts() == 0b0010
}
pub fn has_feat_fhm(&self) -> bool {
self.fhm() == 0b0001
}
pub fn has_feat_dotprod(&self) -> bool {
self.dp() == 0b0001
}
pub fn has_feat_sm4(&self) -> bool {
self.sm4() == 0b0001
}
pub fn has_feat_sm3(&self) -> bool {
self.sm3() == 0b0001
}
pub fn has_feat_sha3(&self) -> bool {
self.sha3() == 0b0001
}
pub fn has_feat_rdm(&self) -> bool {
self.rdm() == 0b0001
}
pub fn has_feat_lse(&self) -> bool {
self.atomic() == 0b0010
}
pub fn has_feat_lse128(&self) -> bool {
self.atomic() == 0b0011
}
/// The current Arm Architecture Registers Manual calls it FEAT_CRC32,
/// but everyone else seems to call it FEAT_CRC.
pub fn has_feat_crc(&self) -> bool {
self.crc32() == 0b0001
}
pub fn has_feat_sha256(&self) -> bool {
self.sha2() == 0b0001
}
pub fn has_feat_sha512(&self) -> bool {
self.sha2() == 0b0010
}
pub fn has_feat_sha1(&self) -> bool {
self.sha1() == 0b0001
}
pub fn has_feat_aes(&self) -> bool {
self.aes() == 0b0001
}
pub fn has_feat_pmull(&self) -> bool {
self.aes() == 0b0010
}
}
impl AA64Isar1 {
pub fn has_feat_i8mm(&self) -> bool {
self.i8mm() == 0b0001
}
pub fn has_feat_bf16(&self) -> bool {
self.bf16() == 0b0001
}
pub fn has_feat_sb(&self) -> bool {
self.sb() == 0b0001
}
pub fn has_feat_frintts(&self) -> bool {
self.frintts() == 0b0001
}
pub fn has_feat_lrcpc(&self) -> bool {
self.lrcpc() == 0b0001
}
pub fn has_feat_lrcpc2(&self) -> bool {
self.lrcpc() == 0b0010
}
pub fn has_feat_lrcpc3(&self) -> bool {
self.lrcpc() == 0b0011
}
pub fn has_feat_fcma(&self) -> bool {
self.fcma() == 0b0001
}
pub fn has_feat_jscvt(&self) -> bool {
self.jscvt() == 0b0011
}
pub fn has_feat_dpb(&self) -> bool {
self.dpb() == 0b0001
}
pub fn has_feat_dpb2(&self) -> bool {
self.dpb() == 0b0010
}
}
pub fn aa64isar0() -> AA64Isar0 {
let ret: u64;
unsafe {
asm!("mrs {}, ID_AA64ISAR0_EL1", out(reg) ret);
}
AA64Isar0(ret)
}
pub fn aa64isar1() -> AA64Isar1 {
let ret: u64;
unsafe {
asm!("mrs {}, ID_AA64ISAR1_EL1", out(reg) ret);
}
AA64Isar1(ret)
}
@@ -0,0 +1,2 @@
pub mod control_regs;
pub mod id_regs;
+145
View File
@@ -0,0 +1,145 @@
use alloc::boxed::Box;
use super::ic_for_chip;
use crate::{
arch::device::cpu::registers::control_regs,
context::{self, timeout},
dtb::{
get_interrupt,
irqchip::{register_irq, InterruptHandler, IRQ_CHIP},
},
scheme::irq::irq_trigger,
sync::CleanLockToken,
time,
};
use fdt::Fdt;
bitflags! {
struct TimerCtrlFlags: u32 {
const ENABLE = 1 << 0;
const IMASK = 1 << 1;
const ISTATUS = 1 << 2;
}
}
pub unsafe fn init(fdt: &Fdt) {
unsafe {
let mut timer = GenericTimer::new();
timer.init();
if let Some(node) = fdt.find_compatible(&["arm,armv7-timer"]) {
let irq = get_interrupt(fdt, &node, 1).unwrap();
debug!("irq = {:?}", irq);
if let Some(ic_idx) = ic_for_chip(&fdt, &node) {
//PHYS_NONSECURE_PPI only
let virq = IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_xlate(irq)
.unwrap();
info!("generic_timer virq = {}", virq);
register_irq(virq as u32, Box::new(timer));
IRQ_CHIP.irq_enable(virq as u32);
} else {
error!("Failed to find irq parent for generic timer");
}
}
}
}
pub struct GenericTimer {
pub use_virtual_timer: bool,
pub clk_freq: u32,
pub reload_count: u32,
}
impl GenericTimer {
pub fn new() -> Self {
Self {
use_virtual_timer: false,
clk_freq: 0,
reload_count: 0,
}
}
pub fn init(&mut self) {
self.use_virtual_timer = unsafe { !control_regs::vhe_present() };
debug!(
"generic_timer use_virtual_timer = {:?}",
self.use_virtual_timer
);
let clk_freq = unsafe { control_regs::cntfrq_el0() };
self.clk_freq = clk_freq;
self.reload_count = clk_freq / 100;
self.reload_count();
}
fn read_tmr_ctrl(&self) -> TimerCtrlFlags {
TimerCtrlFlags::from_bits_truncate(if self.use_virtual_timer {
unsafe { control_regs::vtmr_ctrl() }
} else {
unsafe { control_regs::ptmr_ctrl() }
})
}
fn write_tmr_ctrl(&self, ctrl: TimerCtrlFlags) {
if self.use_virtual_timer {
unsafe { control_regs::vtmr_ctrl_write(ctrl.bits()) };
} else {
unsafe { control_regs::ptmr_ctrl_write(ctrl.bits()) };
}
}
#[allow(unused)]
fn disable(&self) {
let mut ctrl = self.read_tmr_ctrl();
ctrl.remove(TimerCtrlFlags::ENABLE);
self.write_tmr_ctrl(ctrl);
}
#[allow(unused)]
pub fn set_irq(&mut self) {
let mut ctrl = self.read_tmr_ctrl();
ctrl.remove(TimerCtrlFlags::IMASK);
self.write_tmr_ctrl(ctrl);
}
pub fn clear_irq(&mut self) {
let mut ctrl = self.read_tmr_ctrl();
if ctrl.contains(TimerCtrlFlags::ISTATUS) {
ctrl.insert(TimerCtrlFlags::IMASK);
self.write_tmr_ctrl(ctrl);
}
}
pub fn reload_count(&mut self) {
if self.use_virtual_timer {
unsafe { control_regs::vtmr_tval_write(self.reload_count) };
} else {
unsafe { control_regs::ptmr_tval_write(self.reload_count) };
}
let mut ctrl = self.read_tmr_ctrl();
ctrl.insert(TimerCtrlFlags::ENABLE);
ctrl.remove(TimerCtrlFlags::IMASK);
self.write_tmr_ctrl(ctrl);
}
}
impl InterruptHandler for GenericTimer {
fn irq_handler(&mut self, irq: u32, token: &mut CleanLockToken) {
self.clear_irq();
{
*time::OFFSET.write(token.token()) += self.clk_freq as u128;
}
timeout::trigger(token);
context::switch::tick(token);
unsafe {
// FIXME add_irq accepts a u8 as irq number
// PercpuBlock::current().stats.add_irq(irq);
irq_trigger(irq.try_into().unwrap(), token);
IRQ_CHIP.irq_eoi(irq);
}
self.reload_count();
}
}
+288
View File
@@ -0,0 +1,288 @@
use super::InterruptController;
use crate::{
dtb::{
get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc},
},
sync::CleanLockToken,
};
use core::ptr::{read_volatile, write_volatile};
use fdt::{node::FdtNode, Fdt};
use syscall::{
error::{Error, EINVAL},
Result,
};
static GICD_CTLR: u32 = 0x000;
static GICD_TYPER: u32 = 0x004;
static GICD_ISENABLER: u32 = 0x100;
static GICD_ICENABLER: u32 = 0x180;
static GICD_IPRIORITY: u32 = 0x400;
static GICD_ITARGETSR: u32 = 0x800;
static GICD_ICFGR: u32 = 0xc00;
static GICC_EOIR: u32 = 0x0010;
static GICC_IAR: u32 = 0x000c;
static GICC_CTLR: u32 = 0x0000;
static GICC_PMR: u32 = 0x0004;
pub struct GenericInterruptController {
pub gic_dist_if: GicDistIf,
pub gic_cpu_if: GicCpuIf,
pub irq_range: (usize, usize),
}
impl GenericInterruptController {
pub fn new() -> Self {
let gic_dist_if = GicDistIf::default();
let gic_cpu_if = GicCpuIf::default();
GenericInterruptController {
gic_dist_if,
gic_cpu_if,
irq_range: (0, 0),
}
}
pub fn parse(fdt: &Fdt) -> Result<(usize, usize, usize, usize)> {
if let Some(node) = fdt.find_compatible(&["arm,cortex-a15-gic", "arm,gic-400"]) {
return GenericInterruptController::parse_inner(fdt, &node);
} else {
return Err(Error::new(EINVAL));
}
}
fn parse_inner(fdt: &Fdt, node: &FdtNode) -> Result<(usize, usize, usize, usize)> {
//assert address_cells == 0x2, size_cells == 0x2
let reg = node.reg().unwrap();
let mut regs = (0, 0, 0, 0);
let mut idx = 0;
for chunk in reg {
if chunk.size.is_none() {
break;
}
let addr = get_mmio_address(fdt, node, &chunk).unwrap();
match idx {
0 => (regs.0, regs.1) = (addr, chunk.size.unwrap()),
2 => (regs.2, regs.3) = (addr, chunk.size.unwrap()),
_ => break,
}
idx += 2;
}
if idx == 4 {
Ok(regs)
} else {
Err(Error::new(EINVAL))
}
}
}
impl InterruptHandler for GenericInterruptController {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for GenericInterruptController {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
if let Some(fdt) = fdt_opt {
let (dist_addr, _dist_size, cpu_addr, _cpu_size) =
match GenericInterruptController::parse(fdt) {
Ok(regs) => regs,
Err(err) => return Err(err),
};
unsafe {
self.gic_dist_if.init(crate::PHYS_OFFSET + dist_addr);
self.gic_cpu_if.init(crate::PHYS_OFFSET + cpu_addr);
}
}
let idx = *irq_idx;
let cnt = if self.gic_dist_if.nirqs > 1024 {
1024
} else {
self.gic_dist_if.nirqs as usize
};
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("gic irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
Ok(())
}
fn irq_ack(&mut self) -> u32 {
unsafe { self.gic_cpu_if.irq_ack() }
}
fn irq_eoi(&mut self, irq_num: u32) {
unsafe { self.gic_cpu_if.irq_eoi(irq_num) }
}
fn irq_enable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_enable(irq_num) }
}
fn irq_disable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_disable(irq_num) }
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
let off = match irq_data {
IrqCell::L3(0, irq, _flags) => irq as usize + 32, // SPI
IrqCell::L3(1, irq, _flags) => irq as usize + 16, // PPI
_ => return Err(Error::new(EINVAL)),
};
return Ok(off + self.irq_range.0);
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq >= self.gic_dist_if.nirqs {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
#[derive(Debug, Default)]
pub struct GicDistIf {
pub address: usize,
pub ncpus: u32,
pub nirqs: u32,
}
impl GicDistIf {
pub unsafe fn init(&mut self, addr: usize) {
unsafe {
self.address = addr;
// Disable IRQ Distribution
self.write(GICD_CTLR, 0);
let typer = self.read(GICD_TYPER);
self.ncpus = ((typer & (0x7 << 5)) >> 5) + 1;
self.nirqs = ((typer & 0x1f) + 1) * 32;
info!(
"gic: Distributor supports {:?} CPUs and {:?} IRQs",
self.ncpus, self.nirqs
);
// Set all SPIs to level triggered
for irq in (32..self.nirqs).step_by(16) {
self.write(GICD_ICFGR + ((irq / 16) * 4), 0);
}
// Disable all SPIs
for irq in (32..self.nirqs).step_by(32) {
self.write(GICD_ICENABLER + ((irq / 32) * 4), 0xffff_ffff);
}
// Affine all SPIs to CPU0 and set priorities for all IRQs
for irq in 0..self.nirqs {
if irq > 31 {
let ext_offset = GICD_ITARGETSR + (4 * (irq / 4));
let int_offset = irq % 4;
let mut val = self.read(ext_offset);
val |= 0b0000_0001 << (8 * int_offset);
self.write(ext_offset, val);
}
let ext_offset = GICD_IPRIORITY + (4 * (irq / 4));
let int_offset = irq % 4;
let mut val = self.read(ext_offset);
val |= 0b0000_0000 << (8 * int_offset);
self.write(ext_offset, val);
}
// Enable IRQ group 0 and group 1 non-secure distribution
self.write(GICD_CTLR, 0x3);
}
}
pub unsafe fn irq_enable(&mut self, irq: u32) {
unsafe {
let offset = GICD_ISENABLER + (4 * (irq / 32));
let shift = 1 << (irq % 32);
let mut val = self.read(offset);
val |= shift;
self.write(offset, val);
}
}
pub unsafe fn irq_disable(&mut self, irq: u32) {
unsafe {
let offset = GICD_ICENABLER + (4 * (irq / 32));
let shift = 1 << (irq % 32);
let mut val = self.read(offset);
val |= shift;
self.write(offset, val);
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
#[derive(Debug, Default)]
pub struct GicCpuIf {
pub address: usize,
}
impl GicCpuIf {
pub unsafe fn init(&mut self, addr: usize) {
unsafe {
self.address = addr;
// Enable CPU0's GIC interface
self.write(GICC_CTLR, 1);
// Set CPU0's Interrupt Priority Mask
self.write(GICC_PMR, 0xff);
}
}
unsafe fn irq_ack(&mut self) -> u32 {
unsafe {
let irq = self.read(GICC_IAR) & 0x1ff;
if irq == 1023 {
panic!("irq_ack: got ID 1023!!!");
}
irq
}
}
unsafe fn irq_eoi(&mut self, irq: u32) {
unsafe {
self.write(GICC_EOIR, irq);
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
+196
View File
@@ -0,0 +1,196 @@
use alloc::vec::Vec;
use core::arch::asm;
use fdt::{node::NodeProperty, Fdt};
use super::{gic::GicDistIf, InterruptController};
use crate::{
dtb::{
get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc},
},
sync::CleanLockToken,
};
use syscall::{
error::{Error, EINVAL},
Result,
};
#[derive(Debug)]
pub struct GicV3 {
pub gic_dist_if: GicDistIf,
pub gic_cpu_if: GicV3CpuIf,
pub gicrs: Vec<(usize, usize)>,
//TODO: GICC, GICH, GICV?
pub irq_range: (usize, usize),
}
impl GicV3 {
pub fn new() -> Self {
GicV3 {
gic_dist_if: GicDistIf::default(),
gic_cpu_if: GicV3CpuIf,
gicrs: Vec::new(),
irq_range: (0, 0),
}
}
pub fn parse(&mut self, fdt: &Fdt) -> Result<()> {
let Some(node) = fdt.find_compatible(&["arm,gic-v3"]) else {
return Err(Error::new(EINVAL));
};
// Clear current registers
//TODO: deinit?
self.gic_dist_if.address = 0;
self.gicrs.clear();
// Get number of GICRs
let gicrs = node
.property("#redistributor-regions")
.and_then(NodeProperty::as_usize)
.unwrap_or(1);
// Read registers
let mut chunks = node.reg().unwrap();
if let Some(gicd) = chunks.next()
&& let Some(addr) = get_mmio_address(fdt, &node, &gicd)
{
unsafe {
self.gic_dist_if.init(crate::PHYS_OFFSET + addr);
}
}
for _ in 0..gicrs {
if let Some(gicr) = chunks.next() {
self.gicrs.push((
get_mmio_address(fdt, &node, &gicr).unwrap(),
gicr.size.unwrap(),
));
}
}
if self.gic_dist_if.address == 0 || self.gicrs.is_empty() {
Err(Error::new(EINVAL))
} else {
Ok(())
}
}
}
impl InterruptHandler for GicV3 {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for GicV3 {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
if let Some(fdt) = fdt_opt {
self.parse(fdt)?;
}
info!("{:X?}", self);
unsafe {
self.gic_cpu_if.init();
}
let idx = *irq_idx;
let cnt = if self.gic_dist_if.nirqs > 1024 {
1024
} else {
self.gic_dist_if.nirqs as usize
};
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("gic irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
Ok(())
}
fn irq_ack(&mut self) -> u32 {
let irq_num = unsafe { self.gic_cpu_if.irq_ack() };
irq_num
}
fn irq_eoi(&mut self, irq_num: u32) {
unsafe { self.gic_cpu_if.irq_eoi(irq_num) }
}
fn irq_enable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_enable(irq_num) }
}
fn irq_disable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_disable(irq_num) }
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
let off = match irq_data {
IrqCell::L3(0, irq, _flags) => irq as usize + 32, // SPI
IrqCell::L3(1, irq, _flags) => irq as usize + 16, // PPI
_ => return Err(Error::new(EINVAL)),
};
return Ok(off + self.irq_range.0);
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq >= self.gic_dist_if.nirqs {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
#[derive(Debug)]
pub struct GicV3CpuIf;
impl GicV3CpuIf {
pub unsafe fn init(&mut self) {
unsafe {
// Enable system register access
{
let value = 1_usize;
asm!("msr icc_sre_el1, {}", in(reg) value);
}
// Set control register
{
let value = 0_usize;
asm!("msr icc_ctlr_el1, {}", in(reg) value);
}
// Enable non-secure group 1
{
let value = 1_usize;
asm!("msr icc_igrpen1_el1, {}", in(reg) value);
}
// Set CPU0's Interrupt Priority Mask
{
let value = 0xFF_usize;
asm!("msr icc_pmr_el1, {}", in(reg) value);
}
}
}
unsafe fn irq_ack(&mut self) -> u32 {
unsafe {
let mut irq: usize;
asm!("mrs {}, icc_iar1_el1", out(reg) irq);
irq &= 0x1ff;
if irq == 1023 {
panic!("irq_ack: got ID 1023!!!");
}
irq as u32
}
}
unsafe fn irq_eoi(&mut self, irq: u32) {
unsafe {
asm!("msr icc_eoir1_el1, {}", in(reg) irq as usize);
}
}
}
@@ -0,0 +1,299 @@
use core::ptr::{read_volatile, write_volatile};
use fdt::{node::FdtNode, Fdt};
use super::InterruptController;
use crate::{
dtb::{
get_interrupt, get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc, IRQ_CHIP},
},
sync::CleanLockToken,
};
use syscall::{
error::{Error, EINVAL},
Result,
};
#[inline(always)]
fn ffs(num: u32) -> u32 {
let mut x = num;
if x == 0 {
return 0;
}
let mut r = 1;
if (x & 0xffff) == 0 {
x >>= 16;
r += 16;
}
if (x & 0xff) == 0 {
x >>= 8;
r += 8;
}
if (x & 0xf) == 0 {
x >>= 4;
r += 4;
}
if (x & 0x3) == 0 {
x >>= 2;
r += 2;
}
if (x & 0x1) == 0 {
r += 1;
}
r
}
const PENDING_0: u32 = 0x0;
const PENDING_1: u32 = 0x4;
const PENDING_2: u32 = 0x8;
const ENABLE_0: u32 = 0x18;
const ENABLE_1: u32 = 0x10;
const ENABLE_2: u32 = 0x14;
const DISABLE_0: u32 = 0x24;
const DISABLE_1: u32 = 0x1c;
const DISABLE_2: u32 = 0x20;
pub struct Bcm2835ArmInterruptController {
pub address: usize,
pub irq_range: (usize, usize),
}
impl Bcm2835ArmInterruptController {
pub fn new() -> Self {
Bcm2835ArmInterruptController {
address: 0,
irq_range: (0, 0),
}
}
pub fn parse(fdt: &Fdt) -> Result<(usize, usize, Option<usize>)> {
if let Some(node) = fdt.find_compatible(&["brcm,bcm2836-armctrl-ic"]) {
return unsafe { Bcm2835ArmInterruptController::parse_inner(fdt, &node) };
} else {
return Err(Error::new(EINVAL));
}
}
unsafe fn parse_inner(fdt: &Fdt, node: &FdtNode) -> Result<(usize, usize, Option<usize>)> {
unsafe {
//assert address_cells == 0x1, size_cells == 0x1
let mem = node.reg().unwrap().nth(0).unwrap();
let base = get_mmio_address(fdt, node, &mem).unwrap();
let size = mem.size.unwrap() as u32;
let mut ret_virq = None;
if let Some(interrupt_parent) = node.property("interrupt-parent") {
let phandle = interrupt_parent.as_usize().unwrap() as u32;
let irq = get_interrupt(fdt, node, 0).unwrap();
let ic_idx = IRQ_CHIP.phandle_to_ic_idx(phandle).unwrap();
//PHYS_NONSECURE_PPI only
let virq = IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_xlate(irq)
.unwrap();
info!(
"register bcm2835arm_ctrl as ic_idx {}'s child virq = {}",
ic_idx, virq
);
ret_virq = Some(virq);
}
Ok((base as usize, size as usize, ret_virq))
}
}
unsafe fn init(&mut self) {
unsafe {
debug!("IRQ BCM2835 INIT");
//disable all interrupt
self.write(DISABLE_0, 0xffff_ffff);
self.write(DISABLE_1, 0xffff_ffff);
self.write(DISABLE_2, 0xffff_ffff);
debug!("IRQ BCM2835 END");
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
impl InterruptController for Bcm2835ArmInterruptController {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
let (base, _size, _virq) = match Bcm2835ArmInterruptController::parse(fdt_opt.unwrap()) {
Ok((a, b, c)) => (a, b, c),
Err(_) => return Err(Error::new(EINVAL)),
};
unsafe {
self.address = base + crate::PHYS_OFFSET;
self.init();
let idx = *irq_idx;
let cnt = 3 << 5; //3 * 32 irqs, basic == 8, reg1 = 32, reg2 = 32
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("bcm2835 irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
}
Ok(())
}
fn irq_ack(&mut self) -> u32 {
//TODO: support smp self.read(LOCAL_IRQ_PENDING + 4 * cpu)
let sources = unsafe { self.read(PENDING_0) };
let pending_num = ffs(sources) - 1;
let fast_irq = [
7 + 32,
9 + 32,
10 + 32,
18 + 32,
19 + 32,
21 + 64,
22 + 64,
23 + 64,
24 + 64,
25 + 64,
30 + 64,
];
//fast irq
if pending_num >= 10 && pending_num <= 20 {
return fast_irq[(pending_num - 10) as usize];
}
let pending_num = ffs(sources & 0x3ff) - 1;
match pending_num {
num @ 0..=7 => return num,
8 => {
let sources1 = unsafe { self.read(PENDING_1) };
let irq_0_31 = ffs(sources1) - 1;
return irq_0_31 + 32;
}
9 => {
let sources2 = unsafe { self.read(PENDING_2) };
let irq_32_63 = ffs(sources2) - 1;
return irq_32_63 + 64;
}
num => {
error!(
"unexpected irq pending in BASIC PENDING: 0x{}, sources = 0x{:08x}",
num, sources
);
return num;
}
}
}
fn irq_eoi(&mut self, _irq_num: u32) {}
fn irq_enable(&mut self, irq_num: u32) {
debug!("bcm2835 enable {} {}", irq_num, irq_num & 0x1f);
match irq_num {
num @ 0..=31 => {
let val = 1 << num;
unsafe {
self.write(ENABLE_0, val);
}
}
num @ 32..=63 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(ENABLE_1, val);
}
}
num @ 64..=95 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(ENABLE_2, val);
}
}
_ => return,
}
}
fn irq_disable(&mut self, irq_num: u32) {
match irq_num {
num @ 0..=31 => {
let val = 1 << num;
unsafe {
self.write(DISABLE_0, val);
}
}
num @ 32..=63 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(DISABLE_1, val);
}
}
num @ 64..=95 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(DISABLE_2, val);
}
}
_ => return,
}
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
//assert interrupt-cells == 0x2
match irq_data {
IrqCell::L2(bank, irq) => {
//TODO: check bank && irq
let hwirq = (bank as usize) << 5 | (irq as usize);
let off = hwirq + self.irq_range.0;
Ok(off)
}
_ => Err(Error::new(EINVAL)),
}
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq > 95 {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
impl InterruptHandler for Bcm2835ArmInterruptController {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {
unsafe {
let irq = self.irq_ack();
if let Some(virq) = self.irq_to_virq(irq)
&& virq < 1024
{
if let Some(handler) = &mut IRQ_CHIP.irq_desc[virq].handler {
handler.irq_handler(virq as u32, token);
}
} else {
error!("unexpected irq num {}", irq);
}
self.irq_eoi(irq);
}
}
}
@@ -0,0 +1,231 @@
use super::InterruptController;
use crate::{
arch::device::{ROOT_IC_IDX, ROOT_IC_IDX_IS_SET},
dtb::{
get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc},
},
sync::CleanLockToken,
};
use core::{
arch::asm,
ptr::{read_volatile, write_volatile},
sync::atomic::Ordering,
};
use fdt::{node::FdtNode, Fdt};
use syscall::{
error::{Error, EINVAL},
Result,
};
const LOCAL_CONTROL: u32 = 0x000;
const LOCAL_PRESCALER: u32 = 0x008;
const LOCAL_GPU_ROUTING: u32 = 0x00C;
const LOCAL_TIMER_INT_CONTROL0: u32 = 0x040;
const LOCAL_IRQ_PENDING: u32 = 0x060;
const LOCAL_IRQ_CNTPNSIRQ: u32 = 0x1;
const LOCAL_IRQ_GPU_FAST: u32 = 0x8;
const LOCAL_IRQ_PMU_FAST: u32 = 0x9;
const LOCAL_IRQ_LAST: u32 = LOCAL_IRQ_PMU_FAST;
#[inline(always)]
fn ffs(num: u32) -> u32 {
let mut x = num;
if x == 0 {
return 0;
}
let mut r = 1;
if (x & 0xffff) == 0 {
x >>= 16;
r += 16;
}
if (x & 0xff) == 0 {
x >>= 8;
r += 8;
}
if (x & 0xf) == 0 {
x >>= 4;
r += 4;
}
if (x & 0x3) == 0 {
x >>= 2;
r += 2;
}
if (x & 0x1) == 0 {
r += 1;
}
r
}
pub struct Bcm2836ArmInterruptController {
pub address: usize,
pub irq_range: (usize, usize),
pub active_cpu: u32,
}
impl Bcm2836ArmInterruptController {
pub fn new() -> Self {
Bcm2836ArmInterruptController {
address: 0,
irq_range: (0, 0),
active_cpu: 0,
}
}
pub fn parse(fdt: &Fdt) -> Result<(usize, usize)> {
if let Some(node) = fdt.find_compatible(&["brcm,bcm2836-l1-intc"]) {
return Bcm2836ArmInterruptController::parse_inner(fdt, &node);
} else {
return Err(Error::new(EINVAL));
}
}
fn parse_inner(fdt: &Fdt, node: &FdtNode) -> Result<(usize, usize)> {
//assert address_cells == 0x1, size_cells == 0x1
let reg = node.reg().unwrap().nth(0).unwrap();
let addr = get_mmio_address(fdt, node, &reg).unwrap();
Ok((addr, reg.size.unwrap()))
}
unsafe fn init(&mut self) {
unsafe {
debug!("IRQ BCM2836 INIT");
//init local timer freq
self.write(LOCAL_CONTROL, 0x0);
self.write(LOCAL_PRESCALER, 0x8000_0000);
//routing all irq to core
self.write(LOCAL_GPU_ROUTING, self.active_cpu);
debug!("routing all irq to core {}", self.active_cpu);
debug!("IRQ BCM2836 END");
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
impl InterruptHandler for Bcm2836ArmInterruptController {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for Bcm2836ArmInterruptController {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
let (base, _size) = match Bcm2836ArmInterruptController::parse(fdt_opt.unwrap()) {
Ok((a, b)) => (a, b),
Err(_) => return Err(Error::new(EINVAL)),
};
unsafe {
self.address = base + crate::PHYS_OFFSET;
let cpuid: usize;
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
self.active_cpu = cpuid as u32 & 0x3;
self.init();
let idx = *irq_idx;
let cnt = LOCAL_IRQ_LAST as usize;
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("bcm2836 irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
}
//raspi 3b+ dts doesn't follow the rule to set root parent interrupt controller
//so we should set it manually.
ROOT_IC_IDX.store(ic_idx, Ordering::Relaxed);
ROOT_IC_IDX_IS_SET.store(1, Ordering::Relaxed);
Ok(())
}
fn irq_ack(&mut self) -> u32 {
let cpuid: usize;
unsafe {
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
}
let cpu = cpuid as u32 & 0x3;
let sources: u32 = unsafe { self.read(LOCAL_IRQ_PENDING + 4 * cpu) };
ffs(sources) - 1
}
fn irq_eoi(&mut self, _irq_num: u32) {}
fn irq_enable(&mut self, irq_num: u32) {
debug!("bcm2836 enable {}", irq_num);
match irq_num {
LOCAL_IRQ_CNTPNSIRQ => unsafe {
let cpuid: usize;
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
let cpu = cpuid as u32 & 0x3;
let mut reg_val = self.read(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu);
reg_val |= 0x2;
self.write(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu, reg_val);
},
LOCAL_IRQ_GPU_FAST => {
//GPU IRQ always enable
}
_ => {
//ignore
}
}
}
fn irq_disable(&mut self, irq_num: u32) {
match irq_num {
LOCAL_IRQ_CNTPNSIRQ => unsafe {
let cpuid: usize;
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
let cpu = cpuid as u32 & 0x3;
let mut reg_val = self.read(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu);
reg_val &= !0x2;
self.write(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu, reg_val);
},
LOCAL_IRQ_GPU_FAST => {
//GPU IRQ always enable
}
_ => {
//ignore
}
}
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
//assert interrupt-cells == 0x2
match irq_data {
IrqCell::L2(irq, _) => Ok(irq as usize + self.irq_range.0),
_ => Err(Error::new(EINVAL)),
}
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq > LOCAL_IRQ_LAST {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
+41
View File
@@ -0,0 +1,41 @@
use crate::dtb::irqchip::{InterruptController, IRQ_CHIP};
use alloc::boxed::Box;
use fdt::{node::FdtNode, Fdt};
pub(crate) mod gic;
pub(crate) mod gicv3;
mod irq_bcm2835;
mod irq_bcm2836;
mod null;
pub(crate) fn new_irqchip(ic_str: &str) -> Option<Box<dyn InterruptController>> {
if ic_str.contains("arm,gic-v3") {
Some(Box::new(gicv3::GicV3::new()))
} else if ic_str.contains("arm,cortex-a15-gic") || ic_str.contains("arm,gic-400") {
Some(Box::new(gic::GenericInterruptController::new()))
} else if ic_str.contains("brcm,bcm2836-l1-intc") {
Some(Box::new(irq_bcm2836::Bcm2836ArmInterruptController::new()))
} else if ic_str.contains("brcm,bcm2836-armctrl-ic") {
Some(Box::new(irq_bcm2835::Bcm2835ArmInterruptController::new()))
} else {
warn!("no driver for interrupt controller {:?}", ic_str);
//TODO: return None and handle it properly
Some(Box::new(null::Null))
}
}
pub(crate) fn ic_for_chip(fdt: &Fdt, node: &FdtNode) -> Option<usize> {
if let Some(_) = node.property("interrupts-extended") {
error!("multi-parented device not supported");
None
} else if let Some(irqc_phandle) = node
.property("interrupt-parent")
.or(fdt.root().property("interrupt-parent"))
.and_then(|f| f.as_usize())
{
unsafe { IRQ_CHIP.phandle_to_ic_idx(irqc_phandle as u32) }
} else {
error!("no irq parent found");
None
}
}
+41
View File
@@ -0,0 +1,41 @@
use fdt::Fdt;
use syscall::{
error::{Error, EINVAL},
Result,
};
use super::InterruptController;
use crate::{
dtb::irqchip::{InterruptHandler, IrqCell, IrqDesc},
sync::CleanLockToken,
};
pub struct Null;
impl InterruptHandler for Null {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for Null {
fn irq_init(
&mut self,
_fdt_opt: Option<&Fdt>,
_irq_desc: &mut [IrqDesc; 1024],
_ic_idx: usize,
_irq_idx: &mut usize,
) -> Result<()> {
Ok(())
}
fn irq_ack(&mut self) -> u32 {
unimplemented!()
}
fn irq_eoi(&mut self, _irq_num: u32) {}
fn irq_enable(&mut self, _irq_num: u32) {}
fn irq_disable(&mut self, _irq_num: u32) {}
fn irq_xlate(&self, _irq_data: IrqCell) -> Result<usize> {
Err(Error::new(EINVAL))
}
fn irq_to_virq(&self, _hwirq: u32) -> Option<usize> {
None
}
}
+60
View File
@@ -0,0 +1,60 @@
use crate::info;
use core::sync::atomic::{AtomicUsize, Ordering};
use fdt::Fdt;
pub mod cpu;
pub mod generic_timer;
pub mod irqchip;
pub mod rtc;
pub mod serial;
use crate::dtb::irqchip::IRQ_CHIP;
use irqchip::ic_for_chip;
pub static ROOT_IC_IDX: AtomicUsize = AtomicUsize::new(0);
pub static ROOT_IC_IDX_IS_SET: AtomicUsize = AtomicUsize::new(0);
unsafe fn init_root_ic(fdt: &Fdt) {
unsafe {
let is_set = ROOT_IC_IDX_IS_SET.load(Ordering::Relaxed);
if is_set != 0 {
let ic_idx = ROOT_IC_IDX.load(Ordering::Relaxed);
info!("Already selected {} as root ic", ic_idx);
return;
}
let root_irqc_phandle = fdt
.root()
.property("interrupt-parent")
.unwrap()
.as_usize()
.unwrap();
let ic_idx = IRQ_CHIP
.phandle_to_ic_idx(root_irqc_phandle as u32)
.unwrap();
info!("select {} as root ic", ic_idx);
ROOT_IC_IDX.store(ic_idx, Ordering::Relaxed);
}
}
pub unsafe fn init_devicetree(fdt: &Fdt) {
unsafe {
info!("IRQCHIP INIT");
crate::dtb::irqchip::init(&fdt);
init_root_ic(&fdt);
info!("GIT INIT");
generic_timer::init(fdt);
info!("SERIAL INIT");
serial::init(fdt);
info!("RTC INIT");
rtc::init(fdt);
}
}
pub struct ArchPercpuMisc;
impl ArchPercpuMisc {
pub const fn default() -> Self {
Self
}
}
+41
View File
@@ -0,0 +1,41 @@
use crate::{dtb::get_mmio_address, sync::CleanLockToken, time};
use core::ptr::read_volatile;
static RTC_DR: usize = 0x000;
pub unsafe fn init(fdt: &fdt::Fdt) {
if let Some(node) = fdt.find_compatible(&["arm,pl031"]) {
match node
.reg()
.and_then(|mut iter| iter.next())
.and_then(|region| get_mmio_address(fdt, &node, &region))
{
Some(phys) => {
let mut rtc = Pl031rtc { phys };
info!("PL031 RTC at {:#x}", rtc.phys);
let mut token = unsafe { CleanLockToken::new() };
*time::START.lock(token.token()) = (rtc.time() as u128) * time::NANOS_PER_SEC;
}
None => {
warn!("No PL031 RTC registers");
}
}
} else {
warn!("No PL031 RTC found");
}
}
struct Pl031rtc {
pub phys: usize,
}
impl Pl031rtc {
unsafe fn read(&self, reg: usize) -> u32 {
unsafe { read_volatile((crate::PHYS_OFFSET + self.phys + reg) as *const u32) }
}
pub fn time(&mut self) -> u64 {
let seconds = unsafe { self.read(RTC_DR) } as u64;
seconds
}
}
+59
View File
@@ -0,0 +1,59 @@
use alloc::boxed::Box;
use fdt::Fdt;
pub use crate::dtb::serial::COM1;
use crate::{
arch::device::irqchip::ic_for_chip,
dtb::{
get_interrupt,
irqchip::{register_irq, InterruptHandler, IRQ_CHIP},
},
scheme::irq::irq_trigger,
sync::CleanLockToken,
};
pub struct Com1Irq {}
impl InterruptHandler for Com1Irq {
fn irq_handler(&mut self, irq: u32, token: &mut CleanLockToken) {
COM1.lock().receive(token);
unsafe {
// FIXME add_irq accepts a u8 as irq number
// PercpuBlock::current().stats.add_irq(irq);
irq_trigger(irq.try_into().unwrap(), token);
IRQ_CHIP.irq_eoi(irq);
}
}
}
pub unsafe fn init(fdt: &Fdt) {
unsafe {
//TODO: find actual serial device, not just any PL011
if let Some(node) = fdt.find_compatible(&["arm,pl011"]) {
let irq = get_interrupt(fdt, &node, 0).unwrap();
if let Some(ic_idx) = ic_for_chip(&fdt, &node) {
let virq = IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_xlate(irq)
.unwrap();
info!("serial_port virq = {}", virq);
register_irq(virq as u32, Box::new(Com1Irq {}));
IRQ_CHIP.irq_enable(virq as u32);
} else {
error!("serial port irq parent not found");
}
}
COM1.lock().enable_irq();
}
}
pub unsafe fn init_acpi(irq: u32) {
unsafe {
//TODO: what should chip index be?
let virq = IRQ_CHIP.irq_chip_list.chips[0].ic.irq_to_virq(irq).unwrap();
info!("serial_port virq = {}", virq);
register_irq(virq as u32, Box::new(Com1Irq {}));
IRQ_CHIP.irq_enable(virq as u32);
COM1.lock().enable_irq();
}
}
+236
View File
@@ -0,0 +1,236 @@
use ::syscall::Exception;
use rmm::VirtualAddress;
use crate::{
context::signal::excp_handler,
exception_stack,
memory::{ArchIntCtx, GenericPfFlags},
sync::CleanLockToken,
syscall,
};
use super::InterruptStack;
exception_stack!(synchronous_exception_at_el1_with_sp0, |stack| {
println!("Synchronous exception at EL1 with SP0");
stack.trace();
loop {}
});
fn exception_code(esr: usize) -> u8 {
((esr >> 26) & 0x3f) as u8
}
fn iss(esr: usize) -> u32 {
(esr & 0x01ff_ffff) as u32
}
unsafe fn far_el1() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, far_el1", out(reg) ret);
ret
}
}
unsafe fn instr_data_abort_inner(
stack: &mut InterruptStack,
from_user: bool,
instr_not_data: bool,
_from: &str,
) -> bool {
unsafe {
let iss = iss(stack.iret.esr_el1);
let fsc = iss & 0x3F;
//dbg!(fsc);
let was_translation_fault = fsc >= 0b000100 && fsc <= 0b000111;
//let was_permission_fault = fsc >= 0b001101 && fsc <= 0b001111;
let write_not_read_if_data = iss & (1 << 6) != 0;
let mut flags = GenericPfFlags::empty();
flags.set(GenericPfFlags::PRESENT, !was_translation_fault);
// TODO: RMW instructions may "involve" writing to (possibly invalid) memory, but AArch64
// doesn't appear to require that flag to be set if the read alone would trigger a fault.
flags.set(
GenericPfFlags::INVOLVED_WRITE,
write_not_read_if_data && !instr_not_data,
);
flags.set(GenericPfFlags::INSTR_NOT_DATA, instr_not_data);
flags.set(GenericPfFlags::USER_NOT_SUPERVISOR, from_user);
let faulting_addr = VirtualAddress::new(far_el1());
//dbg!(faulting_addr, flags, from);
crate::memory::page_fault_handler(stack, flags, faulting_addr).is_ok()
}
}
unsafe fn cntfrq_el0() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, cntfrq_el0", out(reg) ret);
ret
}
}
unsafe fn cntpct_el0() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, cntpct_el0", out(reg) ret);
ret
}
}
unsafe fn cntvct_el0() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, cntvct_el0", out(reg) ret);
ret
}
}
unsafe fn instr_trapped_msr_mrs_inner(
stack: &mut InterruptStack,
_from_user: bool,
_instr_not_data: bool,
_from: &str,
) -> bool {
unsafe {
let iss = iss(stack.iret.esr_el1);
// let res0 = (iss & 0x1C0_0000) >> 22;
let op0 = (iss & 0x030_0000) >> 20;
let op2 = (iss & 0x00e_0000) >> 17;
let op1 = (iss & 0x001_c000) >> 14;
let crn = (iss & 0x000_3c00) >> 10;
let rt = (iss & 0x000_03e0) >> 5;
let crm = (iss & 0x000_001e) >> 1;
let dir = iss & 0x000_0001;
/*
print!("iss=0x{:x}, res0=0b{:03b}, op0=0b{:02b}\n
op2=0b{:03b}, op1=0b{:03b}, crn=0b{:04b}\n
rt=0b{:05b}, crm=0b{:04b}, dir=0b{:b}\n",
iss, res0, op0, op2, op1, crn, rt, crm, dir);
*/
match (op0, op1, crn, crm, op2, dir) {
//MRS <Xt>, CNTFRQ_EL0
(0b11, 0b011, 0b1110, 0b0000, 0b000, 0b1) => {
let reg_val = cntfrq_el0();
stack.store_reg(rt as usize, reg_val);
//skip faulting instruction, A64 instructions are always 32-bits
stack.iret.elr_el1 += 4;
return true;
}
//MRS <Xt>, CNTPCT_EL0
(0b11, 0b011, 0b1110, 0b0000, 0b001, 0b1) => {
let reg_val = cntpct_el0();
stack.store_reg(rt as usize, reg_val);
//skip faulting instruction, A64 instructions are always 32-bits
stack.iret.elr_el1 += 4;
return true;
}
//MRS <Xt>, CNTVCT_EL0
(0b11, 0b011, 0b1110, 0b0000, 0b010, 0b1) => {
let reg_val = cntvct_el0();
stack.store_reg(rt as usize, reg_val);
//skip faulting instruction, A64 instructions are always 32-bits
stack.iret.elr_el1 += 4;
return true;
}
_ => {}
}
false
}
}
exception_stack!(synchronous_exception_at_el1_with_spx, |stack| {
unsafe {
if !pf_inner(
stack,
exception_code(stack.iret.esr_el1),
"sync_exc_el1_spx",
) {
println!("Synchronous exception at EL1 with SPx");
if exception_code(stack.iret.esr_el1) == 0b100101 {
let far_el1 = far_el1();
println!("FAR_EL1 = 0x{:08x}", far_el1);
} else if exception_code(stack.iret.esr_el1) == 0b100100 {
let far_el1 = far_el1();
println!("USER FAR_EL1 = 0x{:08x}", far_el1);
}
stack.trace();
loop {}
}
}
});
unsafe fn pf_inner(stack: &mut InterruptStack, ty: u8, from: &str) -> bool {
unsafe {
match ty {
// "Data Abort taken from a lower Exception level"
0b100100 => instr_data_abort_inner(stack, true, false, from),
// "Data Abort taken without a change in Exception level"
0b100101 => instr_data_abort_inner(stack, false, false, from),
// "Instruction Abort taken from a lower Exception level"
0b100000 => instr_data_abort_inner(stack, true, true, from),
// "Instruction Abort taken without a change in Exception level"
0b100001 => instr_data_abort_inner(stack, false, true, from),
// "Trapped MSR, MRS or System instruction execution in AArch64 state"
0b011000 => instr_trapped_msr_mrs_inner(stack, true, true, from),
_ => return false,
}
}
}
exception_stack!(synchronous_exception_at_el0, |stack| {
unsafe {
match exception_code(stack.iret.esr_el1) {
0b010101 => {
let scratch = &stack.scratch;
let mut token = CleanLockToken::new();
let ret = syscall::syscall(
scratch.x8, scratch.x0, scratch.x1, scratch.x2, scratch.x3, scratch.x4,
scratch.x5, &mut token,
);
stack.scratch.x0 = ret;
}
ty => {
if !pf_inner(stack, ty as u8, "sync_exc_el0") {
error!(
"FATAL: Not an SVC induced synchronous exception (ty={:b})",
ty
);
println!("FAR_EL1: {:#0x}", far_el1());
//crate::debugger::debugger(None);
stack.trace();
excp_handler(Exception {
kind: 0, // TODO
});
}
}
}
}
});
exception_stack!(unhandled_exception, |stack| {
println!("Unhandled exception");
stack.trace();
loop {}
});
impl ArchIntCtx for InterruptStack {
fn ip(&self) -> usize {
self.iret.elr_el1
}
fn recover_and_efault(&mut self) {
// Set the return value to nonzero to indicate usercopy failure (EFAULT), and emulate the
// return instruction by setting the return pointer to the saved LR value.
self.iret.elr_el1 = self.preserved.x30;
self.scratch.x0 = 1;
}
}
+420
View File
@@ -0,0 +1,420 @@
use crate::{panic, syscall::IntRegisters};
#[derive(Default)]
#[repr(C, packed)]
pub struct ScratchRegisters {
pub x0: usize,
pub x1: usize,
pub x2: usize,
pub x3: usize,
pub x4: usize,
pub x5: usize,
pub x6: usize,
pub x7: usize,
pub x8: usize,
pub x9: usize,
pub x10: usize,
pub x11: usize,
pub x12: usize,
pub x13: usize,
pub x14: usize,
pub x15: usize,
pub x16: usize,
pub x17: usize,
pub x18: usize,
pub _padding: usize,
}
impl ScratchRegisters {
pub fn dump(&self) {
println!("X0: {:>016X}", { self.x0 });
println!("X1: {:>016X}", { self.x1 });
println!("X2: {:>016X}", { self.x2 });
println!("X3: {:>016X}", { self.x3 });
println!("X4: {:>016X}", { self.x4 });
println!("X5: {:>016X}", { self.x5 });
println!("X6: {:>016X}", { self.x6 });
println!("X7: {:>016X}", { self.x7 });
println!("X8: {:>016X}", { self.x8 });
println!("X9: {:>016X}", { self.x9 });
println!("X10: {:>016X}", { self.x10 });
println!("X11: {:>016X}", { self.x11 });
println!("X12: {:>016X}", { self.x12 });
println!("X13: {:>016X}", { self.x13 });
println!("X14: {:>016X}", { self.x14 });
println!("X15: {:>016X}", { self.x15 });
println!("X16: {:>016X}", { self.x16 });
println!("X17: {:>016X}", { self.x17 });
println!("X18: {:>016X}", { self.x18 });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct PreservedRegisters {
//TODO: is X30 a preserved register?
pub x19: usize,
pub x20: usize,
pub x21: usize,
pub x22: usize,
pub x23: usize,
pub x24: usize,
pub x25: usize,
pub x26: usize,
pub x27: usize,
pub x28: usize,
pub x29: usize,
pub x30: usize,
}
impl PreservedRegisters {
pub fn dump(&self) {
println!("X19: {:>016X}", { self.x19 });
println!("X20: {:>016X}", { self.x20 });
println!("X21: {:>016X}", { self.x21 });
println!("X22: {:>016X}", { self.x22 });
println!("X23: {:>016X}", { self.x23 });
println!("X24: {:>016X}", { self.x24 });
println!("X25: {:>016X}", { self.x25 });
println!("X26: {:>016X}", { self.x26 });
println!("X27: {:>016X}", { self.x27 });
println!("X28: {:>016X}", { self.x28 });
println!("X29: {:>016X}", { self.x29 });
println!("X30: {:>016X}", { self.x30 });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct IretRegisters {
// occurred
// The exception vector disambiguates at which EL the interrupt
pub sp_el0: usize, // Shouldn't be used if interrupt occurred at EL1
pub esr_el1: usize,
pub spsr_el1: usize,
pub elr_el1: usize,
}
impl IretRegisters {
pub fn dump(&self) {
println!("ELR_EL1: {:>016X}", { self.elr_el1 });
println!("SPSR_EL1: {:>016X}", { self.spsr_el1 });
println!("ESR_EL1: {:>016X}", { self.esr_el1 });
println!("SP_EL0: {:>016X}", { self.sp_el0 });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct InterruptStack {
pub iret: IretRegisters,
pub scratch: ScratchRegisters,
pub preserved: PreservedRegisters,
}
impl InterruptStack {
pub fn init(&mut self) {}
pub fn frame_pointer(&self) -> usize {
self.preserved.x29
}
pub fn stack_pointer(&self) -> usize {
self.iret.sp_el0
}
pub fn set_stack_pointer(&mut self, sp: usize) {
self.iret.sp_el0 = sp;
}
pub fn sig_archdep_reg(&self) -> usize {
self.scratch.x0
}
pub fn set_instr_pointer(&mut self, ip: usize) {
self.iret.elr_el1 = ip;
}
pub fn instr_pointer(&self) -> usize {
self.iret.elr_el1
}
pub fn set_arg1(&mut self, arg_opt: Option<usize>) {
if let Some(arg) = arg_opt {
self.scratch.x1 = arg;
}
}
pub fn dump(&self) {
self.iret.dump();
self.scratch.dump();
self.preserved.dump();
}
pub fn trace(&self) {
self.dump();
unsafe {
panic::user_stack_trace(&self);
panic::stack_trace();
}
}
/// Saves all registers to a struct used by the proc:
/// scheme to read/write registers.
pub fn save(&self, all: &mut IntRegisters) {
/*TODO: aarch64 registers
all.elr_el1 = self.iret.elr_el1;
all.spsr_el1 = self.iret.spsr_el1;
all.esr_el1 = self.iret.esr_el1;
all.sp_el0 = self.iret.sp_el0;
all.padding = 0;
*/
all.x30 = self.preserved.x30;
all.x29 = self.preserved.x29;
all.x28 = self.preserved.x28;
all.x27 = self.preserved.x27;
all.x26 = self.preserved.x26;
all.x25 = self.preserved.x25;
all.x24 = self.preserved.x24;
all.x23 = self.preserved.x23;
all.x22 = self.preserved.x22;
all.x21 = self.preserved.x21;
all.x20 = self.preserved.x20;
all.x19 = self.preserved.x19;
all.x18 = self.scratch.x18;
all.x17 = self.scratch.x17;
all.x16 = self.scratch.x16;
all.x15 = self.scratch.x15;
all.x14 = self.scratch.x14;
all.x13 = self.scratch.x13;
all.x12 = self.scratch.x12;
all.x11 = self.scratch.x11;
all.x10 = self.scratch.x10;
all.x9 = self.scratch.x9;
all.x8 = self.scratch.x8;
all.x7 = self.scratch.x7;
all.x6 = self.scratch.x6;
all.x5 = self.scratch.x5;
all.x4 = self.scratch.x4;
all.x3 = self.scratch.x3;
all.x2 = self.scratch.x2;
all.x1 = self.scratch.x1;
all.x0 = self.scratch.x0;
}
/// Loads all registers from a struct used by the proc:
/// scheme to read/write registers.
pub fn load(&mut self, all: &IntRegisters) {
/*TODO: aarch64 registers
self.iret.elr_el1 = all.elr_el1;
self.iret.spsr_el1 = all.spsr_el1;
self.iret.esr_el1 = all.esr_el1;
self.iret.sp_el0 = all.sp_el0;
*/
self.preserved.x30 = all.x30;
self.preserved.x29 = all.x29;
self.preserved.x28 = all.x28;
self.preserved.x27 = all.x27;
self.preserved.x26 = all.x26;
self.preserved.x25 = all.x25;
self.preserved.x24 = all.x24;
self.preserved.x23 = all.x23;
self.preserved.x22 = all.x22;
self.preserved.x21 = all.x21;
self.preserved.x20 = all.x20;
self.preserved.x19 = all.x19;
self.scratch.x18 = all.x18;
self.scratch.x17 = all.x17;
self.scratch.x16 = all.x16;
self.scratch.x15 = all.x15;
self.scratch.x14 = all.x14;
self.scratch.x13 = all.x13;
self.scratch.x12 = all.x12;
self.scratch.x11 = all.x11;
self.scratch.x10 = all.x10;
self.scratch.x9 = all.x9;
self.scratch.x8 = all.x8;
self.scratch.x7 = all.x7;
self.scratch.x6 = all.x6;
self.scratch.x5 = all.x5;
self.scratch.x4 = all.x4;
self.scratch.x3 = all.x3;
self.scratch.x2 = all.x2;
self.scratch.x1 = all.x1;
self.scratch.x0 = all.x0;
}
/// Store a specific generic registers
pub fn store_reg(&mut self, idx: usize, val: usize) {
match idx {
0 => self.scratch.x0 = val,
1 => self.scratch.x1 = val,
2 => self.scratch.x2 = val,
3 => self.scratch.x3 = val,
4 => self.scratch.x4 = val,
5 => self.scratch.x5 = val,
6 => self.scratch.x6 = val,
7 => self.scratch.x7 = val,
8 => self.scratch.x8 = val,
9 => self.scratch.x9 = val,
10 => self.scratch.x10 = val,
11 => self.scratch.x11 = val,
12 => self.scratch.x12 = val,
13 => self.scratch.x13 = val,
14 => self.scratch.x14 = val,
15 => self.scratch.x15 = val,
16 => self.scratch.x16 = val,
17 => self.scratch.x17 = val,
18 => self.scratch.x18 = val,
19 => self.preserved.x19 = val,
20 => self.preserved.x20 = val,
21 => self.preserved.x21 = val,
22 => self.preserved.x22 = val,
23 => self.preserved.x23 = val,
24 => self.preserved.x24 = val,
25 => self.preserved.x25 = val,
26 => self.preserved.x26 = val,
27 => self.preserved.x27 = val,
28 => self.preserved.x28 = val,
29 => self.preserved.x29 = val,
30 => self.preserved.x30 = val,
_ => {}
}
}
//TODO
pub fn set_singlestep(&mut self, _singlestep: bool) {}
}
#[macro_export]
macro_rules! push_scratch {
() => {
"
// Push scratch registers
str x18, [sp, #-16]!
stp x16, x17, [sp, #-16]!
stp x14, x15, [sp, #-16]!
stp x12, x13, [sp, #-16]!
stp x10, x11, [sp, #-16]!
stp x8, x9, [sp, #-16]!
stp x6, x7, [sp, #-16]!
stp x4, x5, [sp, #-16]!
stp x2, x3, [sp, #-16]!
stp x0, x1, [sp, #-16]!
"
};
}
#[macro_export]
macro_rules! pop_scratch {
() => {
"
// Pop scratch registers
ldp x0, x1, [sp], #16
ldp x2, x3, [sp], #16
ldp x4, x5, [sp], #16
ldp x6, x7, [sp], #16
ldp x8, x9, [sp], #16
ldp x10, x11, [sp], #16
ldp x12, x13, [sp], #16
ldp x14, x15, [sp], #16
ldp x16, x17, [sp], #16
ldr x18, [sp], #16
"
};
}
#[macro_export]
macro_rules! push_preserved {
() => {
"
// Push preserved registers
stp x29, x30, [sp, #-16]!
stp x27, x28, [sp, #-16]!
stp x25, x26, [sp, #-16]!
stp x23, x24, [sp, #-16]!
stp x21, x22, [sp, #-16]!
stp x19, x20, [sp, #-16]!
"
};
}
#[macro_export]
macro_rules! pop_preserved {
() => {
"
// Pop preserved registers
ldp x19, x20, [sp], #16
ldp x21, x22, [sp], #16
ldp x23, x24, [sp], #16
ldp x25, x26, [sp], #16
ldp x27, x28, [sp], #16
ldp x29, x30, [sp], #16
"
};
}
#[macro_export]
macro_rules! push_special {
() => {
"
mrs x14, spsr_el1
mrs x15, elr_el1
stp x14, x15, [sp, #-16]!
mrs x14, sp_el0
mrs x15, esr_el1
stp x14, x15, [sp, #-16]!
"
};
}
#[macro_export]
macro_rules! pop_special {
() => {
"
ldp x14, x15, [sp], 16
msr esr_el1, x15
msr sp_el0, x14
ldp x14, x15, [sp], 16
msr elr_el1, x15
msr spsr_el1, x14
"
};
}
#[macro_export]
macro_rules! exception_stack {
($name:ident, |$stack:ident| $code:block) => {
#[unsafe(naked)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(stack: &mut $crate::arch::aarch64::interrupt::InterruptStack) {
unsafe extern "C" fn inner($stack: &mut $crate::arch::aarch64::interrupt::InterruptStack) {
$code
}
core::arch::naked_asm!(
// Backup all userspace registers to stack
push_preserved!(),
push_scratch!(),
push_special!(),
// Call inner function with pointer to stack
"mov x29, sp",
"mov x0, sp",
"bl {}",
// Restore all userspace registers
pop_special!(),
pop_scratch!(),
pop_preserved!(),
"eret",
sym inner,
);
}
};
}
#[unsafe(naked)]
pub unsafe extern "C" fn enter_usermode() -> ! {
core::arch::naked_asm!(
"blr x28",
// Restore all userspace registers
pop_special!(),
pop_scratch!(),
pop_preserved!(),
"eret",
);
}
+56
View File
@@ -0,0 +1,56 @@
use crate::{arch::device::ROOT_IC_IDX, dtb::irqchip::IRQ_CHIP, sync::CleanLockToken};
use core::sync::atomic::Ordering;
// use crate::percpu::PercpuBlock;
unsafe fn irq_ack() -> (u32, Option<usize>) {
unsafe {
let ic = &mut IRQ_CHIP.irq_chip_list.chips[ROOT_IC_IDX.load(Ordering::Relaxed)].ic;
let irq = ic.irq_ack();
(irq, ic.irq_to_virq(irq))
}
}
exception_stack!(irq_at_el0, |_stack| {
unsafe {
let mut token = CleanLockToken::new();
let (irq, virq) = irq_ack();
if let Some(virq) = virq
&& virq < 1024
{
IRQ_CHIP.trigger_virq(virq as u32, &mut token);
} else {
println!("unexpected irq num {}", irq);
}
}
});
exception_stack!(irq_at_el1, |_stack| {
unsafe {
let mut token = CleanLockToken::new();
let (irq, virq) = irq_ack();
if let Some(virq) = virq
&& virq < 1024
{
IRQ_CHIP.trigger_virq(virq as u32, &mut token);
} else {
println!("unexpected irq num {}", irq);
}
}
});
/*
pub unsafe fn irq_handler_gentimer(irq: u32) {
GENTIMER.clear_irq();
{
*time::OFFSET.lock() += GENTIMER.clk_freq as u128;
}
timeout::trigger();
context::switch::tick();
trigger(irq);
GENTIMER.reload_count();
}
*/
+49
View File
@@ -0,0 +1,49 @@
//! Interrupt instructions
use core::arch::asm;
#[macro_use]
pub mod handler;
pub mod exception;
pub mod irq;
pub mod syscall;
pub mod trace;
pub use self::handler::InterruptStack;
/// Clear interrupts
#[inline(always)]
pub unsafe fn disable() {
unsafe {
asm!("msr daifset, #2");
}
}
/// Set interrupts and halt
/// This will atomically wait for the next interrupt
/// Performing enable followed by halt is not guaranteed to be atomic, use this instead!
#[inline(always)]
pub unsafe fn enable_and_halt() {
unsafe {
asm!("wfi", "msr daifclr, #2", "nop");
}
}
/// Set interrupts and nop
/// This will enable interrupts and allow the IF flag to be processed
/// Simply enabling interrupts does not gurantee that they will trigger, use this instead!
#[inline(always)]
pub unsafe fn enable_and_nop() {
unsafe {
asm!("msr daifclr, #2", "nop");
}
}
/// Halt instruction
#[inline(always)]
pub unsafe fn halt() {
unsafe {
asm!("wfi");
}
}
+49
View File
@@ -0,0 +1,49 @@
#[unsafe(no_mangle)]
pub unsafe extern "C" fn do_exception_unhandled() {}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn do_exception_synchronous() {}
#[allow(dead_code)]
#[repr(C, packed)]
pub struct SyscallStack {
pub elr_el1: usize,
pub padding: usize,
pub tpidr: usize,
pub tpidrro: usize,
pub rflags: usize,
pub esr: usize,
pub sp: usize,
pub lr: usize,
pub fp: usize,
pub x28: usize,
pub x27: usize,
pub x26: usize,
pub x25: usize,
pub x24: usize,
pub x23: usize,
pub x22: usize,
pub x21: usize,
pub x20: usize,
pub x19: usize,
pub x18: usize,
pub x17: usize,
pub x16: usize,
pub x15: usize,
pub x14: usize,
pub x13: usize,
pub x12: usize,
pub x11: usize,
pub x10: usize,
pub x9: usize,
pub x8: usize,
pub x7: usize,
pub x6: usize,
pub x5: usize,
pub x4: usize,
pub x3: usize,
pub x2: usize,
pub x1: usize,
pub x0: usize,
}
pub use super::handler::enter_usermode;

Some files were not shown because too many files have changed in this diff Show More