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:
parent
21a21c6a11
commit
d3f6b03733
10 changed files with 368 additions and 119 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue