rustyn64_cpu/addr.rs
1//! Virtual → physical address translation (T-11-006, T-12-004).
2//!
3//! Every address the CPU hands to the [`crate::Bus`] is **physical**. This is
4//! where that becomes true: the MIPS segment map is applied here, inside the CPU
5//! crate, exactly as `docs/cpu.md` specifies.
6//!
7//! # The segment map
8//!
9//! The map is **not** a property of the address alone: it depends on the
10//! privilege mode and on whether that mode is using 64-bit addressing. A segment
11//! that is perfectly ordinary in Kernel mode is an **address error** in User
12//! mode, and it is that check — not the TLB — that stops a user program reaching
13//! `KSEG0`. See [`Access`].
14//!
15//! ## Kernel, 32-bit addressing
16//!
17//! | Segment | Range | Mapping |
18//! |---|---|---|
19//! | KUSEG | `0x0000_0000`–`0x7FFF_FFFF` | TLB-mapped |
20//! | KSEG0 | `0x8000_0000`–`0x9FFF_FFFF` | direct, **cached** |
21//! | KSEG1 | `0xA000_0000`–`0xBFFF_FFFF` | direct, **uncached** |
22//! | KSSEG/KSEG3 | `0xC000_0000`–`0xFFFF_FFFF` | TLB-mapped |
23//!
24//! KSEG0 and KSEG1 are *unmapped*: they address the same physical memory and
25//! differ only in cacheability, so translation is a subtraction. That is why a
26//! ROM entry point of `0x8000_1000` and a hardware register at `0xA430_0000`
27//! both work without any TLB.
28//!
29//! ## Supervisor and User, 32-bit addressing
30//!
31//! Supervisor sees SUSEG (`0x0000_0000`–`0x7FFF_FFFF`) and SSEG
32//! (`0xC000_0000`–`0xDFFF_FFFF`), both mapped. User sees USEG
33//! (`0x0000_0000`–`0x7FFF_FFFF`) alone. **Everything else is an address error**,
34//! including the range that would be `KSEG0`.
35//!
36//! ## 64-bit addressing
37//!
38//! With `Status.KX`/`SX`/`UX` set for the current mode, each mapped segment
39//! widens to 2^40 and the address space grows holes: an address inside a
40//! segment's *region* but past its size is an address error, which is what the
41//! `..._gap` cases in n64-systemtest's privilege matrix check.
42//!
43//! Kernel additionally gains **XKPHYS**, `0x8000_..`–`0xBFFF_..`: eight 2^32
44//! direct-mapped windows selected by bits 61:59, differing only in cacheability.
45//! Bits 58:32 must be zero or the access is an address error.
46//!
47//! The compatibility segments `CKSEG0`–`CKSEG3` sit at `0xFFFF_FFFF_8000_0000`
48//! upward and behave exactly as their 32-bit namesakes.
49//!
50//! The mapped segments go through the TLB ([`translate_via`]).
51//!
52//! An earlier `translate` masked mapped addresses to their low 29 bits — right
53//! for the identity mappings early boot code uses, wrong for anything else. It
54//! was **deleted** rather than kept "for unmapped-only paths" once nothing
55//! called it: an unused function that quietly gets translation wrong is exactly
56//! the inert-API hazard `docs/engineering-lessons.md` §3.2 describes.
57
58use crate::tlb::{Tlb, TlbFault};
59use serde::{Deserialize, Serialize};
60
61/// Whether an access goes through the caches.
62///
63/// Not yet consumed: the I- and D-caches are Sprint 2 work. It is returned now
64/// because the *segment* determines it, and recomputing that later from a
65/// physical address is impossible — the information is gone once KSEG0 and KSEG1
66/// have both become the same number.
67#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
68pub enum Cached {
69 /// Through the cache (KSEG0, and mapped segments per their TLB entry).
70 Yes,
71 /// Bypassing it (KSEG1).
72 No,
73}
74
75/// A translated address.
76#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
77pub struct Physical {
78 /// The physical address.
79 pub addr: u32,
80 /// Whether the access is cached.
81 pub cached: Cached,
82}
83
84/// Which segment a virtual address falls in, and how it translates.
85#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
86pub enum Segment {
87 /// Unmapped: physical address is the virtual one minus the segment base.
88 Direct {
89 /// The physical address.
90 addr: u32,
91 /// Whether the access is cached.
92 cached: Cached,
93 },
94 /// TLB-mapped: KUSEG, KSSEG or KSEG3. The TLB decides the physical address
95 /// *and* the cacheability, from the matching entry's `C` field.
96 Mapped,
97 /// Not a valid address in this mode: raises an address error **before** the
98 /// TLB is consulted.
99 ///
100 /// A distinct variant rather than a `TlbFault`, because the two produce
101 /// different exceptions from different vectors. Folding an out-of-range
102 /// kernel address into a TLB refill would send a user program to the refill
103 /// handler, where a well-behaved kernel would map the page and hand it the
104 /// access it was never allowed to make.
105 Invalid,
106}
107
108/// The privilege mode an access is made in.
109#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
110pub enum Mode {
111 /// `Status.KSU == 0`, or `EXL`/`ERL` set.
112 Kernel,
113 /// `Status.KSU == 1`.
114 Supervisor,
115 /// `Status.KSU == 2`.
116 User,
117}
118
119/// Everything outside the address itself that decides how it translates.
120#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
121pub struct Access {
122 /// The privilege mode.
123 pub mode: Mode,
124 /// `Status.KX`/`SX`/`UX` — for [`Access::mode`], not all three.
125 ///
126 /// One flag rather than three because only the current mode's bit ever
127 /// applies; carrying all three would invite reading the wrong one.
128 pub wide: bool,
129 /// `Status.ERL` — makes KUSEG unmapped and uncached (UM §5.2.2).
130 pub erl: bool,
131}
132
133impl Access {
134 /// The mode the CPU is in at reset and inside any exception handler.
135 #[must_use]
136 pub const fn kernel() -> Self {
137 Self {
138 mode: Mode::Kernel,
139 wide: false,
140 erl: false,
141 }
142 }
143}
144
145/// Size of a mapped segment under 64-bit addressing: 2^40.
146const XSEG_SIZE: u64 = 1 << 40;
147
148/// Classify a virtual address by segment (UM §5.2.4, Tables 5-3/5-4).
149///
150/// This is the half of translation that does **not** need the TLB, split out so
151/// callers with no TLB (and the many unmapped accesses) do not pay for one.
152#[must_use]
153pub const fn segment(vaddr: u64, access: Access) -> Segment {
154 // "If the ERL bit of the Status register is 1, the user address area is a
155 // 2 GB area that cannot be cached without TLB mapping (i.e., the virtual
156 // addresses are used as physical addresses as is)" (UM §5.2.2, p. 129).
157 //
158 // This is not an obscure corner: **cold reset sets ERL** (UM §6.4.4), so it
159 // is the state every boot ROM starts in. Without it, the first store to a
160 // low address takes a TLB refill before any mapping could possibly exist --
161 // which is exactly how n64-systemtest failed, two instructions in.
162 // Compared on the FULL 64-bit address, not the truncated low word. The
163 // manual says a **2 GB** area, so `0x0000_0001_0000_1000` is outside it —
164 // while its low 32 bits are not, and truncating would direct-map it.
165 if access.erl && vaddr < 0x8000_0000 {
166 return Segment::Direct {
167 addr: vaddr as u32,
168 cached: Cached::No,
169 };
170 }
171 match access.mode {
172 Mode::User => user_segment(vaddr, access.wide),
173 Mode::Supervisor => supervisor_segment(vaddr, access.wide),
174 Mode::Kernel => kernel_segment(vaddr, access.wide),
175 }
176}
177
178/// Is `vaddr` a sign-extended 32-bit address?
179///
180/// Under 32-bit addressing every valid address is one; anything else is an
181/// address error rather than a truncation. Truncating instead is the natural
182/// shortcut and it silently accepts `0x0000_0001_8000_1000` as `KSEG0`.
183const fn is_compat(vaddr: u64) -> bool {
184 vaddr as i64 as i32 as i64 as u64 == vaddr
185}
186
187/// The 32-bit compatibility segments above `0xFFFF_FFFF_8000_0000`.
188///
189/// Shared by Kernel's 32- and 64-bit maps, where they are `KSEG0..3` and
190/// `CKSEG0..3` respectively — the same four segments under two names.
191const fn compat_kernel_segment(v: u32) -> Segment {
192 match v {
193 0x8000_0000..=0x9FFF_FFFF => Segment::Direct {
194 addr: v - 0x8000_0000,
195 cached: Cached::Yes,
196 },
197 0xA000_0000..=0xBFFF_FFFF => Segment::Direct {
198 addr: v - 0xA000_0000,
199 cached: Cached::No,
200 },
201 // KUSEG, KSSEG and KSEG3 are all TLB-mapped.
202 _ => Segment::Mapped,
203 }
204}
205
206/// User mode: one segment, and everything else is an address error.
207const fn user_segment(vaddr: u64, wide: bool) -> Segment {
208 if wide {
209 // XUSEG, 2^40. The region runs to 2^62 but the segment does not, and the
210 // hole between them faults.
211 if vaddr < XSEG_SIZE {
212 Segment::Mapped
213 } else {
214 Segment::Invalid
215 }
216 } else if is_compat(vaddr) && (vaddr as u32) < 0x8000_0000 {
217 Segment::Mapped
218 } else {
219 Segment::Invalid
220 }
221}
222
223/// Supervisor mode: SUSEG and SSEG, plus their 64-bit widenings.
224const fn supervisor_segment(vaddr: u64, wide: bool) -> Segment {
225 if wide {
226 // In order: XSUSEG, XSSEG, and CSSEG (the compatibility window, mapped
227 // like SSEG). All three are TLB-mapped, so they share one arm.
228 match vaddr {
229 0..0x0000_0100_0000_0000
230 | 0x4000_0000_0000_0000..0x4000_0100_0000_0000
231 | 0xFFFF_FFFF_C000_0000..=0xFFFF_FFFF_DFFF_FFFF => Segment::Mapped,
232 _ => Segment::Invalid,
233 }
234 } else if !is_compat(vaddr) {
235 Segment::Invalid
236 } else {
237 // SUSEG and SSEG, both TLB-mapped.
238 match vaddr as u32 {
239 0x0000_0000..=0x7FFF_FFFF | 0xC000_0000..=0xDFFF_FFFF => Segment::Mapped,
240 // Including the range that would be KSEG0/KSEG1 in Kernel mode: the
241 // privilege check, not the TLB, is what keeps Supervisor out of it.
242 _ => Segment::Invalid,
243 }
244 }
245}
246
247/// Kernel mode: the whole map, and under 64-bit addressing also XKPHYS.
248const fn kernel_segment(vaddr: u64, wide: bool) -> Segment {
249 if !wide {
250 return if is_compat(vaddr) {
251 compat_kernel_segment(vaddr as u32)
252 } else {
253 Segment::Invalid
254 };
255 }
256 match vaddr {
257 // XKUSEG, XKSSEG and XKSEG: 2^40 each (XKSEG a little less), each with a
258 // hole above it, and all TLB-mapped -- so one arm.
259 0..0x0000_0100_0000_0000
260 | 0x4000_0000_0000_0000..0x4000_0100_0000_0000
261 | 0xC000_0000_0000_0000..0xC000_00FF_8000_0000 => Segment::Mapped,
262 // XKPHYS: eight direct windows chosen by bits 61:59, each 2^32 wide and
263 // differing only in cacheability. Bits 58:32 must be zero.
264 0x8000_0000_0000_0000..0xC000_0000_0000_0000 => xkphys(vaddr),
265 // CKSEG0..3 — the same four segments as the 32-bit map.
266 0xFFFF_FFFF_8000_0000..=0xFFFF_FFFF_FFFF_FFFF => compat_kernel_segment(vaddr as u32),
267 _ => Segment::Invalid,
268 }
269}
270
271/// One of the eight XKPHYS windows.
272///
273/// The cacheability comes from the `C` encoding in bits 61:59, the same three-bit
274/// field a TLB entry carries — and by the same rule, **only `C == 2` is
275/// uncached** (UM Table 5-6).
276const fn xkphys(vaddr: u64) -> Segment {
277 if vaddr & 0x07FF_FFFF_0000_0000 != 0 {
278 return Segment::Invalid;
279 }
280 let c = (vaddr >> 59) & 0b111;
281 Segment::Direct {
282 addr: vaddr as u32,
283 cached: if c == 2 { Cached::No } else { Cached::Yes },
284 }
285}
286
287/// Why a translation failed.
288///
289/// Two variants rather than one because they raise **different exceptions from
290/// different vectors**: an address error goes to the general handler, a TLB
291/// fault to the refill vector. Collapsing them would send a user program that
292/// touched a kernel address to the refill handler, where a well-behaved kernel
293/// maps the page and grants the access it was never allowed to make.
294#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
295pub enum TranslateError {
296 /// The address is not valid in this privilege mode: `AdEL`/`AdES`.
297 Address,
298 /// The address is valid but the TLB could not resolve it.
299 Tlb(TlbFault),
300}
301
302/// Translate a virtual address, consulting the TLB for the mapped segments.
303///
304/// # Errors
305///
306/// [`TranslateError::Address`] when the address is not valid in `access`'s mode,
307/// and [`TranslateError::Tlb`] when a mapped access misses, hits an invalid
308/// entry, or stores to a non-writable page. Unmapped segments cannot fail.
309pub fn translate_via(
310 tlb: &mut Tlb,
311 vaddr: u64,
312 asid: u8,
313 store: bool,
314 access: Access,
315) -> Result<Physical, TranslateError> {
316 match segment(vaddr, access) {
317 Segment::Direct { addr, cached } => Ok(Physical { addr, cached }),
318 Segment::Mapped => {
319 let t = tlb
320 .lookup(vaddr, asid, store)
321 .map_err(TranslateError::Tlb)?;
322 Ok(Physical {
323 addr: t.addr,
324 // Cacheability of a mapped page comes from its entry's `C`
325 // field, not from the segment -- which is why `Cached` has to be
326 // returned from here rather than derived from the address later.
327 cached: if t.uncached { Cached::No } else { Cached::Yes },
328 })
329 }
330 Segment::Invalid => Err(TranslateError::Address),
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 /// Resolve an unmapped address, panicking if it is mapped — a test helper,
339 /// so the assertions below stay about addresses rather than about `match`.
340 fn direct(vaddr: u64) -> Physical {
341 match segment(vaddr, Access::kernel()) {
342 Segment::Direct { addr, cached } => Physical { addr, cached },
343 Segment::Mapped => panic!("{vaddr:#X} is TLB-mapped, not direct"),
344 Segment::Invalid => panic!("{vaddr:#X} is not valid in kernel mode"),
345 }
346 }
347
348 /// KSEG0 and KSEG1 address the *same* physical memory and differ only in
349 /// cacheability. Emitting different physical addresses for them is a classic
350 /// bug that makes uncached register writes land in RDRAM.
351 #[test]
352 fn kseg0_and_kseg1_alias_the_same_physical_memory() {
353 for phys in [0u32, 0x1000, 0x0080_0000, 0x0400_0000, 0x1000_0000] {
354 let k0 = direct(0xFFFF_FFFF_8000_0000 + u64::from(phys));
355 let k1 = direct(0xFFFF_FFFF_A000_0000 + u64::from(phys));
356 assert_eq!(k0.addr, phys, "KSEG0 + {phys:#X}");
357 assert_eq!(k1.addr, phys, "KSEG1 + {phys:#X}");
358 assert_eq!(k0.cached, Cached::Yes);
359 assert_eq!(k1.cached, Cached::No, "KSEG1 is uncached");
360 }
361 }
362
363 /// The addresses that actually matter for bring-up. All unmapped, which is
364 /// why early boot works before any TLB entry exists.
365 #[test]
366 fn the_boot_and_register_addresses_translate_correctly() {
367 // basic.z64's entry point.
368 assert_eq!(direct(0xFFFF_FFFF_8000_1000).addr, 0x1000);
369 // n64-systemtest's entry point.
370 assert_eq!(direct(0xFFFF_FFFF_800A_15E8).addr, 0x000A_15E8);
371 // The PIF RAM word basic.z64 writes before its first test.
372 assert_eq!(direct(0xFFFF_FFFF_BFC0_07FC).addr, 0x1FC0_07FC);
373 // Cart domain 1, where a ROM is memory-mapped.
374 assert_eq!(direct(0xFFFF_FFFF_B000_0000).addr, 0x1000_0000);
375 // The RSP DMEM base.
376 assert_eq!(direct(0xFFFF_FFFF_A400_0000).addr, 0x0400_0000);
377 }
378
379 /// A 64-bit register holding a 32-bit address is sign-extended, so KSEG0
380 /// arrives as `0xFFFF_FFFF_8xxx_xxxx`. Classifying that as a 64-bit value
381 /// rather than its low half sends it to the wrong segment.
382 #[test]
383 fn a_sign_extended_32_bit_address_still_translates() {
384 assert_eq!(direct(0xFFFF_FFFF_8000_1000).addr, 0x1000);
385 assert_eq!(
386 direct(0xFFFF_FFFF_A400_0000).cached,
387 Cached::No,
388 "sign extension must not lose the segment"
389 );
390 }
391
392 /// KUSEG, KSSEG and KSEG3 are **mapped** — the TLB decides, and a miss
393 /// raises. Before T-12-004 they were silently masked to their low 29 bits,
394 /// which aliased every unmapped access onto real memory instead of faulting.
395 #[test]
396 fn the_mapped_segments_are_reported_as_mapped_not_masked() {
397 for v in [
398 0x0000_0000u64, // KUSEG
399 0x0000_1000,
400 0x7FFF_FFFF,
401 0xFFFF_FFFF_C000_0000, // KSSEG
402 0xFFFF_FFFF_E000_0000, // KSEG3
403 0xFFFF_FFFF_FFFF_FFFF,
404 ] {
405 assert_eq!(
406 segment(v, Access::kernel()),
407 Segment::Mapped,
408 "{v:#X} must go through the TLB"
409 );
410 }
411 }
412
413 /// **`ERL = 1` makes KUSEG unmapped and uncached** (UM §5.2.2, p. 129):
414 /// *"the user address area is a 2 GB area that cannot be cached without TLB
415 /// mapping (i.e., the virtual addresses are used as physical addresses as
416 /// is)."*
417 ///
418 /// This is not a corner case — **cold reset sets `ERL`** (UM §6.4.4), so it
419 /// is the state every boot ROM starts in. Without it the first store to a low
420 /// address takes a TLB refill before any mapping could exist, which is
421 /// exactly how n64-systemtest failed: two instructions in, `ExcCode = TLBS`,
422 /// `BadVAddr = 0`.
423 #[test]
424 fn erl_makes_kuseg_unmapped_and_uncached() {
425 for v in [0x0000_0000u64, 0x1000, 0x0040_0000, 0x7FFF_FFFF] {
426 assert_eq!(
427 segment(v, Access::kernel()),
428 Segment::Mapped,
429 "{v:#X} is TLB-mapped with ERL clear"
430 );
431 assert_eq!(
432 segment(
433 v,
434 Access {
435 erl: true,
436 ..Access::kernel()
437 }
438 ),
439 Segment::Direct {
440 addr: v as u32,
441 cached: Cached::No
442 },
443 "{v:#X} is identity-mapped and uncached with ERL set"
444 );
445 }
446 }
447
448 /// The `ERL` area is **2 GB**, so the check is on the full 64-bit address.
449 /// Comparing the truncated low half would direct-map a 64-bit address whose
450 /// low 32 bits happen to fall below `0xFFFF_FFFF_8000_0000`.
451 #[test]
452 fn the_erl_user_area_is_two_gigabytes_not_a_truncated_comparison() {
453 // Genuinely inside: a 32-bit KUSEG address is zero-extended.
454 assert_eq!(
455 segment(
456 0x0000_1000,
457 Access {
458 erl: true,
459 ..Access::kernel()
460 }
461 ),
462 Segment::Direct {
463 addr: 0x1000,
464 cached: Cached::No
465 }
466 );
467 // Outside the 2 GB area, but with low 32 bits that look inside.
468 for v in [0x0000_0001_0000_1000u64, 0x0000_00FF_0000_1000] {
469 assert_ne!(
470 segment(
471 v,
472 Access {
473 erl: true,
474 ..Access::kernel()
475 }
476 ),
477 Segment::Direct {
478 addr: 0x1000,
479 cached: Cached::No
480 },
481 "{v:#X} is beyond the 2 GB user area and must not be direct-mapped"
482 );
483 }
484 }
485
486 /// A helper for the matrix below: `Access` for a mode at a given width.
487 fn acc(mode: Mode, wide: bool) -> Access {
488 Access {
489 mode,
490 wide,
491 erl: false,
492 }
493 }
494
495 /// The privilege/segment matrix, mirroring n64-systemtest's own
496 /// `Privilege: memory accesses` cases.
497 ///
498 /// The rows that matter most are the ones where the SAME address resolves
499 /// differently by mode: `0xFFFF_FFFF_8000_1000` is KSEG0 for the kernel and
500 /// an address error for everyone else. A map that ignores the mode passes
501 /// every kernel row and fails exactly these.
502 const SEGMENT_MATRIX: &[(&str, u64, Mode, bool, Segment)] = {
503 use Mode::{Kernel, Supervisor, User};
504 &[
505 // Kernel, 32-bit.
506 (
507 "k32 kuseg",
508 0x0000_0000_0000_1000,
509 Kernel,
510 false,
511 Segment::Mapped,
512 ),
513 (
514 "k32 kseg0",
515 0xFFFF_FFFF_8000_1000,
516 Kernel,
517 false,
518 Segment::Direct {
519 addr: 0x1000,
520 cached: Cached::Yes,
521 },
522 ),
523 (
524 "k32 kseg1",
525 0xFFFF_FFFF_A000_1000,
526 Kernel,
527 false,
528 Segment::Direct {
529 addr: 0x1000,
530 cached: Cached::No,
531 },
532 ),
533 (
534 "k32 ksseg",
535 0xFFFF_FFFF_C000_1000,
536 Kernel,
537 false,
538 Segment::Mapped,
539 ),
540 (
541 "k32 kseg3",
542 0xFFFF_FFFF_E000_1000,
543 Kernel,
544 false,
545 Segment::Mapped,
546 ),
547 // The same KSEG0 address, one privilege level down.
548 (
549 "s32 low unused",
550 0xFFFF_FFFF_9000_1000,
551 Supervisor,
552 false,
553 Segment::Invalid,
554 ),
555 (
556 "s32 suseg",
557 0x0000_0000_0000_1000,
558 Supervisor,
559 false,
560 Segment::Mapped,
561 ),
562 (
563 "s32 sseg",
564 0xFFFF_FFFF_C000_1000,
565 Supervisor,
566 false,
567 Segment::Mapped,
568 ),
569 (
570 "s32 top unused",
571 0xFFFF_FFFF_E000_1000,
572 Supervisor,
573 false,
574 Segment::Invalid,
575 ),
576 (
577 "u32 useg",
578 0x0000_0000_0000_1000,
579 User,
580 false,
581 Segment::Mapped,
582 ),
583 (
584 "u32 unused",
585 0xFFFF_FFFF_9000_1000,
586 User,
587 false,
588 Segment::Invalid,
589 ),
590 // 64-bit addressing: each mapped segment widens to 2^40, and the
591 // hole above it faults.
592 (
593 "k64 xkuseg",
594 0x0000_0000_0000_1000,
595 Kernel,
596 true,
597 Segment::Mapped,
598 ),
599 (
600 "k64 xkuseg gap",
601 0x0000_0100_0000_0000,
602 Kernel,
603 true,
604 Segment::Invalid,
605 ),
606 (
607 "k64 xksseg",
608 0x4000_0000_0000_1000,
609 Kernel,
610 true,
611 Segment::Mapped,
612 ),
613 (
614 "k64 xksseg gap",
615 0x4000_0100_0000_0000,
616 Kernel,
617 true,
618 Segment::Invalid,
619 ),
620 (
621 "k64 xkseg",
622 0xC000_0000_0000_1000,
623 Kernel,
624 true,
625 Segment::Mapped,
626 ),
627 (
628 "k64 xkseg gap",
629 0xC000_0100_0000_0000,
630 Kernel,
631 true,
632 Segment::Invalid,
633 ),
634 (
635 "k64 ckseg0",
636 0xFFFF_FFFF_8000_1000,
637 Kernel,
638 true,
639 Segment::Direct {
640 addr: 0x1000,
641 cached: Cached::Yes,
642 },
643 ),
644 (
645 "k64 ckseg1",
646 0xFFFF_FFFF_A000_1000,
647 Kernel,
648 true,
649 Segment::Direct {
650 addr: 0x1000,
651 cached: Cached::No,
652 },
653 ),
654 (
655 "s64 xsuseg",
656 0x0000_0000_0000_1000,
657 Supervisor,
658 true,
659 Segment::Mapped,
660 ),
661 (
662 "s64 xsuseg gap",
663 0x0000_0100_0000_0000,
664 Supervisor,
665 true,
666 Segment::Invalid,
667 ),
668 (
669 "s64 xsseg",
670 0x4000_0000_0000_1000,
671 Supervisor,
672 true,
673 Segment::Mapped,
674 ),
675 (
676 "s64 xsseg gap",
677 0x4000_0100_0000_0000,
678 Supervisor,
679 true,
680 Segment::Invalid,
681 ),
682 (
683 "s64 csseg",
684 0xFFFF_FFFF_C000_1000,
685 Supervisor,
686 true,
687 Segment::Mapped,
688 ),
689 (
690 "s64 top unused",
691 0xFFFF_FFFF_E000_1000,
692 Supervisor,
693 true,
694 Segment::Invalid,
695 ),
696 (
697 "u64 xuseg",
698 0x0000_0000_0000_1000,
699 User,
700 true,
701 Segment::Mapped,
702 ),
703 (
704 "u64 xuseg gap",
705 0x0000_0100_0000_0000,
706 User,
707 true,
708 Segment::Invalid,
709 ),
710 ]
711 };
712
713 #[test]
714 fn the_segment_map_depends_on_the_privilege_mode() {
715 for (name, vaddr, mode, wide, want) in SEGMENT_MATRIX {
716 assert_eq!(segment(*vaddr, acc(*mode, *wide)), *want, "{name}");
717 }
718 }
719
720 /// All eight XKPHYS windows are direct, and only `C == 2` is uncached — the
721 /// same rule a TLB entry's `C` field follows (UM Table 5-6).
722 #[test]
723 fn xkphys_is_eight_direct_windows_differing_only_in_cacheability() {
724 for c in 0u64..8 {
725 let v = (0b10 << 62) | (c << 59) | 0x1000;
726 assert_eq!(
727 segment(v, acc(Mode::Kernel, true)),
728 Segment::Direct {
729 addr: 0x1000,
730 cached: if c == 2 { Cached::No } else { Cached::Yes },
731 },
732 "XKPHYS window {c}"
733 );
734 // Bits 58:32 must be zero: a window is 2^32 wide, not 2^59.
735 assert_eq!(
736 segment(v | (1 << 32), acc(Mode::Kernel, true)),
737 Segment::Invalid,
738 "XKPHYS window {c} past 2^32"
739 );
740 }
741 }
742
743 /// Under 32-bit addressing an address must be the sign extension of its low
744 /// word. `0x0000_0000_8000_1000` is **not** a shorthand for KSEG0.
745 ///
746 /// n64-systemtest asserts this directly ("LW with address not sign
747 /// extended"), and truncating to `u32` instead — the natural shortcut, and
748 /// what this module did until T-11-003 — accepts it silently.
749 #[test]
750 fn a_non_sign_extended_address_is_invalid_in_32_bit_mode() {
751 assert_eq!(
752 segment(0x0000_0000_8000_1000, acc(Mode::Kernel, false)),
753 Segment::Invalid
754 );
755 assert_eq!(
756 segment(0xFFFF_FFFF_8000_1000, acc(Mode::Kernel, false)),
757 Segment::Direct {
758 addr: 0x1000,
759 cached: Cached::Yes
760 },
761 "the sign-extended form is the same address, and is valid"
762 );
763 }
764
765 /// `ERL` affects **only** the user area. The kernel segments keep their own
766 /// rules, and the mapped kernel segments stay mapped — a blanket
767 /// `if erl { Direct }` would silently unmap KSSEG and KSEG3 too.
768 #[test]
769 fn erl_does_not_change_the_kernel_segments() {
770 // Unmapped kernel segments are unaffected in both directions.
771 assert_eq!(
772 segment(
773 0xFFFF_FFFF_8000_1000,
774 Access {
775 erl: true,
776 ..Access::kernel()
777 }
778 ),
779 segment(0xFFFF_FFFF_8000_1000, Access::kernel()),
780 "KSEG0"
781 );
782 assert_eq!(
783 segment(
784 0xFFFF_FFFF_A400_0000,
785 Access {
786 erl: true,
787 ..Access::kernel()
788 }
789 ),
790 segment(0xFFFF_FFFF_A400_0000, Access::kernel()),
791 "KSEG1"
792 );
793 // Mapped kernel segments stay MAPPED even with ERL set.
794 for v in [0xFFFF_FFFF_C000_0000u64, 0xFFFF_FFFF_E000_0000] {
795 assert_eq!(
796 segment(
797 v,
798 Access {
799 erl: true,
800 ..Access::kernel()
801 }
802 ),
803 Segment::Mapped,
804 "{v:#X} must stay TLB-mapped -- ERL covers the user area only"
805 );
806 }
807 }
808 /// **A sign-extended KSEG0 address stays Direct with `KX = 1`.**
809 ///
810 /// Kept from an R-18 investigation that this test **refuted**: a stalled
811 /// retail title appeared to take `AdES` storing to `0xFFFF_FFFF_8028_4C78`
812 /// in wide kernel mode, which would have meant the segment map mishandled
813 /// KSEG0 under 64-bit addressing. It does not — the address is `Direct`
814 /// either way, and the apparent `AdES` came from an *uncorrelated* reading
815 /// of `Cause`/`EPC` sampled long after the fact.
816 ///
817 /// Retained because the invariant is real and previously untested: KSEG0 is
818 /// direct-mapped regardless of `KX`, and a regression that made 64-bit
819 /// addressing route it through the TLB would be caught here rather than as
820 /// an unexplained boot hang.
821 #[test]
822 fn r18_kseg0_is_direct_in_wide_kernel_mode() {
823 let acc = Access {
824 mode: Mode::Kernel,
825 wide: true,
826 erl: false,
827 };
828 let v = 0xFFFF_FFFF_8028_4C78u64;
829 assert!(
830 matches!(segment(v, acc), Segment::Direct { .. }),
831 "KSEG0 must stay direct with KX=1, got {:?}",
832 segment(v, acc)
833 );
834 // ... and with KX clear, for contrast.
835 let narrow = Access {
836 mode: Mode::Kernel,
837 wide: false,
838 erl: false,
839 };
840 assert!(
841 matches!(segment(v, narrow), Segment::Direct { .. }),
842 "KSEG0 must also stay direct with KX=0, got {:?}",
843 segment(v, narrow)
844 );
845 }
846}