Skip to main content

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}