summaryrefslogtreecommitdiff
path: root/lume-legacy/src/kernel/fdt.cc
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
commite90b72ec3082588b296956730bd2930cf8d5d671 (patch)
tree36ff6916af9b4176ea5a9662219d11e721f0b691 /lume-legacy/src/kernel/fdt.cc
parent1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff)
downloadlume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz
lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz
refactor: promote new architecture to repository root
Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior.
Diffstat (limited to 'lume-legacy/src/kernel/fdt.cc')
-rw-r--r--lume-legacy/src/kernel/fdt.cc273
1 files changed, 273 insertions, 0 deletions
diff --git a/lume-legacy/src/kernel/fdt.cc b/lume-legacy/src/kernel/fdt.cc
new file mode 100644
index 0000000..ae09c84
--- /dev/null
+++ b/lume-legacy/src/kernel/fdt.cc
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard Qin
+ * LumeOS - FDT Parser
+ *
+ * Parses the Flattened Device Tree to discover hardware.
+ * Populates g_devices for use by all drivers.
+ */
+#include "common/types.h"
+#include "kernel/fdt.h"
+#include "lib/string.h"
+
+// Global device registry
+DeviceRegistry g_devices;
+
+// Keep g_dtb_addr for PMM to protect DTB pages
+// g_dtb_addr is defined in entry.S, g_uart_base in config.cc
+extern "C" uint64 g_dtb_addr;
+extern uint64 g_uart_base;
+
+static inline uint32 bswap(uint32 x)
+{
+ return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) |
+ ((x & 0xFF0000) >> 8) | ((x >> 24) & 0xFF);
+}
+
+struct fdt_header
+{
+ uint32 magic;
+ uint32 totalsize;
+ uint32 off_dt_struct;
+ uint32 off_dt_strings;
+ uint32 off_mem_rsvmap;
+ uint32 version;
+ uint32 last_comp_version;
+ uint32 boot_cpuid_phys;
+ uint32 size_dt_strings;
+ uint32 size_dt_struct;
+};
+
+#define FDT_MAGIC 0xd00dfeed
+#define FDT_BEGIN_NODE 1
+#define FDT_END_NODE 2
+#define FDT_PROP 3
+#define FDT_NOP 4
+#define FDT_END 9
+
+// Check if 'str' starts with 'prefix'
+static bool starts_with(const char *str, const char *prefix)
+{
+ while (*prefix)
+ {
+ if (*str != *prefix)
+ return false;
+ str++;
+ prefix++;
+ }
+ return true;
+}
+
+// Check if 'haystack' contains 'needle' as a substring within 'len' bytes
+// (compatible strings may have multiple null-separated entries)
+static bool compat_match(const uint8 *data, uint32 len, const char *needle)
+{
+ uint32 nlen = strlen(needle);
+ for (uint32 i = 0; i + nlen <= len; i++)
+ {
+ if (memcmp(data + i, needle, nlen) == 0)
+ return true;
+ }
+ return false;
+}
+
+// Add a MMIO region to the registry (avoids duplicates)
+static void add_mmio_region(uint64 base, uint64 size)
+{
+ if (base == 0 || size == 0)
+ return;
+ // Only track I/O regions below RAM
+ if (base >= 0x80000000ULL)
+ return;
+ if (g_devices.mmio_count >= DeviceRegistry::MAX_MMIO)
+ return;
+
+ // Check for overlap/duplicate
+ for (int i = 0; i < g_devices.mmio_count; i++)
+ {
+ if (g_devices.mmio_regions[i].base == base)
+ return;
+ }
+
+ g_devices.mmio_regions[g_devices.mmio_count].base = base;
+ g_devices.mmio_regions[g_devices.mmio_count].size = size;
+ g_devices.mmio_count++;
+}
+
+void fdt_parse(uint64 dtb)
+{
+ // Clear registry
+ memset(&g_devices, 0, sizeof(g_devices));
+
+ if (dtb == 0)
+ return;
+
+ g_dtb_addr = dtb;
+
+ struct fdt_header *header = (struct fdt_header *)dtb;
+ if (bswap(header->magic) != FDT_MAGIC)
+ return;
+
+ uint8 *struct_ptr = (uint8 *)(dtb + bswap(header->off_dt_struct));
+ char *strings_ptr = (char *)(dtb + bswap(header->off_dt_strings));
+
+ // Parsing state
+ char current_node_name[64];
+ current_node_name[0] = '\0';
+
+ // Per-node state
+ uint64 current_reg_addr = 0;
+ uint64 current_reg_size = 0;
+ uint32 current_irq = 0;
+ const uint8 *current_compat = nullptr;
+ uint32 current_compat_len = 0;
+ bool has_reg = false;
+ bool has_compat = false;
+ // bool has_irq = false; // Unused
+
+ // Depth tracking (for #address-cells / #size-cells)
+ // Default for root: 2 address-cells, 2 size-cells
+ int depth = 0;
+
+ while (1)
+ {
+ uint32 token = bswap(*(uint32 *)struct_ptr);
+ struct_ptr += 4;
+
+ if (token == FDT_END)
+ break;
+ if (token == FDT_NOP)
+ continue;
+
+ if (token == FDT_BEGIN_NODE)
+ {
+ // Save node name
+ // char *name = (char *)struct_ptr;
+ int i = 0;
+ while (*struct_ptr != 0 && i < 63)
+ {
+ current_node_name[i++] = *struct_ptr;
+ struct_ptr++;
+ }
+ current_node_name[i] = '\0';
+ while (*struct_ptr != 0)
+ struct_ptr++;
+ struct_ptr++;
+ struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3);
+
+ // Reset per-node state
+ current_reg_addr = 0;
+ current_reg_size = 0;
+ current_irq = 0;
+ current_compat = nullptr;
+ current_compat_len = 0;
+ has_reg = false;
+ has_compat = false;
+ // has_irq = false;
+
+ depth++;
+ }
+ else if (token == FDT_END_NODE)
+ {
+ // Process the node we just finished
+ if (has_reg && has_compat)
+ {
+ // UART: ns16550a
+ if (compat_match(current_compat, current_compat_len, "ns16550"))
+ {
+ g_devices.uart.base_addr = current_reg_addr;
+ g_devices.uart.size = current_reg_size ? current_reg_size : 0x100;
+ g_devices.uart.irq = current_irq;
+ add_mmio_region(current_reg_addr, g_devices.uart.size);
+ }
+ // PLIC
+ else if (compat_match(current_compat, current_compat_len, "plic"))
+ {
+ g_devices.plic.base_addr = current_reg_addr;
+ g_devices.plic.size = current_reg_size ? current_reg_size : 0x4000000;
+ g_devices.plic.irq = 0;
+ add_mmio_region(current_reg_addr, g_devices.plic.size);
+ }
+ // CLINT
+ else if (compat_match(current_compat, current_compat_len, "clint"))
+ {
+ g_devices.clint.base_addr = current_reg_addr;
+ g_devices.clint.size = current_reg_size ? current_reg_size : 0x10000;
+ g_devices.clint.irq = 0;
+ add_mmio_region(current_reg_addr, g_devices.clint.size);
+ }
+ // VirtIO
+ else if (compat_match(current_compat, current_compat_len, "virtio"))
+ {
+ if (g_devices.virtio_count < DeviceRegistry::MAX_VIRTIO)
+ {
+ int idx = g_devices.virtio_count++;
+ g_devices.virtio[idx].base_addr = current_reg_addr;
+ g_devices.virtio[idx].size = current_reg_size ? current_reg_size : 0x1000;
+ g_devices.virtio[idx].irq = current_irq;
+ add_mmio_region(current_reg_addr, g_devices.virtio[idx].size);
+ }
+ }
+ }
+
+ // Memory node: name starts with "memory"
+ if (has_reg && starts_with(current_node_name, "memory"))
+ {
+ g_devices.mem_base = current_reg_addr;
+ g_devices.mem_size = current_reg_size;
+ }
+
+ depth--;
+ }
+ else if (token == FDT_PROP)
+ {
+ uint32 len = bswap(*(uint32 *)struct_ptr);
+ struct_ptr += 4;
+ uint32 nameoff = bswap(*(uint32 *)struct_ptr);
+ struct_ptr += 4;
+
+ char *name = strings_ptr + nameoff;
+ uint8 *data = struct_ptr;
+
+ struct_ptr += len;
+ struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3);
+
+ if (strcmp(name, "reg") == 0 && len >= 8)
+ {
+ // Assume 2 address-cells, 2 size-cells (standard for QEMU virt)
+ uint32 hi = bswap(*(uint32 *)data);
+ uint32 lo = bswap(*(uint32 *)(data + 4));
+ current_reg_addr = ((uint64)hi << 32) | lo;
+
+ if (len >= 16)
+ {
+ uint32 shi = bswap(*(uint32 *)(data + 8));
+ uint32 slo = bswap(*(uint32 *)(data + 12));
+ current_reg_size = ((uint64)shi << 32) | slo;
+ }
+ has_reg = true;
+ }
+ else if (strcmp(name, "compatible") == 0)
+ {
+ current_compat = data;
+ current_compat_len = len;
+ has_compat = true;
+ }
+ else if (strcmp(name, "interrupts") == 0 && len >= 4)
+ {
+ current_irq = bswap(*(uint32 *)data);
+ // has_irq = true;
+ }
+ else if (strcmp(name, "interrupts-extended") == 0 && len >= 8)
+ {
+ // interrupts-extended = <&phandle irq_num>
+ // IRQ number is typically the second cell
+ current_irq = bswap(*(uint32 *)(data + 4));
+ // has_irq = true;
+ }
+ }
+ }
+
+ // Legacy compat
+ g_uart_base = g_devices.uart.base_addr;
+} \ No newline at end of file