1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-10 03:37:35 +00:00

Kernel/riscv64: Take the memory map from the FDT and dump it

For this the BootInfo struct was made architecture specific
This commit is contained in:
Hendiadyoin1 2024-01-26 14:37:36 +01:00 committed by Andrew Kaster
parent 21a21c6a11
commit d3f6b03733
10 changed files with 368 additions and 119 deletions

View file

@ -5,6 +5,8 @@
*/
#include <AK/Assertions.h>
#include <AK/MemoryStream.h>
#include <AK/QuickSort.h>
#include <AK/StringView.h>
#include <Kernel/Arch/CPU.h>
#include <Kernel/Arch/PageDirectory.h>
@ -26,6 +28,7 @@
#include <Kernel/Sections.h>
#include <Kernel/Security/AddressSanitizer.h>
#include <Kernel/Tasks/Process.h>
#include <Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.h>
extern u8 start_of_kernel_image[];
extern u8 end_of_kernel_image[];
@ -249,7 +252,7 @@ bool MemoryManager::is_allowed_to_read_physical_memory_for_userspace(PhysicalAdd
UNMAP_AFTER_INIT void MemoryManager::parse_memory_map()
{
// Register used memory regions that we know of.
m_global_data.with([&](auto& global_data) {
m_global_data.with([this](auto& global_data) {
global_data.used_memory_ranges.ensure_capacity(4);
#if ARCH(X86_64)
// NOTE: We don't touch the first 1 MiB of RAM on x86-64 even if it's usable as indicated
@ -278,88 +281,40 @@ UNMAP_AFTER_INIT void MemoryManager::parse_memory_map()
#endif
global_data.used_memory_ranges.append(UsedMemoryRange { UsedMemoryRangeType::Kernel, PhysicalAddress(virtual_to_low_physical((FlatPtr)start_of_kernel_image)), PhysicalAddress(page_round_up(virtual_to_low_physical((FlatPtr)end_of_kernel_image)).release_value_but_fixme_should_propagate_errors()) });
if (multiboot_flags & 0x4) {
auto* bootmods_start = multiboot_copy_boot_modules_array;
auto* bootmods_end = bootmods_start + multiboot_copy_boot_modules_count;
for (auto* bootmod = bootmods_start; bootmod < bootmods_end; bootmod++) {
global_data.used_memory_ranges.append(UsedMemoryRange { UsedMemoryRangeType::BootModule, PhysicalAddress(bootmod->start), PhysicalAddress(bootmod->end) });
}
}
auto* mmap_begin = multiboot_memory_map;
auto* mmap_end = multiboot_memory_map + multiboot_memory_map_count;
#if ARCH(RISCV64)
// FIXME: AARCH64 might be able to make use of this code path
// Some x86 platforms also provide flattened device trees
parse_memory_map_fdt(global_data, s_fdt_storage);
#else
parse_memory_map_multiboot(global_data);
#endif
// Now we need to setup the physical regions we will use later
struct ContiguousPhysicalVirtualRange {
PhysicalAddress lower;
PhysicalAddress upper;
};
Optional<ContiguousPhysicalVirtualRange> last_contiguous_physical_range;
for (auto* mmap = mmap_begin; mmap < mmap_end; mmap++) {
// We have to copy these onto the stack, because we take a reference to these when printing them out,
// and doing so on a packed struct field is UB.
auto address = mmap->addr;
auto length = mmap->len;
ArmedScopeGuard write_back_guard = [&]() {
mmap->addr = address;
mmap->len = length;
};
dmesgln("MM: Multiboot mmap: address={:p}, length={}, type={}", address, length, mmap->type);
auto start_address = PhysicalAddress(address);
switch (mmap->type) {
case (MULTIBOOT_MEMORY_AVAILABLE):
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Usable, start_address, length });
break;
case (MULTIBOOT_MEMORY_RESERVED):
#if ARCH(X86_64)
// Workaround for https://gitlab.com/qemu-project/qemu/-/commit/8504f129450b909c88e199ca44facd35d38ba4de
// That commit added a reserved 12GiB entry for the benefit of virtual firmware.
// We can safely ignore this block as it isn't actually reserved on any real hardware.
// From: https://lore.kernel.org/all/20220701161014.3850-1-joao.m.martins@oracle.com/
// "Always add the HyperTransport range into e820 even when the relocation isn't
// done *and* there's >= 40 phys bit that would put max phyusical boundary to 1T
// This should allow virtual firmware to avoid the reserved range at the
// 1T boundary on VFs with big bars."
if (address != 0x000000fd00000000 || length != (0x000000ffffffffff - 0x000000fd00000000) + 1)
#endif
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Reserved, start_address, length });
break;
case (MULTIBOOT_MEMORY_ACPI_RECLAIMABLE):
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::ACPI_Reclaimable, start_address, length });
break;
case (MULTIBOOT_MEMORY_NVS):
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::ACPI_NVS, start_address, length });
break;
case (MULTIBOOT_MEMORY_BADRAM):
dmesgln("MM: Warning, detected bad memory range!");
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::BadMemory, start_address, length });
break;
default:
dbgln("MM: Unknown range!");
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Unknown, start_address, length });
break;
}
if (mmap->type != MULTIBOOT_MEMORY_AVAILABLE)
for (auto range : global_data.physical_memory_ranges) {
if (range.type != PhysicalMemoryRangeType::Usable)
continue;
auto address = range.start.get();
auto length = range.length;
// Fix up unaligned memory regions.
auto diff = (FlatPtr)address % PAGE_SIZE;
if (diff != 0) {
dmesgln("MM: Got an unaligned physical_region from the bootloader; correcting {:p} by {} bytes", address, diff);
dmesgln("MM: Got an unaligned usable physical_region from the bootloader; correcting {:p} by {} bytes", address, diff);
diff = PAGE_SIZE - diff;
address += diff;
length -= diff;
}
if ((length % PAGE_SIZE) != 0) {
dmesgln("MM: Got an unaligned physical_region from the bootloader; correcting length {} by {} bytes", length, length % PAGE_SIZE);
dmesgln("MM: Got an unaligned usable physical_region from the bootloader; correcting length {} by {} bytes", length, length % PAGE_SIZE);
length -= length % PAGE_SIZE;
}
if (length < PAGE_SIZE) {
dmesgln("MM: Memory physical_region from bootloader is too small; we want >= {} bytes, but got {} bytes", PAGE_SIZE, length);
dmesgln("MM: Memory usable physical_region from bootloader is too small; we want >= {} bytes, but got {} bytes", PAGE_SIZE, length);
continue;
}
@ -391,13 +346,12 @@ UNMAP_AFTER_INIT void MemoryManager::parse_memory_map()
last_contiguous_physical_range->upper = addr;
}
}
}
// FIXME: If this is ever false, theres a good chance that all physical memory is already spent
if (last_contiguous_physical_range.has_value()) {
auto range = last_contiguous_physical_range.release_value();
// FIXME: OOM?
global_data.physical_regions.append(PhysicalRegion::try_create(range.lower, range.upper).release_nonnull());
// FIXME: If this is ever false, theres a good chance that all physical memory is already spent
if (last_contiguous_physical_range.has_value()) {
auto range = last_contiguous_physical_range.release_value();
// FIXME: OOM?
global_data.physical_regions.append(PhysicalRegion::try_create(range.lower, range.upper).release_nonnull());
}
}
for (auto& region : global_data.physical_regions)
@ -426,6 +380,247 @@ UNMAP_AFTER_INIT void MemoryManager::parse_memory_map()
});
}
UNMAP_AFTER_INIT void MemoryManager::parse_memory_map_fdt(MemoryManager::GlobalData& global_data, u8 const* fdt_addr)
{
auto const& fdt_header = *reinterpret_cast<DeviceTree::FlattenedDeviceTreeHeader const*>(fdt_addr);
auto fdt_buffer = ReadonlyBytes(fdt_addr, fdt_header.totalsize);
// FIXME: Parse the MemoryReservationBlock
// Schema:
// https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/root-node.yaml
// -> /#address-cells ∈ [1,2], /#size-cells ∈ [1,2]
// Reserved Memory:
// https://android.googlesource.com/kernel/msm/+/android-7.1.0_r0.2/Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt
// -> #address-cells === /#address-cells, #size-cells === /#size-cells
// https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/reserved-memory/reserved-memory.yaml
// Memory:
// https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/memory.yaml
// -> #address-cells: /#address-cells , #size-cells: /size-cells #
// FIXME: When booting from UEFI, the /memory node may not be relied upon
enum class State {
Root,
InReservedMemory,
InReservedMemoryChild,
InMemory
};
struct {
u32 depth = 0;
State state = State::Root;
Optional<u64> start {};
Optional<u64> size {};
u32 address_cells = 0;
u32 size_cells = 0;
} state;
MUST(DeviceTree::walk_device_tree(
fdt_header, fdt_buffer,
DeviceTree::DeviceTreeCallbacks {
.on_node_begin = [&state](StringView node_name) -> ErrorOr<IterationDecision> {
switch (state.state) {
case State::Root:
if (state.depth != 1)
break;
if (node_name == "reserved-memory")
state.state = State::InReservedMemory;
else if (node_name.starts_with("memory"sv))
state.state = State::InMemory;
break;
case State::InReservedMemory:
// FIXME: The node names may hint to the purpose
state.state = State::InReservedMemoryChild;
state.start = {};
state.size = {};
break;
case State::InReservedMemoryChild:
case State::InMemory:
// We should never be here
VERIFY_NOT_REACHED();
}
state.depth++;
return IterationDecision::Continue;
},
.on_node_end = [&global_data, &state](StringView node_name) -> ErrorOr<IterationDecision> {
switch (state.state) {
case State::Root:
break;
case State::InReservedMemory:
state.state = State::Root;
break;
case State::InMemory:
VERIFY(state.start.has_value() && state.size.has_value());
dbgln("MM: Memory Range {}: address: {} size {:#x}", node_name, PhysicalAddress { state.start.value() }, state.size.value());
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Usable, PhysicalAddress { state.start.value() }, state.size.value() });
state.state = State::Root;
break;
case State::InReservedMemoryChild:
// FIXME: Handle non static allocations,
VERIFY(state.start.has_value() && state.size.has_value());
dbgln("MM: Reserved Range {}: address: {} size {:#x}", node_name, PhysicalAddress { state.start.value() }, state.size.value());
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Reserved, PhysicalAddress { state.start.value() }, state.size.value() });
// FIXME: Not all of these are "used", only those in "memory" are actually "used"
// There might be for example debug DMA control registers, which are marked as reserved
global_data.used_memory_ranges.append(UsedMemoryRange { UsedMemoryRangeType::BootModule, PhysicalAddress { state.start.value() }, PhysicalAddress { state.start.value() + state.size.value() } });
state.state = State::InReservedMemory;
break;
}
state.depth--;
return IterationDecision::Continue;
},
.on_property = [&state](StringView property_name, ReadonlyBytes data) -> ErrorOr<IterationDecision> {
switch (state.state) {
case State::Root:
if (state.depth != 1)
break;
if (property_name == "#address-cells"sv) {
BigEndian<u32> data_as_int;
__builtin_memcpy(&data_as_int, data.data(), sizeof(u32));
state.address_cells = data_as_int;
VERIFY(state.address_cells != 0);
VERIFY(state.address_cells <= 2);
} else if (property_name == "#size-cells"sv) {
BigEndian<u32> data_as_int;
__builtin_memcpy(&data_as_int, data.data(), sizeof(u32));
state.size_cells = data_as_int;
VERIFY(state.size_cells != 0);
VERIFY(state.size_cells <= 2);
}
break;
case State::InReservedMemory:
// FIXME: We could check and verify that the address and size cells
// are the same as in the root node
// FIXME: Handle the ranges attribute if not empty
if (property_name == "ranges"sv && data.size() != 0)
TODO();
break;
case State::InReservedMemoryChild:
case State::InMemory:
if (property_name == "reg"sv) {
VERIFY(state.address_cells);
VERIFY(state.size_cells);
// FIXME: We may get more than one range here
if (data.size() > (state.address_cells + state.size_cells) * sizeof(u32))
TODO();
if (state.address_cells == 1) {
BigEndian<u32> data_as_int;
__builtin_memcpy(&data_as_int, data.data(), sizeof(u32));
state.start = data_as_int;
data = data.slice(sizeof(u32));
} else {
BigEndian<u64> data_as_int;
__builtin_memcpy(&data_as_int, data.data(), sizeof(u64));
state.start = data_as_int;
data = data.slice(sizeof(u64));
}
if (state.size_cells == 1) {
BigEndian<u32> data_as_int;
__builtin_memcpy(&data_as_int, data.data(), sizeof(u32));
state.size = data_as_int;
data = data.slice(sizeof(u32));
} else {
BigEndian<u64> data_as_int;
__builtin_memcpy(&data_as_int, data.data(), sizeof(u64));
state.size = data_as_int;
data = data.slice(sizeof(u64));
}
} else {
// Reserved Memory:
// FIXME: Handle `compatible: "framebuffer";`
// FIMXE: Handle `compatible: "shared-dma-pool";`, `compatible: "restricted-dma-pool";`
// FIXME: Handle "iommu-addresses" property
// FIXME: Support "size" and "align" property
// Also "alloc-ranges"
// FIXME: Support no-map
// FIXME: Support no-map-fixup
// FIXME: Support reusable
}
break;
}
return IterationDecision::Continue;
},
.on_noop = []() -> ErrorOr<IterationDecision> { return IterationDecision::Continue; },
.on_end = []() -> ErrorOr<void> { return {}; },
}));
// FDTs do not seem to be fully sort memory ranges, especially as we get them from at least two structures
quick_sort(global_data.physical_memory_ranges, [](auto& a, auto& b) -> bool { return a.start > b.start; });
}
UNMAP_AFTER_INIT void MemoryManager::parse_memory_map_multiboot(MemoryManager::GlobalData& global_data)
{
// Register used memory regions that we know of.
if (multiboot_flags & 0x4) {
auto* bootmods_start = multiboot_copy_boot_modules_array;
auto* bootmods_end = bootmods_start + multiboot_copy_boot_modules_count;
for (auto* bootmod = bootmods_start; bootmod < bootmods_end; bootmod++) {
global_data.used_memory_ranges.append(UsedMemoryRange { UsedMemoryRangeType::BootModule, PhysicalAddress(bootmod->start), PhysicalAddress(bootmod->end) });
}
}
auto* mmap_begin = multiboot_memory_map;
auto* mmap_end = multiboot_memory_map + multiboot_memory_map_count;
struct ContiguousPhysicalVirtualRange {
PhysicalAddress lower;
PhysicalAddress upper;
};
Optional<ContiguousPhysicalVirtualRange> last_contiguous_physical_range;
for (auto* mmap = mmap_begin; mmap < mmap_end; mmap++) {
// We have to copy these onto the stack, because we take a reference to these when printing them out,
// and doing so on a packed struct field is UB.
auto address = mmap->addr;
auto length = mmap->len;
ArmedScopeGuard write_back_guard = [&]() {
mmap->addr = address;
mmap->len = length;
};
dmesgln("MM: Multiboot mmap: address={:p}, length={}, type={}", address, length, mmap->type);
auto start_address = PhysicalAddress(address);
switch (mmap->type) {
case (MULTIBOOT_MEMORY_AVAILABLE):
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Usable, start_address, length });
break;
case (MULTIBOOT_MEMORY_RESERVED):
#if ARCH(X86_64)
// Workaround for https://gitlab.com/qemu-project/qemu/-/commit/8504f129450b909c88e199ca44facd35d38ba4de
// That commit added a reserved 12GiB entry for the benefit of virtual firmware.
// We can safely ignore this block as it isn't actually reserved on any real hardware.
// From: https://lore.kernel.org/all/20220701161014.3850-1-joao.m.martins@oracle.com/
// "Always add the HyperTransport range into e820 even when the relocation isn't
// done *and* there's >= 40 phys bit that would put max phyusical boundary to 1T
// This should allow virtual firmware to avoid the reserved range at the
// 1T boundary on VFs with big bars."
if (address != 0x000000fd00000000 || length != (0x000000ffffffffff - 0x000000fd00000000) + 1)
#endif
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Reserved, start_address, length });
break;
case (MULTIBOOT_MEMORY_ACPI_RECLAIMABLE):
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::ACPI_Reclaimable, start_address, length });
break;
case (MULTIBOOT_MEMORY_NVS):
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::ACPI_NVS, start_address, length });
break;
case (MULTIBOOT_MEMORY_BADRAM):
dmesgln("MM: Warning, detected bad memory range!");
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::BadMemory, start_address, length });
break;
default:
dbgln("MM: Unknown range!");
global_data.physical_memory_ranges.append(PhysicalMemoryRange { PhysicalMemoryRangeType::Unknown, start_address, length });
break;
}
}
}
UNMAP_AFTER_INIT void MemoryManager::initialize_physical_pages()
{
m_global_data.with([&](auto& global_data) {