diff --git a/api/boot/multiboot.h b/api/boot/multiboot.h index ab43187787..240d6b9b65 100644 --- a/api/boot/multiboot.h +++ b/api/boot/multiboot.h @@ -195,12 +195,19 @@ typedef struct multiboot_info multiboot_info_t; struct multiboot_mmap_entry { - multiboot_uint32_t size; + multiboot_uint32_t size; // size of struct multiboot_uint64_t addr; - multiboot_uint64_t len; + multiboot_uint64_t len; // bytes available #define MULTIBOOT_MEMORY_AVAILABLE 1 #define MULTIBOOT_MEMORY_RESERVED 2 + #define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 + #define MULTIBOOT_MEMORY_NVS 4 + #define MULTIBOOT_MEMORY_BADRAM 5 multiboot_uint32_t type; + + [[nodiscard]] constexpr bool is_available() const noexcept { + return type == MULTIBOOT_MEMORY_AVAILABLE; + } } __attribute__((packed)); typedef struct multiboot_mmap_entry multiboot_memory_map_t; diff --git a/api/kernel/memmap.hpp b/api/kernel/memmap.hpp index 1009c2388f..ac6cef6c03 100644 --- a/api/kernel/memmap.hpp +++ b/api/kernel/memmap.hpp @@ -339,6 +339,12 @@ class Memory_map { */ Fixed_memory_range& assign_range(const Fixed_memory_range::size_type size); + /** + * Removes a memory range previously defined + * Useful to redefine the purpose of a range + */ + void unassign_range(const Fixed_memory_range& range); + /** * Check if an address is within a range in the map * diff --git a/api/util/bitops.hpp b/api/util/bitops.hpp index 4c9c0e6175..76e9088acf 100644 --- a/api/util/bitops.hpp +++ b/api/util/bitops.hpp @@ -247,7 +247,7 @@ inline bool is_aligned(uintptr_t A, uintptr_t ptr) noexcept return (ptr & (A - 1)) == 0; } -inline size_t upercent(size_t a, size_t b) noexcept +inline std::size_t upercent(std::size_t a, std::size_t b) noexcept { return (100 * a + b / 2) / b; } diff --git a/example/src/main.cpp b/example/src/main.cpp index dabb793620..b2bfb97fba 100644 --- a/example/src/main.cpp +++ b/example/src/main.cpp @@ -1,9 +1,16 @@ +#include "kernel/memory.hpp" #include #include void Service::start(const std::string& args){ - printf("Args = %s\n", args.c_str()); - printf("Try giving the service less memory, eg. 10MB in vm.json\n"); - printf("Service done. Shutting down...\n"); + std::println("Hello from the example unikernel!"); + + std::println("Current virtual mappings:"); + for (const auto& entry : os::mem::vmmap()) + std::println(" {}", entry.second.to_string()); + + std::println("Tip: Try changing how much memory you give to the service in vm.json"); + std::println("Service done. Shutting down..."); + os::shutdown(); } diff --git a/example/src/vm.json b/example/src/vm.json new file mode 100644 index 0000000000..7b976178d7 --- /dev/null +++ b/example/src/vm.json @@ -0,0 +1,3 @@ +{ + "mem": 16384 +} diff --git a/src/arch/x86_64/paging.cpp b/src/arch/x86_64/paging.cpp index cd3c789182..eeed3433ed 100644 --- a/src/arch/x86_64/paging.cpp +++ b/src/arch/x86_64/paging.cpp @@ -376,6 +376,8 @@ uintptr_t mem::active_page_size(uintptr_t addr){ void allow_executable() { + // FIXME: this seems weird. is this only used for tests? + INFO2("* Allowing execute on %p -> %p", (void*) __exec_begin, (void*)__exec_end); @@ -391,7 +393,7 @@ void allow_executable() m.page_sizes = os::mem::Map::any_size; m.flags = os::mem::Access::execute | os::mem::Access::read; - os::mem::map(m, "ELF .text"); + os::mem::map(m, "Executable"); } /* TODO: Compiler warning unused diff --git a/src/kernel/memmap.cpp b/src/kernel/memmap.cpp index 38ca1dd009..1c52cabfb6 100644 --- a/src/kernel/memmap.cpp +++ b/src/kernel/memmap.cpp @@ -173,6 +173,21 @@ Fixed_memory_range& Memory_map::assign_range(Fixed_memory_range&& rng) { return new_entry.first->second; } +/////////////////////////////////////////////////////////////////////////////// +void Memory_map::unassign_range(const Fixed_memory_range& range) { + auto it = map_.find(range.addr_start()); + + if (it != map_.end()) { + if (it->second.addr_end() == range.addr_end()) { + map_.erase(it); + } else { + throw Memory_range_exception{"Range mismatch at address " + std::to_string(range.addr_start())}; + } + } else { + throw Memory_range_exception{"No range found to erase at " + std::to_string(range.addr_start())}; + } +} + /////////////////////////////////////////////////////////////////////////////// Fixed_memory_range& Memory_map::at(const Key key) { return const_cast(static_cast(this)->at(key)); diff --git a/src/kernel/multiboot.cpp b/src/kernel/multiboot.cpp index 2959820167..4914474304 100644 --- a/src/kernel/multiboot.cpp +++ b/src/kernel/multiboot.cpp @@ -22,6 +22,7 @@ #include #include #include +#include template static inline void _kfmt(fmt::string_view prefix, fmt::format_string fmtstr, Args&&... args) { @@ -122,9 +123,41 @@ uintptr_t _multiboot_free_begin(uintptr_t boot_addr) return multi_end; } +constexpr static inline const char* multiboot_memory_type_str(uint32_t type) noexcept { + // TODO: convert multiboot types to enum class + switch (type) { + case MULTIBOOT_MEMORY_AVAILABLE: + return "Available"; + case MULTIBOOT_MEMORY_ACPI_RECLAIMABLE: + return "ACPI Reclaimable"; + case MULTIBOOT_MEMORY_NVS: + return "ACPI Non-volatile Storage"; + case MULTIBOOT_MEMORY_BADRAM: + return "Bad RAM"; + case MULTIBOOT_MEMORY_RESERVED: + return "Reserved"; + default: + return "UNKNOWN"; + } +} + + +std::span _multiboot_memory_maps() { + auto* info = kernel::bootinfo(); + + auto* hardware_map = reinterpret_cast(info->mmap_addr); + const size_t entry_count = static_cast(info->mmap_length / sizeof(multiboot_memory_map_t)); + + return std::span { hardware_map, entry_count }; +} + void kernel::multiboot(uint32_t boot_addr) { - MYINFO("Booted with multiboot"); +#if defined(__x86_64) + MYINFO("Booted with multiboot x86_64"); +#else + MYINFO("Booted with multiboot x86"); +#endif auto* info = ::bootinfo(boot_addr); INFO2("* Boot flags: {:#x}", info->flags); @@ -152,36 +185,50 @@ void kernel::multiboot(uint32_t boot_addr) } if (info->flags & MULTIBOOT_INFO_MEM_MAP) { - INFO2("* Multiboot provided memory map ({} entries @ {})", - info->mmap_length / sizeof(multiboot_memory_map_t), - (const void*)(uintptr_t)info->mmap_addr); - std::span mmap { - reinterpret_cast(info->mmap_addr), - static_cast(info->mmap_length / sizeof(multiboot_memory_map_t)) - }; - - for (auto map : mmap) + auto* hardware_map = reinterpret_cast(info->mmap_addr); + const size_t entry_count = static_cast(info->mmap_length / sizeof(multiboot_memory_map_t)); + + INFO2("* Multiboot provided memory map ({} entries @ {})\n", entry_count, reinterpret_cast(hardware_map)); + + for (auto map : std::span{ hardware_map, entry_count }) { - const char* str_type = map.type & MULTIBOOT_MEMORY_AVAILABLE ? "FREE" : "RESERVED"; - const uintptr_t addr = map.addr; + const uintptr_t start = map.addr; const uintptr_t size = map.len; - INFO2(" {:#x} - {:#x} {} ({} KiB)", addr, addr + size - 1, str_type, size / 1024); - - if (not (map.type & MULTIBOOT_MEMORY_AVAILABLE)) { + const uintptr_t end = start + size - 1; - if (util::bits::is_aligned<4_KiB>(map.addr)) { - os::mem::map({addr, addr, os::mem::Access::read | os::mem::Access::write, size}, - "Reserved (Multiboot)"); - continue; - } + INFO2(" {:#16x} - {:#16x} ({} KiB): {}", start, end, size / 1024, multiboot_memory_type_str(map.type)); - // For non-aligned addresses, assign - os::mem::vmmap().assign_range({addr, addr + size - 1, "Reserved (Multiboot)"}); + // os::mem::map() does not accept non-aligned page addresses + if (not util::bits::is_aligned<4_KiB>(map.addr)) { + os::mem::vmmap().assign_range({start, start + size - 1, "UNALIGNED"}); + continue; } - else + + os::mem::Map rw_map = { /*.linear=*/start, /*.physical=*/start, /*.fl=*/os::mem::Access::read | os::mem::Access::write, /*.sz=*/size }; + switch (map.type) { - // Map as free memory - //os::mem::map_avail({map.addr, map.addr, {os::mem::Access::read | os::mem::Access::write}, map.len}, "Reserved (Multiboot)"); + case MULTIBOOT_MEMORY_ACPI_RECLAIMABLE: + os::mem::map(rw_map, "Multiboot (ACPI Reclaimable)"); + break; + case MULTIBOOT_MEMORY_NVS: + os::mem::map(rw_map, "Multiboot (ACPI Non-volatile Storage)"); + break; + case MULTIBOOT_MEMORY_BADRAM: + os::mem::map(rw_map, "Multiboot (Bad RAM)"); + break; + case MULTIBOOT_MEMORY_RESERVED: + os::mem::map(rw_map, "Multiboot (Reserved)"); + break; + + case MULTIBOOT_MEMORY_AVAILABLE: { + // these are mapped in src/platform/${platform}/os.cpp + break; + } + default: { + char buf[32]; // libc is not entirely initialized at this point + std::snprintf(buf, sizeof(buf), "Unknown memory map type: %d", map.type); + os::panic(buf); + } } } INFO2(""); diff --git a/src/musl/common.hpp b/src/musl/common.hpp index 40ac85391b..1970f65b70 100644 --- a/src/musl/common.hpp +++ b/src/musl/common.hpp @@ -74,6 +74,7 @@ template inline auto strace(Fn func, [[maybe_unused]]const char* name, Args&&... args) { if (!kernel::state().allow_syscalls) { fprintf(stderr, "Syscalls not allowed here. Unexpected call to %s - terminating\n", name); + os::halt(); Expects(kernel::state().allow_syscalls); } diff --git a/src/platform/x86_pc/os.cpp b/src/platform/x86_pc/os.cpp index 9b2097889c..ddb1b119d5 100644 --- a/src/platform/x86_pc/os.cpp +++ b/src/platform/x86_pc/os.cpp @@ -48,6 +48,8 @@ extern uintptr_t _ELF_END_; // in kernel/os.cpp extern bool os_default_stdout; +extern std::span _multiboot_memory_maps(); + struct alignas(SMP_ALIGN) OS_CPU { uint64_t cycles_hlt = 0; }; @@ -125,10 +127,6 @@ void kernel::start(uint32_t boot_magic, uint32_t boot_addr) MYINFO("Total memory detected as %s ", util::Byte_r(kernel::memory_end()).to_string().c_str()); - // Give the rest of physical memory to heap - kernel::state().heap_max = kernel::memory_end() - 1; - assert(kernel::heap_begin() != 0x0 and kernel::heap_max() != 0x0); - PROFILE("Memory map"); // Assign memory ranges used by the kernel auto& memmap = os::mem::vmmap(); @@ -145,13 +143,28 @@ void kernel::start(uint32_t boot_magic, uint32_t boot_addr) memmap.assign_range({0x10000, 0x9d3ff, "Stack"}); #endif - // heap (physical) area - uintptr_t span_max = std::numeric_limits::max(); - uintptr_t heap_range_max_ = std::min(span_max, kernel::heap_max()); + multiboot_memory_map_t heap_map = {0,0,0,0}; + for (auto entry : _multiboot_memory_maps()) + { + if (not entry.is_available()) continue; + + if (entry.len > heap_map.len) { + heap_map = entry; + } + } + uintptr_t end = heap_map.addr + heap_map.len - 1; + + if (heap_map.addr < 0x1'000'000) { + kernel::state().heap_begin = std::max((uintptr_t)0x1'000'000, (uintptr_t)heap_map.addr); + kernel::state().heap_max = std::min(kernel::heap_max(), end); + } else { + kernel::state().heap_begin = heap_map.addr; + kernel::state().heap_max = end; + } - INFO2("* Assigning heap 0x%zx -> 0x%zx", kernel::heap_begin(), heap_range_max_); - memmap.assign_range({kernel::heap_begin(), heap_range_max_, - "Dynamic memory", kernel::heap_usage }); + + INFO2("* Assigning heap 0x%lx -> 0x%lx", kernel::heap_begin(), kernel::heap_max()); + memmap.assign_range({kernel::heap_begin(), kernel::heap_max(), "Heap", kernel::heap_usage }); MYINFO("Virtual memory map"); for (const auto& entry : memmap) @@ -182,7 +195,7 @@ void os::event_loop() __arch_poweroff(); } - +/* legacy boot is used when MULTIBOOT_BOOTLOADER_MAGIC is unset, see x86_pc/kernel_start.cpp */ void kernel::legacy_boot() { // Fetch CMOS memory info (unfortunately this is maximally 10^16 kb)