Skip to main content

strat9_kernel/boot/
fdt.rs

1//! Device Tree (FDT) parsing for the custom boot protocol.
2//!
3//! Parses the Flattened Device Tree to extract boot information:
4//! - Memory map
5//! - Framebuffer
6//! - ACPI RSDP (via EFI System Table)
7//! - Boot command line
8//! - HHDM offset (derived from memory map)
9//!
10//! # FDT format
11//!
12//! The DTB is a binary blob with big-endian fields. Structure tokens:
13//! - 0x1 = FDT_BEGIN_NODE
14//! - 0x2 = FDT_END_NODE
15//! - 0x3 = FDT_PROP
16//! - 0x4 = FDT_NOP
17//! - 0x9 = FDT_END
18//!
19//! # Cell sizes
20//!
21//! The root node defines `#address-cells` and `#size-cells` which determine
22//! how many 32-bit cells are used for addresses and sizes in `reg` properties.
23//! Default on most platforms: #address-cells=2, #size-cells=2 (8 bytes each).
24
25use crate::boot::entry::{KernelArgs, MemoryKind, MemoryRegion};
26
27/// Maximum number of memory regions supported from DTB
28const MAX_DTB_MEMORY_REGIONS: usize = 128;
29
30/// Maximum string length for FDT strings
31const MAX_CSTRING_LEN: usize = 512;
32
33/// Default HHDM offset for x86_64 (4 GiB). Used when DTB doesn't specify one.
34const DEFAULT_HHDM_OFFSET: u64 = 0x1_0000_0000;
35
36/// Static storage for memory map parsed from DTB.
37///
38/// Lives for the entire kernel lifetime. The slice is handed out via
39/// [`KernelArgs::memory_regions`].
40static mut DTB_MEMORY_MAP: [MemoryRegion; MAX_DTB_MEMORY_REGIONS] = [MemoryRegion {
41    base: 0,
42    size: 0,
43    kind: MemoryKind::Reserved,
44}; MAX_DTB_MEMORY_REGIONS];
45
46/// Parse the Flattened Device Tree and build a KernelArgs structure.
47///
48/// # Safety
49///
50/// `dtb_ptr` must point to a valid flattened device tree in memory.
51#[cfg_attr(not(test), allow(static_mut_refs))]
52pub unsafe fn build_kernel_args_from_dtb(dtb_ptr: u64) -> KernelArgs {
53    let mut args = KernelArgs {
54        magic: strat9_abi::boot::STRAT9_BOOT_MAGIC,
55        abi_version: strat9_abi::boot::STRAT9_BOOT_ABI_VERSION,
56        kernel_base: 0,
57        kernel_size: 0,
58        acpi_rsdp_base: 0,
59        memory_map_base: 0,
60        memory_map_size: 0,
61        framebuffer_addr: 0,
62        framebuffer_width: 0,
63        framebuffer_height: 0,
64        framebuffer_stride: 0,
65        framebuffer_bpp: 0,
66        framebuffer_red_mask_size: 0,
67        framebuffer_red_mask_shift: 0,
68        framebuffer_green_mask_size: 0,
69        framebuffer_green_mask_shift: 0,
70        framebuffer_blue_mask_size: 0,
71        framebuffer_blue_mask_shift: 0,
72        hhdm_offset: 0,
73        cmdline_ptr: 0,
74        cmdline_len: 0,
75        modules_base: 0,
76        modules_size: 0,
77        bss_virt_base: 0,
78        bss_virt_size: 0,
79    };
80
81    if dtb_ptr == 0 {
82        crate::serial_println!("[fdt] ERROR: null DTB pointer");
83        return args;
84    }
85
86    let fdt = match parse_fdt_header(dtb_ptr) {
87        Ok(fdt) => fdt,
88        Err(e) => {
89            crate::serial_println!("[fdt] ERROR: {}", e);
90            return args;
91        }
92    };
93
94    crate::serial_println!("[fdt] DTB at {:#x}, version {}", dtb_ptr, fdt.version);
95
96    // Parse root node to get #address-cells and #size-cells
97    let (addr_cells, size_cells) = parse_root_properties(&fdt);
98    crate::serial_println!(
99        "[fdt] Root: #address-cells={} #size-cells={}",
100        addr_cells,
101        size_cells
102    );
103
104    // Parse memory nodes => memory map
105    let regions = parse_memory_nodes(&fdt, addr_cells, size_cells);
106    let count = regions.len();
107    args.memory_map_base = regions.as_ptr() as u64;
108    args.memory_map_size = (count * core::mem::size_of::<MemoryRegion>()) as u64;
109
110    // Derive HHDM offset from memory map
111    args.hhdm_offset = derive_hhdm_offset(regions);
112
113    // Parse /chosen node (bootargs, initrd, stdout-path, uefi-systab)
114    let mut efi_systab_addr = 0u64;
115    parse_chosen_node(&fdt, &mut args, &mut efi_systab_addr);
116
117    // Parse framebuffer (from /chosen or framebuffer node)
118    parse_framebuffer(&fdt, &mut args, addr_cells);
119
120    // Resolve RSDP from EFI System Table if available
121    if efi_systab_addr != 0 && args.acpi_rsdp_base == 0 {
122        args.acpi_rsdp_base = find_rsdp_from_efi_systab(efi_systab_addr);
123    }
124
125    args
126}
127
128// ============================================================================
129// FDT header
130// ============================================================================
131
132struct FdtHeader {
133    off_dt_struct: u32,
134    off_dt_strings: u32,
135    version: u32,
136    size_dt_struct: u32,
137    base_ptr: u64,
138}
139
140unsafe fn parse_fdt_header(dtb_ptr: u64) -> Result<FdtHeader, &'static str> {
141    let ptr = dtb_ptr as *const u8;
142
143    if read_be32(ptr) != 0xd00dfeed {
144        return Err("invalid FDT magic number");
145    }
146
147    Ok(FdtHeader {
148        off_dt_struct: read_be32(ptr.add(8)),
149        off_dt_strings: read_be32(ptr.add(12)),
150        version: read_be32(ptr.add(20)),
151        size_dt_struct: read_be32(ptr.add(32)),
152        base_ptr: dtb_ptr,
153    })
154}
155
156// ============================================================================
157// Root properties
158// ============================================================================
159
160/// Parse root node properties to extract #address-cells and #size-cells.
161unsafe fn parse_root_properties(fdt: &FdtHeader) -> (u32, u32) {
162    let struct_base = fdt.base_ptr + fdt.off_dt_struct as u64;
163    let strings_base = fdt.base_ptr + fdt.off_dt_strings as u64;
164    let struct_end = struct_base + fdt.size_dt_struct as u64;
165
166    let mut pos = struct_base;
167    let mut addr_cells = 2u32;
168    let mut size_cells = 2u32;
169    let mut in_root = false;
170
171    while pos < struct_end {
172        let token = read_be32(pos as *const u8);
173        pos += 4;
174
175        match token {
176            0x1 => {
177                // FDT_BEGIN_NODE
178                let name = read_cstring(pos as *const u8);
179                let name_len = name.len() + 1;
180                pos += (name_len as u64 + 3) & !3;
181
182                if name.is_empty() {
183                    in_root = true;
184                } else {
185                    break;
186                }
187            }
188            0x2 if in_root => break,
189            0x3 => {
190                // FDT_PROP
191                let prop_len = read_be32(pos as *const u8) as u64;
192                pos += 4;
193                let prop_nameoff = read_be32(pos as *const u8) as u64;
194                pos += 4;
195                let prop_ptr = pos as *const u8;
196                pos += (prop_len + 3) & !3;
197
198                if !in_root {
199                    continue;
200                }
201
202                let prop_name = read_cstring((strings_base + prop_nameoff) as *const u8);
203                match prop_name.as_bytes() {
204                    b"#address-cells" if prop_len >= 4 => addr_cells = read_be32(prop_ptr),
205                    b"#size-cells" if prop_len >= 4 => size_cells = read_be32(prop_ptr),
206                    _ => {}
207                }
208            }
209            0x4 => {} // FDT_NOP
210            0x9 => break,
211            _ => break,
212        }
213    }
214
215    (addr_cells, size_cells)
216}
217
218// ============================================================================
219// Memory map
220// ============================================================================
221
222/// Parse /memory nodes to build the memory map.
223///
224/// Returns a slice into the static [`DTB_MEMORY_MAP`].
225#[allow(static_mut_refs)]
226unsafe fn parse_memory_nodes(
227    fdt: &FdtHeader,
228    addr_cells: u32,
229    size_cells: u32,
230) -> &'static [MemoryRegion] {
231    let struct_base = fdt.base_ptr + fdt.off_dt_struct as u64;
232    let strings_base = fdt.base_ptr + fdt.off_dt_strings as u64;
233    let struct_end = struct_base + fdt.size_dt_struct as u64;
234
235    let mut pos = struct_base;
236    let mut count = 0usize;
237    let mut depth = 0u32;
238    let mut in_memory_node = false;
239
240    crate::serial_println!("[fdt] Scanning for /memory nodes...");
241
242    while pos < struct_end && count < MAX_DTB_MEMORY_REGIONS {
243        let token = read_be32(pos as *const u8);
244        pos += 4;
245
246        match token {
247            0x1 => {
248                // FDT_BEGIN_NODE
249                depth += 1;
250                let name = read_cstring(pos as *const u8);
251                let name_len = (name.len() + 1) as u64;
252                pos += (name_len + 3) & !3;
253
254                if depth == 1 && name.starts_with("memory") {
255                    in_memory_node = true;
256                    crate::serial_println!("[fdt] Found node: {}", name);
257                }
258            }
259            0x2 => {
260                if in_memory_node && depth == 1 {
261                    in_memory_node = false;
262                }
263                depth = depth.saturating_sub(1);
264            }
265            0x3 => {
266                // FDT_PROP
267                let prop_len = read_be32(pos as *const u8) as u64;
268                pos += 4;
269                let prop_nameoff = read_be32(pos as *const u8) as u64;
270                pos += 4;
271                let prop_ptr = pos as *const u8;
272                pos += (prop_len + 3) & !3;
273
274                if in_memory_node {
275                    let prop_name = read_cstring((strings_base + prop_nameoff) as *const u8);
276                    if prop_name == "reg" {
277                        let (base, size) =
278                            read_reg_property(prop_ptr, prop_len, addr_cells, size_cells);
279                        if base != 0 && size != 0 {
280                            DTB_MEMORY_MAP[count] = MemoryRegion {
281                                base,
282                                size,
283                                kind: MemoryKind::Free,
284                            };
285                            crate::serial_println!(
286                                "[fdt] Memory region: base={:#x} size={:#x}",
287                                base,
288                                size
289                            );
290                            count += 1;
291                        }
292                    }
293                }
294            }
295            0x4 => {}
296            0x9 => break,
297            _ => {
298                crate::serial_println!("[fdt] Unknown token: {:#x}", token);
299                break;
300            }
301        }
302    }
303
304    crate::serial_println!("[fdt] Found {} memory regions", count);
305
306    // SAFETY: DTB_MEMORY_MAP is written above and never mutated after this point.
307    unsafe { core::slice::from_raw_parts(DTB_MEMORY_MAP.as_ptr(), count) }
308}
309
310/// Read a `reg` property value using the correct cell sizes.
311unsafe fn read_reg_property(
312    ptr: *const u8,
313    len: u64,
314    addr_cells: u32,
315    size_cells: u32,
316) -> (u64, u64) {
317    let addr_bytes = (addr_cells * 4) as usize;
318    let size_bytes = (size_cells * 4) as usize;
319    let total = addr_bytes + size_bytes;
320
321    if (len as usize) < total {
322        return (0, 0);
323    }
324
325    let mut base = 0u64;
326    for i in 0..addr_bytes {
327        base = (base << 8) | *ptr.add(i) as u64;
328    }
329
330    let mut size = 0u64;
331    for i in addr_bytes..total {
332        size = (size << 8) | *ptr.add(i) as u64;
333    }
334
335    (base, size)
336}
337
338// ============================================================================
339// HHDM offset
340// ============================================================================
341
342/// Derive HHDM offset from the memory map.
343///
344/// On x86_64 with the custom bootloader, the HHDM is typically at 4 GiB (0x1_0000_0000).
345fn derive_hhdm_offset(regions: &[MemoryRegion]) -> u64 {
346    let max_addr = regions
347        .iter()
348        .filter(|r| matches!(r.kind, MemoryKind::Free))
349        .map(|r| r.base + r.size)
350        .max()
351        .unwrap_or(0);
352
353    crate::serial_println!(
354        "[fdt] Memory map: max_addr={:#x}, default HHDM={:#x}",
355        max_addr,
356        DEFAULT_HHDM_OFFSET
357    );
358
359    DEFAULT_HHDM_OFFSET
360}
361
362// ============================================================================
363// /chosen node
364// ============================================================================
365
366/// Parse /chosen node for bootargs, initrd, stdout-path, uefi-systab.
367unsafe fn parse_chosen_node(fdt: &FdtHeader, args: &mut KernelArgs, efi_systab_addr: &mut u64) {
368    let struct_base = fdt.base_ptr + fdt.off_dt_struct as u64;
369    let strings_base = fdt.base_ptr + fdt.off_dt_strings as u64;
370    let struct_end = struct_base + fdt.size_dt_struct as u64;
371
372    let mut pos = struct_base;
373    let mut depth = 0u32;
374    let mut in_chosen = false;
375
376    crate::serial_println!("[fdt] Scanning for /chosen node...");
377
378    while pos < struct_end {
379        let token = read_be32(pos as *const u8);
380        pos += 4;
381
382        match token {
383            0x1 => {
384                depth += 1;
385                let name = read_cstring(pos as *const u8);
386                let name_len = name.len() + 1;
387                pos += (name_len as u64 + 3) & !3;
388
389                if depth == 1 && name == "chosen" {
390                    in_chosen = true;
391                    crate::serial_println!("[fdt] Found /chosen node");
392                }
393            }
394            0x2 => {
395                if in_chosen && depth == 1 {
396                    in_chosen = false;
397                }
398                depth = depth.saturating_sub(1);
399            }
400            0x3 => {
401                // FDT_PROP
402                let prop_len = read_be32(pos as *const u8) as u64;
403                pos += 4;
404                let prop_nameoff = read_be32(pos as *const u8) as u64;
405                pos += 4;
406                let prop_ptr = pos as *const u8;
407                pos += (prop_len + 3) & !3;
408
409                if !in_chosen {
410                    continue;
411                }
412
413                let prop_name = read_cstring((strings_base + prop_nameoff) as *const u8);
414
415                match prop_name.as_bytes() {
416                    b"bootargs" => {
417                        let bootargs = read_cstring(prop_ptr);
418                        crate::serial_println!("[fdt] bootargs: '{}'", bootargs);
419                        args.cmdline_ptr = prop_ptr as u64;
420                        args.cmdline_len = bootargs.len() as u64 + 1;
421                    }
422                    b"linux,initrd-start" => {
423                        let addr = read_prop_u64(prop_ptr, prop_len);
424                        crate::serial_println!(
425                            "[fdt] initrd-start: {:#x} (FDT boot, module table not yet supported)",
426                            addr
427                        );
428                        // NOTE: FDT boot doesn't use the module table yet.
429                        // The initrd is loaded but not registered as a module.
430                    }
431                    b"linux,initrd-end" => {
432                        let addr = read_prop_u64(prop_ptr, prop_len);
433                        crate::serial_println!("[fdt] initrd-end: {:#x}", addr);
434                    }
435                    b"stdout-path" => {
436                        crate::serial_println!("[fdt] stdout-path: {}", read_cstring(prop_ptr));
437                    }
438                    b"uefi-systab" => {
439                        // EFI System Table address, NOT RSDP.
440                        let addr = read_prop_u64(prop_ptr, prop_len);
441                        crate::serial_println!("[fdt] uefi-systab (EFI System Table): {:#x}", addr);
442                        *efi_systab_addr = addr;
443                    }
444                    _ => {}
445                }
446            }
447            0x4 => {}
448            0x9 => break,
449            _ => break,
450        }
451    }
452}
453
454// ============================================================================
455// Framebuffer
456// ============================================================================
457
458/// Parse framebuffer information from DTB.
459unsafe fn parse_framebuffer(fdt: &FdtHeader, args: &mut KernelArgs, addr_cells: u32) {
460    let struct_base = fdt.base_ptr + fdt.off_dt_struct as u64;
461    let strings_base = fdt.base_ptr + fdt.off_dt_strings as u64;
462    let struct_end = struct_base + fdt.size_dt_struct as u64;
463
464    let mut pos = struct_base;
465    let mut depth = 0u32;
466    let mut in_framebuffer = false;
467
468    while pos < struct_end {
469        let token = read_be32(pos as *const u8);
470        pos += 4;
471
472        match token {
473            0x1 => {
474                depth += 1;
475                let name = read_cstring(pos as *const u8);
476                let name_len = name.len() + 1;
477                pos += (name_len as u64 + 3) & !3;
478
479                if name.contains("framebuffer") || name.contains("display") {
480                    in_framebuffer = true;
481                    crate::serial_println!("[fdt] Found framebuffer node: {}", name);
482                }
483            }
484            0x2 => {
485                if in_framebuffer {
486                    in_framebuffer = false;
487                }
488                depth = depth.saturating_sub(1);
489            }
490            0x3 => {
491                let prop_len = read_be32(pos as *const u8) as u64;
492                pos += 4;
493                let prop_nameoff = read_be32(pos as *const u8) as u64;
494                pos += 4;
495                let prop_ptr = pos as *const u8;
496                pos += (prop_len + 3) & !3;
497
498                if !in_framebuffer {
499                    continue;
500                }
501
502                let prop_name = read_cstring((strings_base + prop_nameoff) as *const u8);
503
504                match prop_name.as_bytes() {
505                    b"reg" => {
506                        let addr_bytes = (addr_cells * 4) as u64;
507                        if prop_len >= addr_bytes {
508                            args.framebuffer_addr = read_be_int(prop_ptr, addr_bytes as usize);
509                        }
510                    }
511                    b"width" if prop_len >= 4 => args.framebuffer_width = read_be32(prop_ptr),
512                    b"height" if prop_len >= 4 => args.framebuffer_height = read_be32(prop_ptr),
513                    b"stride" if prop_len >= 4 => args.framebuffer_stride = read_be32(prop_ptr),
514                    b"bpp" if prop_len >= 4 => args.framebuffer_bpp = read_be32(prop_ptr) as u16,
515                    _ => {}
516                }
517            }
518            0x4 => {}
519            0x9 => break,
520            _ => break,
521        }
522    }
523
524    // Default framebuffer format if not specified
525    if args.framebuffer_bpp == 0 && args.framebuffer_addr != 0 {
526        args.framebuffer_bpp = 32;
527        args.framebuffer_red_mask_size = 8;
528        args.framebuffer_red_mask_shift = 16;
529        args.framebuffer_green_mask_size = 8;
530        args.framebuffer_green_mask_shift = 8;
531        args.framebuffer_blue_mask_size = 8;
532        args.framebuffer_blue_mask_shift = 0;
533    }
534}
535
536// ============================================================================
537// RSDP via EFI System Table
538// ============================================================================
539
540/// Find RSDP from EFI System Table.
541///
542/// The EFI System Table contains a pointer to the EFI Configuration Table array.
543/// Each entry has a 128-bit GUID and a pointer. The RSDP GUID is
544/// `ac03114e-0409-47d4-a7c2-4596dd3ff5a1`.
545unsafe fn find_rsdp_from_efi_systab(efi_systab_addr: u64) -> u64 {
546    crate::serial_println!(
547        "[fdt] Searching for RSDP via EFI System Table at {:#x}",
548        efi_systab_addr
549    );
550
551    let systab = efi_systab_addr as *const u8;
552
553    // EFI System Table layout (UEFI Spec 2.10, §4.3.1):
554    //   +0x68: NumberOfTableEntries (uintn_t)
555    //   +0x70: ConfigurationTable (EFI_CONFIGURATION_TABLE*)
556    let num_entries = read_be64(systab.add(0x68));
557    let config_table_ptr = read_be64(systab.add(0x70));
558
559    crate::serial_println!(
560        "[fdt] EFI Config Table: {} entries at {:#x}",
561        num_entries,
562        config_table_ptr
563    );
564
565    if config_table_ptr == 0 || num_entries == 0 {
566        crate::serial_println!("[fdt] No EFI configuration tables found");
567        return 0;
568    }
569
570    // RSDP GUID: ac03114e-0409-47d4-a7c2-4596dd3ff5a1
571    let rsdp_guid: [u64; 2] = [0x47d4_0409_ac03_114e, 0xa1f5_d3dd_9645_c2a7];
572
573    // Each EFI_CONFIGURATION_TABLE is 24 bytes: GUID (16) + pointer (8)
574    let config_table = config_table_ptr as *const u8;
575
576    for i in 0..num_entries as usize {
577        let entry = config_table.add(i * 24);
578        let guid_lo = read_be64(entry);
579        let guid_hi = read_be64(entry.add(8));
580        let table_ptr = read_be64(entry.add(16));
581
582        if guid_lo == rsdp_guid[0] && guid_hi == rsdp_guid[1] && table_ptr != 0 {
583            crate::serial_println!("[fdt] RSDP found at {:#x}", table_ptr);
584            return table_ptr;
585        }
586    }
587
588    crate::serial_println!("[fdt] RSDP not found in EFI configuration tables");
589    0
590}
591
592// ============================================================================
593// Helpers
594// ============================================================================
595
596/// Read a big-endian 32-bit value.
597#[inline]
598unsafe fn read_be32(ptr: *const u8) -> u32 {
599    u32::from_be_bytes([
600        core::ptr::read_volatile(ptr),
601        core::ptr::read_volatile(ptr.add(1)),
602        core::ptr::read_volatile(ptr.add(2)),
603        core::ptr::read_volatile(ptr.add(3)),
604    ])
605}
606
607/// Read a big-endian 64-bit value.
608#[inline]
609unsafe fn read_be64(ptr: *const u8) -> u64 {
610    u64::from_be_bytes([
611        core::ptr::read_volatile(ptr),
612        core::ptr::read_volatile(ptr.add(1)),
613        core::ptr::read_volatile(ptr.add(2)),
614        core::ptr::read_volatile(ptr.add(3)),
615        core::ptr::read_volatile(ptr.add(4)),
616        core::ptr::read_volatile(ptr.add(5)),
617        core::ptr::read_volatile(ptr.add(6)),
618        core::ptr::read_volatile(ptr.add(7)),
619    ])
620}
621
622/// Read a big-endian integer of `num_bytes` bytes (1–8).
623unsafe fn read_be_int(ptr: *const u8, num_bytes: usize) -> u64 {
624    let mut val = 0u64;
625    for i in 0..num_bytes {
626        val = (val << 8) | *ptr.add(i) as u64;
627    }
628    val
629}
630
631/// Read a property value as u64 (32 or 64 bit depending on prop_len).
632unsafe fn read_prop_u64(ptr: *const u8, prop_len: u64) -> u64 {
633    if prop_len >= 8 {
634        read_be64(ptr)
635    } else {
636        read_be32(ptr) as u64
637    }
638}
639
640/// Read a null-terminated string from memory.
641unsafe fn read_cstring(ptr: *const u8) -> &'static str {
642    let mut len = 0;
643    while len < MAX_CSTRING_LEN && core::ptr::read_volatile(ptr.add(len)) != 0 {
644        len += 1;
645    }
646    let slice = core::slice::from_raw_parts(ptr, len);
647    core::str::from_utf8(slice).unwrap_or("")
648}